diff --git a/.gitignore b/.gitignore index b5aee5f..f713805 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ /node_modules/ -/dist/ -/lib/ +/app/ /client.config.toml /webirc.data.json diff --git a/README.md b/README.md index 5950e37..f8c7acc 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This application requires [node.js](https://nodejs.org/) to be installed. 1. Install the dependencies `npm install` 2. Copy the configuration `cp client.config.example.toml client.config.toml` 3. Build the project using `npm run build` -4. Run the server `./teemant.js` +4. Run the server `npm start` The client will be accessible at http://localhost:9000/ diff --git a/index.js b/index.js new file mode 100755 index 0000000..59db561 --- /dev/null +++ b/index.js @@ -0,0 +1,8 @@ +#!/usr/bin/env node +const path = require('path') + +try { + require(path.join(__dirname, 'app')) +} catch (e) { + console.error('Please build the application before running.') +} diff --git a/package-lock.json b/package-lock.json index 8eb7c35..37177c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1055,6 +1055,12 @@ "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", "dev": true }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, "ansi-align": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", @@ -1149,6 +1155,21 @@ "es-abstract": "^1.7.0" } }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -1910,12 +1931,29 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, "clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", "dev": true }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -1986,6 +2024,76 @@ "typedarray": "^0.0.6" } }, + "concurrently": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-4.1.0.tgz", + "integrity": "sha512-pwzXCE7qtOB346LyO9eFWpkFJVO3JQZ/qU/feGeaAHiX1M3Rw3zgXKc5cZ8vSH5DGygkjzLFDzA/pwoQDkRNGg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "date-fns": "^1.23.0", + "lodash": "^4.17.10", + "read-pkg": "^4.0.1", + "rxjs": "^6.3.3", + "spawn-command": "^0.0.2-1", + "supports-color": "^4.5.0", + "tree-kill": "^1.1.0", + "yargs": "^12.0.1" + }, + "dependencies": { + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", + "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", + "dev": true, + "requires": { + "normalize-package-data": "^2.3.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0" + } + }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "^2.0.0" + } + } + } + }, "configstore": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", @@ -2070,6 +2178,127 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, + "copy-webpack-plugin": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.6.0.tgz", + "integrity": "sha512-Y+SQCF+0NoWQryez2zXn5J5knmr9z/9qSQt7fbL78u83rxmigOy8X5+BFn8CFSuX+nKT8gpYwJX68ekqtQt6ZA==", + "dev": true, + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "globby": "^7.1.1", + "is-glob": "^4.0.0", + "loader-utils": "^1.1.0", + "minimatch": "^3.0.4", + "p-limit": "^1.0.0", + "serialize-javascript": "^1.4.0" + }, + "dependencies": { + "cacache": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "mississippi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", + "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "ssri": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -2215,6 +2444,12 @@ } } }, + "css-parse": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", + "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=", + "dev": true + }, "css-select": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", @@ -2308,6 +2543,12 @@ "es5-ext": "^0.10.9" } }, + "date-fns": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", + "dev": true + }, "date-now": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", @@ -2491,6 +2732,32 @@ "randombytes": "^2.0.0" } }, + "dir-glob": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.0.tgz", + "integrity": "sha512-YqrO+bduKFqPgspvpjDAaKk0qhmvY+SY7NjIRljCDAy6CX7Ft65irIduHbrYXhy+BxJnYKjWuREw6X42w9/+DQ==", + "dev": true, + "requires": { + "path-type": "^3.0.0" + }, + "dependencies": { + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -4098,6 +4365,12 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, "get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -4166,6 +4439,40 @@ "integrity": "sha512-0GZF1RiPKU97IHUO5TORo9w1PwrH/NBPl+fS7oMLdaTRiYmYbwK4NWoZWrAdd0/abG9R2BU+OiwyQpTpE6pdfQ==", "dev": true }, + "globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + }, + "dependencies": { + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + } + } + }, "got": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", @@ -4609,6 +4916,12 @@ "loose-envify": "^1.0.0" } }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, "ipaddr.js": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", @@ -4993,6 +5306,15 @@ "package-json": "^4.0.0" } }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -5161,6 +5483,15 @@ } } }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -5204,6 +5535,17 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, + "mem": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", + "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^1.1.0" + } + }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -5659,6 +6001,12 @@ "boolbase": "~1.0.0" } }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, "object-assign": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", @@ -5828,6 +6176,43 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + }, + "dependencies": { + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -5845,12 +6230,24 @@ "mkdirp": "^0.5.1" } }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, + "p-is-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", + "dev": true + }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -6667,6 +7064,18 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, "require-uncached": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", @@ -6802,6 +7211,12 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "sax": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", + "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=", + "dev": true + }, "schema-utils": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", @@ -6812,6 +7227,11 @@ "ajv-keywords": "^3.1.0" } }, + "seedrandom": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.4.tgz", + "integrity": "sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA==" + }, "semver": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", @@ -6864,6 +7284,12 @@ "send": "0.16.2" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", @@ -7184,6 +7610,12 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, + "spawn-command": { + "version": "0.0.2-1", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", + "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", + "dev": true + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -7402,6 +7834,45 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, + "stylus": { + "version": "0.54.5", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz", + "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=", + "dev": true, + "requires": { + "css-parse": "1.7.x", + "debug": "*", + "glob": "7.0.x", + "mkdirp": "0.5.x", + "sax": "0.5.x", + "source-map": "0.1.x" + }, + "dependencies": { + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, "stylus-loader": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.2.tgz", @@ -7639,6 +8110,12 @@ "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", "dev": true }, + "tree-kill": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", + "dev": true + }, "trim-newlines": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", @@ -8171,6 +8648,12 @@ "isexe": "^2.0.0" } }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, "widest-line": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", @@ -8195,6 +8678,47 @@ "errno": "~0.1.7" } }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -8264,6 +8788,93 @@ "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + }, + "dependencies": { + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "yargs-parser": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", diff --git a/package.json b/package.json index f770d7f..2af7f85 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,19 @@ "name": "teemantirc", "version": "2.0.0", "description": "Web-based IRC client", - "main": "teemant.js", + "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "css": "mkdir -p app/public/style && stylus -o app/public/style src/style/*.styl", + "css:watch": "mkdir -p app/public/style && stylus -w -o app/public/style src/style/*.styl", + "js": "webpack -p --config webpack.config.js", + "js:watch": "webpack -w --mode=development --config webpack.config.js", + "app": "babel src/server -d app", + "app:watch": "babel src/server -d app -w", + "build": "npm run app && npm run js && npm run css", + "watch": "concurrently --kill-others \"npm run app:watch\" \"npm run css:watch\" \"npm run js:watch\"", + "clean": "rm -rf app", + "start": "node index.js" }, "keywords": [ "irc" @@ -13,6 +23,7 @@ "license": "MIT", "dependencies": { "express": "^4.16.4", + "seedrandom": "^2.4.4", "socket.io": "^2.2.0", "toml": "^2.3.5" }, @@ -25,11 +36,14 @@ "@babel/core": "^7.2.2", "@babel/preset-env": "^7.2.3", "babel-loader": "^8.0.5", + "concurrently": "^4.1.0", + "copy-webpack-plugin": "^4.6.0", "css-loader": "^2.1.0", "file-loader": "^3.0.1", "html-webpack-plugin": "^3.2.0", "mini-css-extract-plugin": "^0.5.0", "standard": "^12.0.1", + "stylus": "^0.54.5", "stylus-loader": "^3.0.2", "webpack": "^4.28.1", "webpack-command": "^0.4.2" diff --git a/src/document/index.html b/src/document/index.html index 66a20f0..3a3a73d 100644 --- a/src/document/index.html +++ b/src/document/index.html @@ -6,11 +6,10 @@ TeemantIRC - - + - + diff --git a/src/js/colorparser.js b/src/js/colorparser.js index f2c9706..bc28933 100644 --- a/src/js/colorparser.js +++ b/src/js/colorparser.js @@ -1,83 +1,90 @@ +/* eslint-disable no-useless-escape, no-control-regex */ + // Shamelessly copied from https://github.com/megawac/irc-style-parser/ // Lol, I just gave credit, didn't I.. // Dammit, well, there's that. -const styleCheck_Re = /[\x00-\x1F]/; -const back_re = /^(\d{1,2})(,(\d{1,2}))?/; -const colourKey = '\x03'; -const colour_re = /\x03/g; -const styleBreak = '\x0F'; // breaks all open styles ^O (\x0F) +import { el } from './utility' + +const styleCheckRe = /[\x00-\x1F]/ +const backRe = /^(\d{1,2})(,(\d{1,2}))?/ +const colourKey = '\x03' +const colourRe = /\x03/g +const styleBreak = '\x0F' // breaks all open styles ^O (\x0F) let styles = [ - ['normal', '\x00', ''], ['underline', '\x1F'], - ['bold', '\x02'], ['italic', '\x1D'] -].map(function(style) { - var escaped = encodeURI(style[1]).replace('%', '\\x'); - return { - name: style[0], - style: style[2] != null ? style[2] : 'irc-' + style[0], - key: style[1], - keyregex: new RegExp(escaped + '(.*?)(' + escaped + '|$)') - }; -}); + ['normal', '\x00', ''], ['underline', '\x1F'], + ['bold', '\x02'], ['italic', '\x1D'] +].map(function (style) { + var escaped = encodeURI(style[1]).replace('%', '\\x') + return { + name: style[0], + style: style[2] != null ? style[2] : 'irc-' + style[0], + key: style[1], + keyregex: new RegExp(escaped + '(.*?)(' + escaped + '|$)') + } +}) -//http://www.mirc.com/colors.html +// http://www.mirc.com/colors.html let colors = [ - 'white', 'black', 'navy', 'green', 'red', 'brown', - 'purple', 'olive', 'yellow', 'lightgreen', 'teal', - 'cyan', 'blue', 'pink', 'gray', 'lightgrey' -].reduce(function(memo, name, index) { - memo[index] = { - name: name, - fore: 'irc-fg' + index, - back: 'irc-bg' + index, - key: index - }; - return memo; -}, {}); + 'white', 'black', 'navy', 'green', 'red', 'brown', + 'purple', 'olive', 'yellow', 'lightgreen', 'teal', + 'cyan', 'blue', 'pink', 'gray', 'lightgrey' +].reduce(function (memo, name, index) { + memo[index] = { + name: name, + fore: 'irc-fg' + index, + back: 'irc-bg' + index, + key: index + } + return memo +}, {}) -function stylize(line) { - // Recheck - if (!styleCheck_Re.test(line)) return line; +function stylize (line) { + // Recheck + if (!styleCheckRe.test(line)) return line - // split up by the irc style break character ^O - if (line.indexOf(styleBreak) >= 0) { - return line.split(styleBreak).map(stylize).join(''); - } + // split up by the irc style break character ^O + if (line.indexOf(styleBreak) >= 0) { + return line.split(styleBreak).map(stylize).join('') + } - var result = line; - var parseArr = result.split(colourKey); - var text, match, colour, background = ''; - for (var i = 0; i < parseArr.length; i++) { - text = parseArr[i]; - match = text.match(back_re); - colour = match && colors[+match[1]]; - if (!match || !colour) { - // ^C (no colour) ending. Escape current colour and carry on - background = ''; - continue; - } - // set the background colour - // we don't overide the background local var to support nesting - if (colors[+match[3]]) { - background = ' ' + colors[+match[3]].back; - } - // update the parsed text result - result = result.replace(colourKey + text, - '{1}'.format(colour.fore + background, text.slice(match[0].length))); - } + let result = line + let parseArr = result.split(colourKey) - // Matching styles (italics/bold/underline) - // if only colors were this easy... - styles.forEach(function(style) { - if (result.indexOf(style.key) < 0) return; - result = result.replace(style.keyregex, function(match, text) { - return '{1}'.format(style.style, text); - }); - }); + let text + let match + let colour + let background = '' + for (let i = 0; i < parseArr.length; i++) { + text = parseArr[i] + match = text.match(backRe) + colour = match && colors[+match[1]] + if (!match || !colour) { + // ^C (no colour) ending. Escape current colour and carry on + background = '' + continue + } + // set the background colour + // we don't overide the background local var to support nesting + if (colors[+match[3]]) { + background = ' ' + colors[+match[3]].back + } + // update the parsed text result + result = result.replace(colourKey + text, el('span', text.slice(match[0].length), [colour.fore + background])) + } - //replace the reminent colour terminations and be done with it - return result.replace(colour_re, ''); + // Matching styles (italics/bold/underline) + // if only colors were this easy... + styles.forEach(function (style) { + if (result.indexOf(style.key) < 0) return + result = result.replace(style.keyregex, function (match, text) { + return el('span', text, [style.style]) + }) + }) + + // replace the reminent colour terminations and be done with it + return result.replace(colourRe, '') } -module.exports = stylize; +export default stylize diff --git a/src/js/main.js b/src/js/main.js index e52dee2..6ce33f7 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -1,2230 +1,2243 @@ -const themes = require('./theme.js'); +/* global irc, twemoji, io */ +/* eslint-disable no-useless-escape, no-control-regex */ +import { el, span, sf, formatDate, removeStr, match, serialize, objectGetKey, rand, addClass, removeClass, toggleClass } from './utility' +import stylize from './colorparser' +import seedrandom from 'seedrandom' + +const themes = require('./theme.js') window.irc = { - socketUp: false, - primaryFrame: null, - config: { - colors: true, - channelify: true, - sharedInput: false, - timestamps: true, - timestampFormat: 'HH:mm:ss', - colorNicknames: true, - colorNicklist: false, - scrollOnResize: true, - scrollOnFocus: true, - emoji: true, - theme: 'default' - }, - serverData: {}, - serverChatQueue: {}, - chatType: 'simple', - documentTitle: 'TeemantIRC', -}; + socketUp: false, + primaryFrame: null, + config: { + colors: true, + channelify: true, + sharedInput: false, + timestamps: true, + timestampFormat: 'HH:mm:ss', + colorNicknames: true, + colorNicklist: false, + scrollOnResize: true, + scrollOnFocus: true, + emoji: true, + theme: 'default' + }, + serverData: {}, + serverChatQueue: {}, + chatType: 'simple', + documentTitle: 'TeemantIRC' +} -const clientdom = {connector: {}, settings: {}}; +const clientdom = { connector: {}, settings: {} } const colorizer = { - get_random_color: function(nickname) { - let themefunc = themes.available[irc.config.theme].nick_pallete; + get_random_color: function (nickname) { + let themefunc = themes.available[irc.config.theme].nick_pallete - Math.seedrandom(nickname); - let h = rand(themefunc.H[0], themefunc.H[1]); - let s = rand(themefunc.S[0], themefunc.S[1]); - let l = rand(themefunc.L[0], themefunc.L[1]); - return 'hsl(' + h + ',' + s + '%,' + l + '%)'; - }, - strip: function(message) { - return message.replace(/(\x03\d{0,2}(,\d{0,2})?)/g, '').replace(/[\x0F\x02\x16\x1F]/g, ''); - }, - stylize: require('./colorparser.js') -}; + let randgen = seedrandom(nickname) + let h = rand(randgen, themefunc.H[0], themefunc.H[1]) + let s = rand(randgen, themefunc.S[0], themefunc.S[1]) + let l = rand(randgen, themefunc.L[0], themefunc.L[1]) + return 'hsl(' + h + ',' + s + '%,' + l + '%)' + }, + strip: function (message) { + return message.replace(/(\x03\d{0,2}(,\d{0,2})?)/g, '').replace(/[\x0F\x02\x16\x1F]/g, '') + }, + stylize: stylize +} -/*********************\ -|** **| -|** UTILITIES **| -|** **| -\*********************/ +// Utilities -const validators = {}; +const validators = {} -validators.iporhost = function(str) { - let valid = false; +validators.iporhost = function (str) { + let valid = false - if(str.match(/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/i)) { - valid = true; - } else if (str.match(/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/i)) { - valid = true; - } + if (str.match(/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/i)) { + valid = true + } else if (str.match(/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/i)) { + valid = true + } - return valid; -}; + return valid +} -validators.nickname = function(str) { - if(str.match(/[a-z_\-\[\]\\^{}|`][a-z0-9_\-\[\]\\^{}|`]*/i)) { - return true; - } - return false; -}; +validators.nickname = function (str) { + if (str.match(/[a-z_\-\[\]\\^{}|`][a-z0-9_\-\[\]\\^{}|`]*/i)) { + return true + } + return false +} -validators.escapeHTML = function(str) { - return str.replace(/\/, '>'); -}; - -Date.prototype.format = function (format, utc){ - var date = this; - var MMMM = ['\x00', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; - var MMM = ['\x01', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; - var dddd = ['\x02', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; - var ddd = ['\x03', 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - function ii(i, len) { var s = i + ''; len = len || 2; while (s.length < len) s = '0' + s; return s; } - - var y = utc ? date.getUTCFullYear() : date.getFullYear(); - format = format.replace(/(^|[^\\])yyyy+/g, '$1' + y); - format = format.replace(/(^|[^\\])yy/g, '$1' + y.toString().substr(2, 2)); - format = format.replace(/(^|[^\\])y/g, '$1' + y); - - var M = (utc ? date.getUTCMonth() : date.getMonth()) + 1; - format = format.replace(/(^|[^\\])MMMM+/g, '$1' + MMMM[0]); - format = format.replace(/(^|[^\\])MMM/g, '$1' + MMM[0]); - format = format.replace(/(^|[^\\])MM/g, '$1' + ii(M)); - format = format.replace(/(^|[^\\])M/g, '$1' + M); - - var d = utc ? date.getUTCDate() : date.getDate(); - format = format.replace(/(^|[^\\])dddd+/g, '$1' + dddd[0]); - format = format.replace(/(^|[^\\])ddd/g, '$1' + ddd[0]); - format = format.replace(/(^|[^\\])dd/g, '$1' + ii(d)); - format = format.replace(/(^|[^\\])d/g, '$1' + d); - - var H = utc ? date.getUTCHours() : date.getHours(); - format = format.replace(/(^|[^\\])HH+/g, '$1' + ii(H)); - format = format.replace(/(^|[^\\])H/g, '$1' + H); - - var h = H > 12 ? H - 12 : H == 0 ? 12 : H; - format = format.replace(/(^|[^\\])hh+/g, '$1' + ii(h)); - format = format.replace(/(^|[^\\])h/g, '$1' + h); - - var m = utc ? date.getUTCMinutes() : date.getMinutes(); - format = format.replace(/(^|[^\\])mm+/g, '$1' + ii(m)); - format = format.replace(/(^|[^\\])m/g, '$1' + m); - - var s = utc ? date.getUTCSeconds() : date.getSeconds(); - format = format.replace(/(^|[^\\])ss+/g, '$1' + ii(s)); - format = format.replace(/(^|[^\\])s/g, '$1' + s); - - var f = utc ? date.getUTCMilliseconds() : date.getMilliseconds(); - format = format.replace(/(^|[^\\])fff+/g, '$1' + ii(f, 3)); - f = Math.round(f / 10); - format = format.replace(/(^|[^\\])ff/g, '$1' + ii(f)); - f = Math.round(f / 10); - format = format.replace(/(^|[^\\])f/g, '$1' + f); - - var T = H < 12 ? 'AM' : 'PM'; - format = format.replace(/(^|[^\\])TT+/g, '$1' + T); - format = format.replace(/(^|[^\\])T/g, '$1' + T.charAt(0)); - - var t = T.toLowerCase(); - format = format.replace(/(^|[^\\])tt+/g, '$1' + t); - format = format.replace(/(^|[^\\])t/g, '$1' + t.charAt(0)); - - var tz = -date.getTimezoneOffset(); - var K = utc || !tz ? 'Z' : tz > 0 ? '+' : '-'; - if (!utc) - { - tz = Math.abs(tz); - var tzHrs = Math.floor(tz / 60); - var tzMin = tz % 60; - K += ii(tzHrs) + ':' + ii(tzMin); - } - format = format.replace(/(^|[^\\])K/g, '$1' + K); - - var day = (utc ? date.getUTCDay() : date.getDay()) + 1; - format = format.replace(new RegExp(dddd[0], 'g'), dddd[day]); - format = format.replace(new RegExp(ddd[0], 'g'), ddd[day]); - - format = format.replace(new RegExp(MMMM[0], 'g'), MMMM[M]); - format = format.replace(new RegExp(MMM[0], 'g'), MMM[M]); - - format = format.replace(/\\(.)/g, '$1'); - - return format; -}; +validators.escapeHTML = function (str) { + return str.replace(//, '>') +} const processors = { - inline_color: function (text) { - // TODO: Figure out how to make hex colors behave - //const hexRegex = /(^|[^&])(\#[0-9a-f]{6};?)(?!\w)/gmi; - const rgbRegex = /(.?)(rgba?\((?:\s*\d+\s*,){2}\s*\d+\s*(?:,\s*[\d.]+\s*)?\);?)/gmi; - const substitute = '$1$2
'; - //text = text.replace(hexRegex, substitute); - text = text.replace(rgbRegex, substitute); - return text; - }, - linkify(text) { - const re = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:''.,<>?«»“”‘’]))/gi; - return text.replace(re, function(url) { - let href = url; - if (url.indexOf('http') !== 0) { - href = 'http://' + url; - } - return '' + url + ''; - }); - }, - channelify(text) { - if(!irc.config.channelify) return text; + inline_color: function (text) { + // TODO: Figure out how to make hex colors behave + // const hexRegex = /(^|[^&])(\#[0-9a-f]{6};?)(?!\w)/gmi + const rgbRegex = /(.?)(rgba?\((?:\s*\d+\s*,){2}\s*\d+\s*(?:,\s*[\d.]+\s*)?\);?)/gmi + const substitute = '$1$2
' + // text = text.replace(hexRegex, substitute) + text = text.replace(rgbRegex, substitute) + return text + }, + linkify (text) { + const re = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:''.,<>?«»“”‘’]))/gi + return text.replace(re, function (url) { + let href = url + if (url.indexOf('http') !== 0) { + href = 'http://' + url + } + return '' + url + '' + }) + }, + channelify (text) { + if (!irc.config.channelify) return text - const channelRegex = /(^|[\s,.:;?!"'()+@-\~%])(#+[^\x00\x07\r\n\s,:]*[a-z][^\x00\x07\r\n\s,:]*)/gmi; - const substitute = '$1$2'; + const channelRegex = /(^|[\s,.:;?!"'()+@-\~%])(#+[^\x00\x07\r\n\s,:]*[a-z][^\x00\x07\r\n\s,:]*)/gmi + const substitute = '$1$2' - return text.replace(channelRegex, substitute); - }, - emojify(text) { - if (irc.config.emoji === true && window.emojione !== undefined) { - // Emoji live in the D800-DFFF surrogate plane; only bother passing - // this range to CPU-expensive unicodeToImage(); - const emojiRegex = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g; - if (emojiRegex.test(text)) { - return emojione.unicodeToImage(text); - } else { - return text; - } - } else { - return text; + return text.replace(channelRegex, substitute) + }, + emojify (text) { + if (irc.config.emoji === true && window.twemoji !== undefined) { + // Emoji live in the D800-DFFF surrogate plane; only bother passing + // this range to CPU-expensive parse() + const emojiRegex = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g + if (emojiRegex.test(text)) { + return twemoji.parse(text) + } else { + return text + } + } else { + return text + } + } +} + +function whoisMessage (whoisData, buffer) { + let messages = [] + for (let key in whoisData) { + switch (key) { + case 'hostmask': + messages.push(span(whoisData[key], ['hostmask']) + ': ' + validators.escapeHTML(whoisData['realname'])) + break + case 'idleSeconds': + let msgs = 'is idle for ' + whoisData[key] + ' seconds' + if (whoisData['signonTime']) { + msgs += ', signed on at ' + (new Date(parseInt(whoisData['signonTime']) * 1000)) } - } -}; + messages.push(msgs) + break + case 'loggedIn': + case 'registered': + case 'connectingFrom': + case 'usingModes': + case 'title': + messages.push(validators.escapeHTML(whoisData[key])) + break + case 'channels': + messages.push(whoisData[key].join(' ')) + break + case 'server': + let adfd = sf('is on %s', span(whoisData[key], ['server', 'nick'])) + if (whoisData['server_name']) { + adfd += ' ' + adfd += span(validators.escapeHTML(whoisData['server_name']), ['hostmask']) + } + messages.push(adfd) + break + case 'secure': + messages.push('is using a secure connection.') + break + case 'bot': + messages.push('is a bot on ' + irc.serverData[buffer.server].network) + break + } + } -function whoisMessage(whoisData, buffer) { - let messages = []; - for(let key in whoisData) { - switch(key) { - case 'hostmask': - messages.push(''+whoisData[key]+': '+validators.escapeHTML(whoisData['realname'])); - break; - case 'idleSeconds': - let msgs = 'is idle for '+whoisData[key]+' seconds'; - if(whoisData['signonTime']) - msgs += ', signed on at '+new Date(parseInt(whoisData['signonTime'])*1000); - messages.push(msgs); - break; - case 'loggedIn': - case 'registered': - case 'connectingFrom': - case 'usingModes': - case 'title': - messages.push(validators.escapeHTML(whoisData[key])); - break; - case 'channels': - messages.push(whoisData[key].join(' ')); - break; - case 'server': - let adfd = 'is on '+whoisData[key]+''; - if(whoisData['server_name']) - adfd += ' '+validators.escapeHTML(whoisData['server_name'])+''; - messages.push(adfd); - break; - case 'secure': - messages.push('is using a secure connection.'); - break; - case 'bot': - messages.push('is a bot on '+irc.serverData[buffer.server].network); - break; - } - } - - for(let i in messages) { - let mesg = '['+whoisData.nickname+'] '+messages[i]; - buffer.addMessage(mesg, null, 'whois'); - } + for (let i in messages) { + let mesg = sf('[%s] %s', span(whoisData.nickname, ['nick']), messages[i]) + buffer.addMessage(mesg, null, 'whois') + } } -function rand(min, max) { - return parseInt(Math.random() * (max-min+1), 10) + min; -} - -if (!String.prototype.format) { - String.prototype.format = function() { - var args = arguments; - return this.replace(/{(\d+)}/g, function(match, number) { - return typeof args[number] != undefined ? args[number] : match; - }); - }; -} - -function remove_str(arr, str) { - let index = arr.indexOf(str); - - if(index > -1) { - arr.splice(index, 1); - return arr; - } - return arr; -}; - -function grep(items, callback) { - let filtered = []; - for (let i in items) { - let item = items[i]; - let cond = callback(item); - if (cond) { - filtered.push(item); - } - } - - return filtered; -}; - -function match(word, array) { - return grep( - array, - function(w) { - return w.toLowerCase().indexOf(word.toLowerCase()) == 0; - } - ); -} - -function serialize(obj) { - let str = []; - for(let p in obj) - if (obj.hasOwnProperty(p)) { - str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p])); - } - return str.join('&'); -} - -function removeClass(element, cl) { - let classList = element.className.split(' '); - - if(classList.indexOf(cl) != -1) - classList.splice(classList.indexOf(cl), 1); - - element.className = classList.join(' '); -} - -function addClass(element, cl) { - let classList = element.className.split(' '); - - if(classList.indexOf(cl) != -1) - return; - - classList.push(cl); - element.className = classList.join(' '); -} - -function toggleClass(element, cl) { - let classList = element.className.split(' '); - if(classList.indexOf(cl) != -1) - removeClass(element, cl); - else - addClass(element, cl); -} - -function objectGetKey(obj, value) { - let key = null; - for(let f in obj) { - if(obj[f] == value) - key = f; - } - return key; -} +const asterisk = span('ℹ', ['asterisk']) +const arrowout = span('⬅', ['arrowout']) +const arrowin = span('➡', ['arrowin']) let composer = { - message: { - simple: function(time, sender, message, type, server) { - let element = document.createElement('div'); - element.className = 'message type_simple m_'+type; + message: { + simple: function (time, sender, message, type, server) { + let element = document.createElement('div') + element.className = 'message type_simple m_' + type - if(irc.config.timestamps) - element.innerHTML += ''+time.format(irc.config.timestampFormat)+' '; + if (irc.config.timestamps) { + element.innerHTML += span(formatDate(time, irc.config.timestampFormat), ['timestamp']) + element.innerHTML += ' ' + } - if(irc.config.colors) - message = colorizer.stylize(message); - else - message = colorizer.strip(message); + if (irc.config.colors) { + message = colorizer.stylize(message) + } else { + message = colorizer.strip(message) + } - message = processors.inline_color(message); - message = processors.channelify(message); - message = processors.linkify(message); - message = processors.emojify(message); + message = processors.inline_color(message) + message = processors.channelify(message) + message = processors.linkify(message) + message = processors.emojify(message) - switch(type) { - case 'mode': - element.innerHTML += ' '+sender+' '; - element.innerHTML += ''+message+''; - break; - case 'action': - element.innerHTML += ' '+sender+' '; - element.innerHTML += ''+message+''; - break; - case 'part': - case 'quit': - case 'kick': - element.innerHTML += ' '+sender+''; - element.innerHTML += ' '+message+''; - break; - case 'join': - element.innerHTML += ' '+sender+''; - element.innerHTML += ' '+message+''; - break; - case 'ctcp_response': - element.innerHTML += ' CTCP response from '+sender+' '; - element.innerHTML += ''+message+''; - break; - case 'ctcp_request': - element.innerHTML += ' CTCP request to '+sender+' '; - element.innerHTML += ''+message+''; - break; - default: - if(sender) { - element.innerHTML += ''+sender+' '+message+''; - } else { - element.innerHTML += ''+message+''; - addClass(element, 'no_sender'); - } - break; - } + switch (type) { + case 'mode': + element.innerHTML += ' ' + span(sender, ['actionee', 'nick']) + ' ' + element.innerHTML += span(message, ['content']) + break + case 'action': + element.innerHTML += asterisk + ' ' + span(sender, ['actionee', 'nick']) + ' ' + element.innerHTML += span(message, ['content']) + break + case 'part': + case 'quit': + case 'kick': + element.innerHTML += arrowout + ' ' + span(span(sender, ['actionee', 'nick']) + ' ' + message, ['content']) + break + case 'join': + element.innerHTML += arrowin + ' ' + span(span(sender, ['actionee', 'nick']) + ' ' + message, ['content']) + break + case 'ctcp_response': + element.innerHTML += asterisk + ' CTCP response from ' + span(sender, ['actionee', 'nick']) + ' ' + element.innerHTML += span(message, ['content']) + break + case 'ctcp_request': + element.innerHTML += asterisk + ' CTCP request to ' + span(sender, ['actionee', 'nick']) + ' ' + element.innerHTML += span(message, ['content']) + break + default: + if (sender) { + element.innerHTML += span(sender, ['sender']) + ' ' + span(message, ['content']) + } else { + element.innerHTML += span(message, ['content', 'no_sender']) + } + break + } - if(irc.config.colorNicknames == true) { - if(sender) { - let sndr1 = element.querySelector('.sender'); - if(sndr1) - sndr1.style.color = colorizer.get_random_color(sndr1.innerHTML); - } + if (irc.config.colorNicknames === true) { + if (sender) { + let sndr1 = element.querySelector('.sender') + if (sndr1) { + sndr1.style.color = colorizer.get_random_color(sndr1.innerHTML) + } + } - let sndr2 = element.querySelectorAll('.nick'); - if(sndr2.length > 0) - for(let a in sndr2) - if(sndr2[a] && sndr2[a]['style']) - sndr2[a].style.color = colorizer.get_random_color(sndr2[a].innerHTML); - } - return element; - } - }, - theme_selection: function(name, theme) { - let btn = document.createElement('div'); - btn.className = 'theme_button theme_'+theme.type; - let sampler = document.createElement('div'); - sampler.className = 'sampler'; - sampler.style['background-color'] = theme.colorSamples.background; - let toolbar = document.createElement('span'); - toolbar.className = 's_toolbar'; - toolbar.style['background-color'] = theme.colorSamples.toolbar; - let nameb = document.createElement('span'); - nameb.className = 'name'; - nameb.innerHTML = theme['name']; - sampler.appendChild(toolbar); - btn.appendChild(sampler); - btn.appendChild(nameb); - btn.setAttribute('id', 'theme-'+name); + let sndr2 = element.querySelectorAll('.nick') + if (sndr2.length > 0) { + for (let a in sndr2) { + if (sndr2[a] && sndr2[a]['style']) { + sndr2[a].style.color = colorizer.get_random_color(sndr2[a].innerHTML) + } + } + } + } + return element + } + }, + themeSelection: function (name, theme) { + let btn = document.createElement('div') + btn.className = 'theme_button theme_' + theme.type + let sampler = document.createElement('div') + sampler.className = 'sampler' + sampler.style['background-color'] = theme.colorSamples.background + let toolbar = document.createElement('span') + toolbar.className = 's_toolbar' + toolbar.style['background-color'] = theme.colorSamples.toolbar + let nameb = document.createElement('span') + nameb.className = 'name' + nameb.innerHTML = theme['name'] + sampler.appendChild(toolbar) + btn.appendChild(sampler) + btn.appendChild(nameb) + btn.setAttribute('id', 'theme-' + name) - return btn; - }, - embedURL: function(server, port, defaultNick, useSSL, hideServerData, channels) { - let builder = window.location.origin + '/'; - let params = {}; - let final_channels = []; + return btn + }, + embedURL: function (server, port, defaultNick, useSSL, hideServerData, channels) { + let builder = window.location.origin + '/' + let params = {} + let finalChannels = [] - if(server) - builder += server + '/'; + if (server) builder += server + '/' + if (channels) channels = channels.trim() + if (defaultNick) params.nickname = defaultNick + if (port && port !== 6667) params.port = port + if (useSSL) params.secure = 1 + if (hideServerData) params.server_data = 0 + if (Object.keys(params).length > 0) builder += '?' + serialize(params) - if(channels) - channels = channels.trim(); + if (channels) { + if (channels.indexOf(',')) { + let tmp = channels.split(',') + for (let i in tmp) { + tmp[i] = tmp[i].trim() - if(defaultNick) - params.nickname = defaultNick; + if (tmp[i].indexOf('#') !== 0) { + tmp[i] = '#' + tmp[i] + } - if(port && port != 6667) - params.port = port; + finalChannels.push(tmp[i]) + } + } else { + if (channels.indexOf('#') !== 0) { + channels = '#' + channels + } + finalChannels.push(channels) + } - if(useSSL) - params.secure = 1; + builder += finalChannels.join(',') + } - if(hideServerData) - params.server_data = 0; - - if(Object.keys(params).length > 0) - builder += '?' + serialize(params); - - if(channels) { - if(channels.indexOf(',')) { - let tmp = channels.split(','); - for(let i in tmp) { - tmp[i] = tmp[i].trim(); - - if(tmp[i].indexOf('#') != 0) - tmp[i] = '#'+tmp[i]; - - final_channels.push(tmp[i]); - } - } else { - if(channels.indexOf('#') != 0) - channels = '#'+channels; - final_channels.push(channels); - } - - builder += final_channels.join(','); - } - - return builder; - } -}; + return builder + } +} // onclick food window.irc.joinChannel = (channel) => { - let buf = irc.chat.getActiveChatBuffer(); - if(!buf || !buf.server) return; + let buf = irc.chat.getActiveChatBuffer() + if (!buf || !buf.server) return - irc.socket.emit('userinput', {command: 'join', server: buf.server, message: '', arguments: [channel]}); - return false; -}; + irc.socket.emit('userinput', { command: 'join', server: buf.server, message: '', arguments: [channel] }) + return false +} -/*****************************\ -|** **| -|** CLIENT COMMANDS **| -|** **| -\*****************************/ +// Client commands -// commandName: {execute: function(buffer, handler, command, message, listargs) {}, description: ''} +// commandName: { execute: function (buffer, handler, command, message, listargs) {}, description: ''} let commands = { - embed: {execute: function(buffer, handler, command, message, listargs) { - let data = irc.auther.getDataFromForm(); - let url = 'Couldn\'t compose URL!'; + embed: { + execute: function (buffer, handler, command, message, listargs) { + let data = irc.auther.getDataFromForm() + let url = 'Couldn\'t compose URL!' - if(data) - url = composer.embedURL(data.server, data.port, data.nickname, data.secure, true, data.autojoin.join(',')); + if (data) { + url = composer.embedURL(data.server, data.port, data.nickname, data.secure, true, data.autojoin.join(',')) + } - buffer.addMessage(url, null, 'help'); - }, description: '- Embed URL for the current connection'}, + buffer.addMessage(url, null, 'help') + }, + description: '- Embed URL for the current connection' + }, - join: {execute: function(buffer, handler, command, message, listargs) { - if (!listargs[1]) { - if(!buffer.alive) { - irc.socket.emit('userinput', {command: 'join', server: buffer.server, message: '', arguments: [buffer.name]}); - } else { - handler.commandError(buffer, listargs[0].toUpperCase()+': Missing parameters!'); - } - } else { - irc.socket.emit('userinput', {command: 'join', server: buffer.server, message: '', arguments: [listargs[1]]}); - } - }, description: ' - Join a channel'}, + join: { + execute: function (buffer, handler, command, message, listargs) { + if (!listargs[1]) { + if (!buffer.alive) { + irc.socket.emit('userinput', { command: 'join', server: buffer.server, message: '', arguments: [buffer.name] }) + } else { + handler.commandError(buffer, listargs[0].toUpperCase() + ': Missing parameters!') + } + } else { + irc.socket.emit('userinput', { command: 'join', server: buffer.server, message: '', arguments: [listargs[1]] }) + } + }, + description: ' - Join a channel' + }, - part: {execute: function(buffer, handler, command, message, listargs) { - if (!listargs[1] && buffer.type == 'channel') { - irc.socket.emit('userinput', {command: 'part', server: buffer.server, message: '', arguments: [buffer.name]}); - } else if(buffer.type != 'channel') { - handler.commandError(buffer, listargs[0].toUpperCase()+': Buffer is not a channel.'); - } else if(listargs[1]) { - if(listargs[1].indexOf('#') != -1) { - let msg = ''; - if(listargs[2]) - msg = listargs.slice(2).join(' '); - irc.socket.emit('userinput', {command: 'part', server: buffer.server, message: msg, arguments: [listargs[1]]}); - } else { - if(buffer.type == 'channel') { - irc.socket.emit('userinput', {command: 'part', server: buffer.server, message: listargs.slice(1).join(' '), arguments: [buffer.name]}); - } else { - handler.commandError(buffer, listargs[0].toUpperCase()+': Buffer is not a channel.'); - } - } - } - }, description: '[<#channel>|] [message] - Leave the channel. If no channel specified, leave the current buffer.', aliases: ['leave']}, + part: { + execute: function (buffer, handler, command, message, listargs) { + if (!listargs[1] && buffer.type === 'channel') { + irc.socket.emit('userinput', { command: 'part', server: buffer.server, message: '', arguments: [buffer.name] }) + } else if (buffer.type !== 'channel') { + handler.commandError(buffer, listargs[0].toUpperCase() + ': Buffer is not a channel.') + } else if (listargs[1]) { + if (listargs[1].indexOf('#') !== -1) { + let msg = '' + if (listargs[2]) { + msg = listargs.slice(2).join(' ') + } + irc.socket.emit('userinput', { command: 'part', server: buffer.server, message: msg, arguments: [listargs[1]] }) + } else { + if (buffer.type === 'channel') { + irc.socket.emit('userinput', { command: 'part', server: buffer.server, message: listargs.slice(1).join(' '), arguments: [buffer.name] }) + } else { + handler.commandError(buffer, listargs[0].toUpperCase() + ': Buffer is not a channel.') + } + } + } + }, + description: '[<#channel>|] [message] - Leave the channel. If no channel specified, leave the current buffer.', + aliases: ['leave'] + }, - topic: {execute: function(buffer, handler, command, message, listargs) { - if (!listargs[1] && buffer.type == 'channel') { - irc.socket.emit('userinput', {command: 'topic', server: buffer.server, message: '', arguments: [buffer.name]}); - } else if(buffer.type != 'channel') { - handler.commandError(buffer, listargs[0].toUpperCase()+': Buffer is not a channel.'); - } else if(listargs[1]) { - if(listargs[1].indexOf('#') != -1) { - let msg = ''; - if(listargs[2]) - msg = listargs.slice(2).join(' '); - irc.socket.emit('userinput', {command: 'topic', server: buffer.server, message: msg, arguments: [listargs[1]]}); - } else { - if(buffer.type == 'channel') { - irc.socket.emit('userinput', {command: 'topic', server: buffer.server, message: listargs.slice(1).join(' '), arguments: [buffer.name]}); - } else { - handler.commandError(buffer, listargs[0].toUpperCase()+': Buffer is not a channel.'); - } - } - } - }, description: '[<#channel>|] [topic] - Sets/prints the current topic of the channel.', aliases: ['t']}, + topic: { + execute: function (buffer, handler, command, message, listargs) { + if (!listargs[1] && buffer.type === 'channel') { + irc.socket.emit('userinput', { command: 'topic', server: buffer.server, message: '', arguments: [buffer.name] }) + } else if (buffer.type !== 'channel') { + handler.commandError(buffer, listargs[0].toUpperCase() + ': Buffer is not a channel.') + } else if (listargs[1]) { + if (listargs[1].indexOf('#') !== -1) { + let msg = '' + if (listargs[2]) { + msg = listargs.slice(2).join(' ') + } + irc.socket.emit('userinput', { command: 'topic', server: buffer.server, message: msg, arguments: [listargs[1]] }) + } else { + if (buffer.type === 'channel') { + irc.socket.emit('userinput', { command: 'topic', server: buffer.server, message: listargs.slice(1).join(' '), arguments: [buffer.name] }) + } else { + handler.commandError(buffer, listargs[0].toUpperCase() + ': Buffer is not a channel.') + } + } + } + }, + description: '[<#channel>|] [topic] - Sets/prints the current topic of the channel.', + aliases: ['t'] + }, - kick: {execute: function(buffer, handler, command, message, listargs) { - if(buffer.type != 'channel') - return handler.commandError(buffer, listargs[0].toUpperCase()+': Buffer is not a channel!'); - if(!listargs[1]) - return handler.commandError(buffer, listargs[0].toUpperCase()+': Missing parameter !'); - irc.socket.emit('userinput', {command: 'kick', server: buffer.server, message: listargs.slice(2).join(' '), arguments: [buffer.name, listargs[1]]}); - }, description: ' - Kick the following user from the channel.'}, + kick: { + execute: function (buffer, handler, command, message, listargs) { + if (buffer.type !== 'channel') { + return handler.commandError(buffer, listargs[0].toUpperCase() + ': Buffer is not a channel!') + } - quit: {execute: function(buffer, handler, command, message, listargs) { - irc.socket.emit('userinput', {command: 'quit', server: buffer.server, message: listargs.slice(1).join(' '), arguments: []}); - }, description: '[] - Quit the current server with message.', aliases: ['exit']}, + if (!listargs[1]) { + return handler.commandError(buffer, listargs[0].toUpperCase() + ': Missing parameter !') + } - mode: {execute: function(buffer, handler, command, message, listargs) { - irc.socket.emit('userinput', {command: 'mode', server: buffer.server, message: listargs.slice(1).join(' '), arguments: []}); - }, description: '[target] [mode] - Set/remove mode of target.'}, + irc.socket.emit('userinput', { + command: 'kick', + server: buffer.server, + message: listargs.slice(2).join(' '), + arguments: [buffer.name, listargs[1]] + }) + }, + description: ' - Kick the following user from the channel.' + }, - msg: {execute: function(buffer, handler, command, message, listargs) { - if(!listargs[1] || !listargs[2]) - return handler.commandError(buffer, listargs[0].toUpperCase()+': Missing parameters!'); - if(listargs[1] == '*') - listargs[1] = buffer.name; - irc.socket.emit('userinput', {command: 'privmsg', server: buffer.server, message: listargs.slice(2).join(' '), arguments: [listargs[1]]}); - }, description: ' - Sends a message to target.', aliases: ['privmsg', 'q', 'query', 'say']}, + quit: { + execute: function (buffer, handler, command, message, listargs) { + irc.socket.emit('userinput', { command: 'quit', server: buffer.server, message: listargs.slice(1).join(' '), arguments: [] }) + }, + description: '[] - Quit the current server with message.', + aliases: ['exit'] + }, - ctcp: {execute: function(buffer, handler, command, message, listargs) { - if(!listargs[1] || !listargs[2]) - return handler.commandError(buffer, listargs[0].toUpperCase()+': Missing parameters!'); - if(listargs[1] == '*') - listargs[1] = buffer.name; - - listargs[2] = listargs[2].toUpperCase(); + mode: { + execute: function (buffer, handler, command, message, listargs) { + irc.socket.emit('userinput', { command: 'mode', server: buffer.server, message: listargs.slice(1).join(' '), arguments: [] }) + }, + description: '[target] [mode] - Set/remove mode of target.' + }, - irc.socket.emit('userinput', {command: 'ctcp', server: buffer.server, message: listargs.slice(2).join(' '), arguments: listargs.slice(1)}); - }, description: ' [arguments] - Sends a CTCP request to target.'}, + msg: { + execute: function (buffer, handler, command, message, listargs) { + if (!listargs[1] || !listargs[2]) { + return handler.commandError(buffer, listargs[0].toUpperCase() + ': Missing parameters!') + } - notice: {execute: function(buffer, handler, command, message, listargs) { - if(!listargs[1] || !listargs[2]) - return handler.commandError(buffer, listargs[0].toUpperCase()+': Missing parameters!'); - if(listargs[1] == '*') - listargs[1] = buffer.name; - irc.socket.emit('userinput', {command: 'notice', server: buffer.server, message: listargs.slice(2).join(' '), arguments: [listargs[1]]}); - }, description: ' - Sends a NOTICE to target.'}, + if (listargs[1] === '*') { + listargs[1] = buffer.name + } - action: {execute: function(buffer, handler, command, message, listargs) { - irc.socket.emit('userinput', {command: 'privmsg', server: buffer.server, message: '\x01ACTION '+message.substring(command.length+2)+'\x01', arguments: [buffer.name]}); - }, description: ' - act as yourself', aliases: ['me']}, + irc.socket.emit('userinput', { command: 'privmsg', server: buffer.server, message: listargs.slice(2).join(' '), arguments: [listargs[1]] }) + }, + description: ' - Sends a message to target.', + aliases: ['privmsg', 'q', 'query', 'say'] + }, - list: {execute: function(buffer, handler, command, message, listargs) { - irc.socket.emit('userinput', {command: 'list', server: buffer.server, message: '', arguments: listargs.splice(1)}); - }, description: '- List all channels on the current server.'}, + ctcp: { + execute: function (buffer, handler, command, message, listargs) { + if (!listargs[1] || !listargs[2]) { + return handler.commandError(buffer, listargs[0].toUpperCase() + ': Missing parameters!') + } - nick: {execute: function(buffer, handler, command, message, listargs) { - if(!listargs[1]) { - if(buffer.server != '') { - let mynick = irc.serverData[buffer.server].my_nick; - buffer.addMessage('Your nickname is: '+mynick+'', null, 'help'); - } - return; - } - irc.socket.emit('userinput', {command: 'nick', server: buffer.server, message: '', arguments: listargs.splice(1)}); - }, description: '- List all channels on the current server.', aliases: ['nickname']}, + if (listargs[1] === '*') { + listargs[1] = buffer.name + } - names: {execute: function(buffer, handler, command, message, listargs) { - let channel = ''; - if(!listargs[1]) { - if(buffer.type == 'channel') - channel = buffer.name; - else - return handler.commandError(buffer, '/'+cmd.toUpperCase()+': Buffer is not a channel!'); - } else if(listargs[1].indexOf('#') == 0) { - channel = listargs[1]; - } else { - return handler.commandError(buffer, '/'+cmd.toUpperCase()+': Invalid channel name!'); - } - irc.socket.emit('userinput', {command: 'names', server: buffer.server, message: '', arguments: [channel]}); - }, description: '- List all users on the current channel.', aliases: ['nicknames']}, + listargs[2] = listargs[2].toUpperCase() - quote: {execute: function(buffer, handler, command, message, listargs) { - irc.socket.emit('userinput', {command: listargs[1], server: buffer.server, message: listargs.slice(2).join(' '), arguments: listargs.splice(2)}); - }, description: ' [args] - Send a raw command to the server.', aliases: ['raw']}, + irc.socket.emit('userinput', { command: 'ctcp', server: buffer.server, message: listargs.slice(2).join(' '), arguments: listargs.slice(1) }) + }, + description: ' [arguments] - Sends a CTCP request to target.' + }, - whois: {execute: function(buffer, handler, command, message, listargs) { - if(!listargs[1]) - return handler.commandError(buffer, listargs[0].toUpperCase()+': Missing parameters!'); + notice: { + execute: function (buffer, handler, command, message, listargs) { + if (!listargs[1] || !listargs[2]) { + return handler.commandError(buffer, listargs[0].toUpperCase() + ': Missing parameters!') + } - irc.socket.emit('userinput', {command: 'whois', server: buffer.server, message: '', arguments: [listargs[1]]}); - }, description: ' - Display information about a user.'}, + if (listargs[1] === '*') { + listargs[1] = buffer.name + } - connect: {execute: function(buffer, handler, command, message, listargs) { - clientdom.connector.frame.style.display = 'block'; - irc.auther.authMessage('Create a new connection', false); - irc.auther.canClose = true; - }, description: '- Create a new connection.'}, + irc.socket.emit('userinput', { + command: 'notice', + server: buffer.server, + message: listargs.slice(2).join(' '), + arguments: [listargs[1]] + }) + }, + description: ' - Sends a NOTICE to target.' + }, - help: {execute: function(buffer, handler, command, message, listargs) { - if(!listargs[1]) - return handler.commandError(buffer, listargs[0].toUpperCase()+': Missing parameters!'); + action: { + execute: function (buffer, handler, command, message, listargs) { + irc.socket.emit('userinput', { + command: 'privmsg', + server: buffer.server, + message: '\x01ACTION ' + message.substring(command.length + 2) + '\x01', + arguments: [buffer.name] + }) + }, + description: ' - act as yourself', + aliases: ['me'] + }, - let cmd = listargs[1].toLowerCase(); - if(cmd.indexOf('/') === 0) - cmd = cmd.substring(1); + list: { + execute: function (buffer, handler, command, message, listargs) { + irc.socket.emit('userinput', { command: 'list', server: buffer.server, message: '', arguments: listargs.splice(1) }) + }, + description: '- List all channels on the current server.' + }, - if(cmd in commands) { - if('description' in commands[cmd]) - buffer.addMessage('/'+cmd.toUpperCase()+' '+ - validators.escapeHTML(commands[cmd].description), null, 'help'); - else - buffer.addMessage('/'+cmd.toUpperCase()+' - No description provided', null, 'help'); - } else { - let foundAliased = null; - for(let cmd2 in commands) { - if(!commands[cmd2]['aliases']) continue; - if(commands[cmd2].aliases.indexOf(cmd) != -1) foundAliased = cmd2; - } - if(foundAliased) { - if('description' in commands[foundAliased]) - buffer.addMessage('/'+cmd.toUpperCase()+' '+ - validators.escapeHTML(commands[foundAliased].description), null, 'help'); - else - buffer.addMessage('/'+cmd.toUpperCase()+' - No description provided', null, 'help'); - } else { - handler.commandError(buffer, '/'+cmd.toUpperCase()+': Unknown command!'); - } - } - }, description: ' - Display help for command'}, + nick: { + execute: function (buffer, handler, command, message, listargs) { + if (!listargs[1]) { + if (buffer.server !== '' && irc.serverData[buffer.server] != null) { + let mynick = irc.serverData[buffer.server].my_nick + buffer.addMessage('Your nickname is: ' + mynick + '', null, 'help') + } + return + } + irc.socket.emit('userinput', { command: 'nick', server: buffer.server, message: '', arguments: listargs.splice(1) }) + }, + description: '- List all channels on the current server.', + aliases: ['nickname'] + }, - clear: {execute: function(buffer, handler, command, message, listargs) { - buffer.clearMessages(); - }, description: '- Clears the current buffer.'} -}; + names: { + execute: function (buffer, handler, command, message, listargs) { + let channel = '' + if (!listargs[1]) { + if (buffer.type === 'channel') { + channel = buffer.name + } else { + return handler.commandError(buffer, '/' + command.toUpperCase() + ': Buffer is not a channel!') + } + } else if (listargs[1].indexOf('#') === 0) { + channel = listargs[1] + } else { + return handler.commandError(buffer, '/' + command.toUpperCase() + ': Invalid channel name!') + } + irc.socket.emit('userinput', { command: 'names', server: buffer.server, message: '', arguments: [channel] }) + }, + description: '- List all users on the current channel.', + aliases: ['nicknames'] + }, -/*********************\ -|** **| -|** CLASSES **| -|** **| -\*********************/ + quote: { + execute: function (buffer, handler, command, message, listargs) { + irc.socket.emit('userinput', { + command: listargs[1], + server: buffer.server, + message: listargs.slice(2).join(' '), + arguments: listargs.splice(2) + }) + }, + description: ' [args] - Send a raw command to the server.', + aliases: ['raw'] + }, + + whois: { + execute: function (buffer, handler, command, message, listargs) { + if (!listargs[1]) { + return handler.commandError(buffer, listargs[0].toUpperCase() + ': Missing parameters!') + } + + irc.socket.emit('userinput', { command: 'whois', server: buffer.server, message: '', arguments: [listargs[1]] }) + }, + description: ' - Display information about a user.' + }, + + connect: { + execute: function (buffer, handler, command, message, listargs) { + clientdom.connector.frame.style.display = 'block' + irc.auther.authMessage('Create a new connection', false) + irc.auther.canClose = true + }, + description: '- Create a new connection.' + }, + + help: { + execute: function (buffer, handler, command, message, listargs) { + if (!listargs[1]) { + return handler.commandError(buffer, listargs[0].toUpperCase() + ': Missing parameters!') + } + + let cmd = listargs[1].toLowerCase() + if (cmd.indexOf('/') === 0) { + cmd = cmd.substring(1) + } + + if (cmd in commands) { + if ('description' in commands[cmd]) { + buffer.addMessage(span('/' + cmd.toUpperCase(), ['command']) + ' ' + + validators.escapeHTML(commands[cmd].description), null, 'help') + } else { + buffer.addMessage(span('/' + cmd.toUpperCase(), ['command']) + ' - No description provided', null, 'help') + } + } else { + let foundAliased = null + for (let cmd2 in commands) { + if (!commands[cmd2]['aliases']) continue + if (commands[cmd2].aliases.indexOf(cmd) !== -1) foundAliased = cmd2 + } + if (foundAliased) { + if ('description' in commands[foundAliased]) { + buffer.addMessage(span('/' + cmd.toUpperCase(), ['command']) + ' ' + + validators.escapeHTML(commands[foundAliased].description), null, 'help') + } else { + buffer.addMessage(span('/' + cmd.toUpperCase(), ['command']) + ' - No description provided', null, 'help') + } + } else { + handler.commandError(buffer, span('/' + cmd.toUpperCase(), ['command']) + ': Unknown command!') + } + } + }, + description: ' - Display help for command' + }, + + clear: { + execute: function (buffer, handler, command, message, listargs) { + buffer.clearMessages() + }, + description: '- Clears the current buffer.' + } +} + +// Classes class Nicklist { - constructor(buffer) { - this.buffer = buffer; - this.nicks = []; - this.simplifiedNicksList = []; - } + constructor (buffer) { + this.buffer = buffer + this.nicks = [] + this.simplifiedNicksList = [] + } - sort() { - let spfx = irc.serverData[this.buffer.server].supportedPrefixes; - this.nicks.sort(function (a,b) { - let rex = new RegExp('^['+spfx+']'); - let nicks = [a.prefix.replace(rex,'').toLowerCase(), b.prefix.replace(rex,'').toLowerCase()]; - let prefix = []; - if (rex.test(a.prefix)) prefix.push(spfx.indexOf(a.prefix[0])); - else prefix.push(spfx.length+1); - if (rex.test(b.prefix)) prefix.push(spfx.indexOf(b.prefix[0])); - else prefix.push(spfx.length+1); - if (prefix[0] < prefix[1]) return -1; - if (prefix[0] > prefix[1]) return 1; - if (nicks[0] > nicks[1]) return 1; - if (nicks[0] < nicks[1]) return -1; - return 0; - }); - return this.nicks; - } + sort () { + let spfx = irc.serverData[this.buffer.server].supportedPrefixes + this.nicks.sort(function (a, b) { + let rex = new RegExp('^[' + spfx + ']') + let nicks = [a.prefix.replace(rex, '').toLowerCase(), b.prefix.replace(rex, '').toLowerCase()] + let prefix = [] + if (rex.test(a.prefix)) prefix.push(spfx.indexOf(a.prefix[0])) + else prefix.push(spfx.length + 1) + if (rex.test(b.prefix)) prefix.push(spfx.indexOf(b.prefix[0])) + else prefix.push(spfx.length + 1) + if (prefix[0] < prefix[1]) return -1 + if (prefix[0] > prefix[1]) return 1 + if (nicks[0] > nicks[1]) return 1 + if (nicks[0] < nicks[1]) return -1 + return 0 + }) + return this.nicks + } - appendToList(nick) { - if(!this.buffer.active) return; + appendToList (nick) { + if (!this.buffer.active) return - let str = document.createElement('div'); - str.className = 'nick'; - str.setAttribute('id', 'nick-'+nick.nickname); - let construct = ''; + let str = document.createElement('div') + str.className = 'nick' + str.setAttribute('id', 'nick-' + nick.nickname) + let construct = '' - if(nick.prefix != '') - construct += ''+nick.prefix+''; - else - construct += ' '; + if (nick.prefix !== '') { + construct += span(nick.prefix, ['prefix']) + } else { + construct += span(' ', ['no-prefix']) + } - if(irc.config.colorNicklist) - construct += ''+nick.nickname+''; - else - construct += ''+nick.nickname+''; + if (irc.config.colorNicklist) { + construct += '' + nick.nickname + '' + } else { + construct += span(nick.nickname, ['nickname']) + } - str.innerHTML = construct; - clientdom.nicklist.appendChild(str); - } + str.innerHTML = construct + clientdom.nicklist.appendChild(str) + } - render() { - if(!this.buffer.active) return; - clientdom.nicklist.innerHTML = ''; - this.simplifiedNicksList = []; - this.sort(); + render () { + if (!this.buffer.active) return + if (!irc.serverData[this.buffer.server]) return - for(let n in this.nicks) { - let nick = this.nicks[n]; - this.simplifiedNicksList.push(nick.nickname); - this.appendToList(nick); - } + clientdom.nicklist.innerHTML = '' + this.simplifiedNicksList = [] + this.sort() - irc.chat.input_handler.searchNicknames = this.simplifiedNicksList; - } + for (let n in this.nicks) { + let nick = this.nicks[n] + this.simplifiedNicksList.push(nick.nickname) + this.appendToList(nick) + } - nickAdd(nickname) { - let newbie = { nickname: nickname, prefix: '', modes: [] }; - if(this.getNickIndex(nickname) != null) return; - this.nicks.push(newbie); - this.render(); - } + irc.chat.input_handler.searchNicknames = this.simplifiedNicksList + } - nickAddObject(obj) { - if(this.getNickIndex(obj.nickname) != null) return; - this.nicks.push(obj); - } + nickAdd (nickname) { + let newbie = { nickname: nickname, prefix: '', modes: [] } + if (this.getNickIndex(nickname) != null) return + this.nicks.push(newbie) + this.render() + } - nickRemove(nickname) { - let nickIndex = this.getNickIndex(nickname); + nickAddObject (obj) { + if (this.getNickIndex(obj.nickname) != null) return + this.nicks.push(obj) + } - if(nickIndex != null) - this.nicks.splice(nickIndex, 1); - else - return; + nickRemove (nickname) { + let nickIndex = this.getNickIndex(nickname) - if(!this.buffer.active) return; - let tt = clientdom.nicklist.querySelector('#nick-'+nickname); - if(tt) tt.remove(); - remove_str(this.simplifiedNicksList, nickname); - } + if (nickIndex != null) { + this.nicks.splice(nickIndex, 1) + } else { + return + } - nickChange(oldNickname, newNickname) { - let nickIndex = this.getNickIndex(oldNickname); + if (!this.buffer.active) return + let tt = clientdom.nicklist.querySelector('#nick-' + nickname) + if (tt) tt.remove() + removeStr(this.simplifiedNicksList, nickname) + } - if(nickIndex != null) - this.nicks[nickIndex].nickname = newNickname; - else - return; + nickChange (oldNickname, newNickname) { + let nickIndex = this.getNickIndex(oldNickname) - this.render(); - } + if (nickIndex != null) { + this.nicks[nickIndex].nickname = newNickname + } else { + return + } - getNickIndex(nickname) { - let result = null; - - for(let n in this.nicks) - if(this.nicks[n].nickname == nickname) result = n; + this.render() + } - return result; - } + getNickIndex (nickname) { + let result = null - modeAdded(nickname, newMode) { - let nickIndex = this.getNickIndex(nickname); - let nick = null; + for (let n in this.nicks) { + if (this.nicks[n].nickname === nickname) result = n + } - if(nickIndex != null) - nick = this.nicks[nickIndex]; - else - return; + return result + } - let modeTranslations = irc.serverData[this.buffer.server].modeTranslation; - let prefixes = irc.serverData[this.buffer.server].supportedPrefixes; + modeAdded (nickname, newMode) { + let nickIndex = this.getNickIndex(nickname) + let nick = null - nick.modes.push(newMode); + if (nickIndex != null) { + nick = this.nicks[nickIndex] + } else { + return + } - for(let mode in modeTranslations) { - let prefix = modeTranslations[mode]; - if(nick.modes.indexOf(mode) == -1) continue; - let a = nick.modes.indexOf(mode) - 1; - if(a >= 0) { - if(prefixes.indexOf(modeTranslations[nick.modes[a]]) < prefixes.indexOf(prefix)) { - nick.prefix = modeTranslations[nick.modes[a]]; - break; - } - } else { - nick.prefix = prefix; - break; - } - } + let modeTranslations = irc.serverData[this.buffer.server].modeTranslation + let prefixes = irc.serverData[this.buffer.server].supportedPrefixes - this.render(); - } + nick.modes.push(newMode) - modeRemoved(nickname, oldMode) { - let nickIndex = this.getNickIndex(nickname); - let nick = null; + for (let mode in modeTranslations) { + let prefix = modeTranslations[mode] + if (nick.modes.indexOf(mode) === -1) continue + let a = nick.modes.indexOf(mode) - 1 + if (a >= 0) { + if (prefixes.indexOf(modeTranslations[nick.modes[a]]) < prefixes.indexOf(prefix)) { + nick.prefix = modeTranslations[nick.modes[a]] + break + } + } else { + nick.prefix = prefix + break + } + } - if(nickIndex != null) - nick = this.nicks[nickIndex]; - else - return; + this.render() + } - let modeTranslations = irc.serverData[this.buffer.server].modeTranslation; - let prefixes = irc.serverData[this.buffer.server].supportedPrefixes; + modeRemoved (nickname, oldMode) { + let nickIndex = this.getNickIndex(nickname) + let nick = null - remove_str(nick.modes, oldMode); + if (nickIndex != null) { + nick = this.nicks[nickIndex] + } else { + return + } - let currentLowest = ''; + let modeTranslations = irc.serverData[this.buffer.server].modeTranslation + let prefixes = irc.serverData[this.buffer.server].supportedPrefixes - for(let n in nick.modes) { - let mode = nick.modes[n]; - let nextMode = nick.modes[n+1]; - if(!nextMode && mode) { - currentLowest = modeTranslations[mode]; - break; - } else if(nextMode) { - if(prefixes.indexOf(modeTranslations[nextMode]) > prefixes.indexOf(modeTranslations[mode])) - currentLowest = modeTranslations[nextMode]; - } else { - break; - } - } + removeStr(nick.modes, oldMode) - nick.prefix = currentLowest; + let currentLowest = '' - this.render(); - } + for (let n in nick.modes) { + let mode = nick.modes[n] + let nextMode = nick.modes[n + 1] + if (!nextMode && mode) { + currentLowest = modeTranslations[mode] + break + } else if (nextMode) { + if (prefixes.indexOf(modeTranslations[nextMode]) > prefixes.indexOf(modeTranslations[mode])) { + currentLowest = modeTranslations[nextMode] + } + } else { + break + } + } + + nick.prefix = currentLowest + + this.render() + } } class Tab { - constructor(buffer) { - this.buffer = buffer; - this.element = null; - this.closeRequested = false; - } + constructor (buffer) { + this.buffer = buffer + this.element = null + this.closeRequested = false + } - // Create a tab element - renderTab() { - let internals = ''+ this.buffer.title +''; - - let ttt = document.createElement('div'); - ttt.innerHTML = internals; - ttt.className = 'tab'; - ttt.setAttribute('id', 'tab-'+this.buffer.name); - clientdom.tabby.appendChild(ttt); - this.element = ttt; + // Create a tab element + renderTab () { + let internals = span(this.buffer.title, null, 'title') + span('', ['none'], 'unread') - ttt.innerHTML += 'x'; - ttt.querySelector('#close').addEventListener('click', () => { - this.close(); - }, false); + let ttt = document.createElement('div') + ttt.innerHTML = internals + ttt.className = 'tab' + ttt.setAttribute('id', 'tab-' + this.buffer.name) + clientdom.tabby.appendChild(ttt) + this.element = ttt - ttt.addEventListener('click', () => { - if(this.closeRequested) return; + ttt.innerHTML += span('x', null, 'close') + ttt.querySelector('#close').addEventListener('click', () => { + this.close() + }, false) - if(this.buffer.active) - return; + ttt.addEventListener('click', () => { + if (this.closeRequested) return + if (this.buffer.active) return - irc.chat.render(this.buffer); - }, false); - } + irc.chat.render(this.buffer) + }, false) + } - setActive(active) { - if(this.element) { - this.element.className = 'tab'; - if(active) { - addClass(this.element, 'active'); - } - } - } + setActive (active) { + if (this.element) { + this.element.className = 'tab' + if (active) { + addClass(this.element, 'active') + } + } + } - setHot(hot) { - if(this.element) { - if(hot) - addClass(this.element, 'hot'); - else - removeClass(this.element, 'hot'); - } - } + setHot (hot) { + if (this.element) { + if (hot) { + addClass(this.element, 'hot') + } else { + removeClass(this.element, 'hot') + } + } + } - setUnreadCount(count) { - if(this.element) { - let counter = this.element.querySelector('#unread'); - if(count == 0) { - counter.className = 'none'; - } else { - counter.innerHTML = count; - counter.className = ''; - } - } - } + setUnreadCount (count) { + if (this.element) { + let counter = this.element.querySelector('#unread') + if (count === 0) { + counter.className = 'none' + } else { + counter.innerHTML = count + counter.className = '' + } + } + } - setTitle(title) { - let titleEl = this.element.querySelector('#title'); - if(titleEl) - titleEl.innerHTML = title; - } + setTitle (title) { + let titleEl = this.element.querySelector('#title') + if (titleEl) { + titleEl.innerHTML = title + } + } - close() { - this.closeRequested = true; - this.buffer.closeChatBuffer(); - } + close () { + this.closeRequested = true + this.buffer.closeChatBuffer() + } } class ChatBuffer { - constructor(servername, buffername, tabname, type) { - this.messages = []; - this.nicklist = null; - this.topic = null; - this.input = ''; - this.lastscroll = 0; - this.wasAtBottom = false; - this.unreadCount = 0; + constructor (servername, buffername, tabname, type) { + this.messages = [] + this.nicklist = null + this.topic = null + this.input = '' + this.lastscroll = 0 + this.wasAtBottom = false + this.unreadCount = 0 - this.server = servername; - this.name = buffername; - this.title = tabname; - this.type = type; - this.active = false; - this.alive = true; - this.hot = false; + this.server = servername + this.name = buffername + this.title = tabname + this.type = type + this.active = false + this.alive = true + this.hot = false - if(type != 'applet') { - this.tab = new Tab(this); - this.tab.renderTab(); - } + if (type !== 'applet') { + this.tab = new Tab(this) + this.tab.renderTab() + } - if(type == 'channel') - this.nicklist = new Nicklist(this, clientdom.nicklist); - } + if (type === 'channel') { + this.nicklist = new Nicklist(this, clientdom.nicklist) + } + } - render() { - this.active = true; - this.tab.setActive(true); - this.unreadCount = 0; - this.tab.setUnreadCount(0); + render () { + this.active = true + this.tab.setActive(true) + this.unreadCount = 0 + this.tab.setUnreadCount(0) - clientdom.chat.className = 'chatarea'; - clientdom.nicklist.innerHTML = ''; - clientdom.topicbar.innerHTML = ''; + clientdom.chat.className = 'chatarea' + clientdom.nicklist.innerHTML = '' + clientdom.topicbar.innerHTML = '' - if(!irc.config.sharedInput) - clientdom.input.value = this.input; + if (!irc.config.sharedInput) { + clientdom.input.value = this.input + } - if(this.nicklist) { - addClass(clientdom.chat, 'vnicks'); - this.nicklist.render(); - } + if (this.nicklist) { + addClass(clientdom.chat, 'vnicks') + this.nicklist.render() + } - if(this.hot) - this.setHotStatus(false); + if (this.hot) { + this.setHotStatus(false) + } - if(this.topic != null && this.topic != '') { - addClass(clientdom.chat, 'vtopic'); - if(irc.config.colors) - clientdom.topicbar.innerHTML = processors.linkify(processors.channelify(colorizer.stylize(this.topic))); - else - clientdom.topicbar.innerHTML = processors.linkify(processors.channelify(colorizer.strip(this.topic))); - } + if (this.topic != null && this.topic !== '') { + addClass(clientdom.chat, 'vtopic') + if (irc.config.colors) { + clientdom.topicbar.innerHTML = processors.linkify(processors.channelify(colorizer.stylize(this.topic))) + } else { + clientdom.topicbar.innerHTML = processors.linkify(processors.channelify(colorizer.strip(this.topic))) + } + } - this.renderMessages(); - if(this.wasAtBottom) - clientdom.letterbox.scrollTop = clientdom.letterbox.scrollHeight; - else - clientdom.letterbox.scrollTop = this.lastscroll; + this.renderMessages() + if (this.wasAtBottom) { + clientdom.letterbox.scrollTop = clientdom.letterbox.scrollHeight + } else { + clientdom.letterbox.scrollTop = this.lastscroll + } - clientdom.currentNickname.innerHTML = irc.serverData[this.server].my_nick; + clientdom.currentNickname.innerHTML = irc.serverData[this.server] ? irc.serverData[this.server].my_nick + : el('i', 'Disconnected') - irc.chat.changeTitle('TeemantIRC - '+this.title); - } + irc.chat.changeTitle('TeemantIRC - ' + this.title) + } - renderMessages() { - if(!this.active) return; + renderMessages () { + if (!this.active) return - clientdom.letterbox.innerHTML = ''; + clientdom.letterbox.innerHTML = '' - for(let t in this.messages) { - let mesg = this.messages[t]; - this.appendMessage({message: mesg.message, sender: mesg.sender, type: mesg.type, time: mesg.time}); - } - } + for (let t in this.messages) { + let mesg = this.messages[t] + this.appendMessage({ message: mesg.message, sender: mesg.sender, type: mesg.type, time: mesg.time }) + } + } - clearMessages() { - this.messages = []; + clearMessages () { + this.messages = [] - if(this.active) - clientdom.letterbox.innerHTML = ''; - } + if (this.active) { + clientdom.letterbox.innerHTML = '' + } + } - setHotStatus(hot) { - this.hot = hot; - if(this.tab) - this.tab.setHot(hot); - } + setHotStatus (hot) { + this.hot = hot + if (this.tab) { + this.tab.setHot(hot) + } + } - appendMessage(meta) { - let mesgConstr = composer.message[irc.chatType](meta.time, meta.sender, meta.message, meta.type, this.server); + appendMessage (meta) { + let mesgConstr = composer.message[irc.chatType](meta.time, meta.sender, meta.message, meta.type, this.server) - if(irc.serverData[this.server]) { - let mynick = irc.serverData[this.server].my_nick; - if((meta.type == 'privmsg' || meta.type == 'notice' || meta.type == 'action') && - meta.message.toLowerCase().indexOf(mynick.toLowerCase()) != -1 && meta.sender != mynick) - addClass(mesgConstr, 'mentioned'); - } + if (irc.serverData[this.server]) { + let mynick = irc.serverData[this.server].my_nick + if ((meta.type === 'privmsg' || meta.type === 'notice' || meta.type === 'action') && + meta.message.toLowerCase().indexOf(mynick.toLowerCase()) !== -1 && meta.sender !== mynick) { + addClass(mesgConstr, 'mentioned') + } + } - clientdom.letterbox.appendChild(mesgConstr); + clientdom.letterbox.appendChild(mesgConstr) - let lFactor = clientdom.letterbox.offsetHeight + clientdom.letterbox.scrollTop; - if(lFactor > clientdom.letterbox.scrollHeight - 100) - clientdom.letterbox.scrollTop = clientdom.letterbox.scrollHeight; - } + let lFactor = clientdom.letterbox.offsetHeight + clientdom.letterbox.scrollTop + if (lFactor > clientdom.letterbox.scrollHeight - 100) { + clientdom.letterbox.scrollTop = clientdom.letterbox.scrollHeight + } + } - topicChange(topic) { - if(this.active) { - if(irc.config.colors) - clientdom.topicbar.innerHTML = processors.linkify(colorizer.stylize(topic)); - else - clientdom.topicbar.innerHTML = processors.linkify(colorizer.strip(topic)); + topicChange (topic) { + if (this.active) { + if (irc.config.colors) { + clientdom.topicbar.innerHTML = processors.linkify(colorizer.stylize(topic)) + } else { + clientdom.topicbar.innerHTML = processors.linkify(colorizer.strip(topic)) + } - if(this.topic == null) - addClass(clientdom.chat, 'vtopic'); - } - this.topic = topic; - } + if (this.topic == null) { + addClass(clientdom.chat, 'vtopic') + } + } + this.topic = topic + } - addMessage(message, sender, type, time) { - let mesg = {message: message, sender: sender, type: type, time: time || new Date()}; - this.messages.push(mesg); + addMessage (message, sender, type, time) { + let mesg = { message: message, sender: sender, type: type, time: time || new Date() } + this.messages.push(mesg) - if(this.active) { - this.appendMessage(mesg); - } else { - this.unreadCount += 1; - if(irc.serverData[this.server]) { - let mynick = irc.serverData[this.server].my_nick; - if((type == 'privmsg' || type == 'notice' || type == 'action') && - message.toLowerCase().indexOf(mynick.toLowerCase()) != -1 && sender != mynick) { - // TODO: notify user of mentioned - - if(!this.active) - this.setHotStatus(true); - } - } - } + if (this.active) { + this.appendMessage(mesg) + } else { + this.unreadCount += 1 + if (irc.serverData[this.server]) { + let mynick = irc.serverData[this.server].my_nick + if ((type === 'privmsg' || type === 'notice' || type === 'action') && + message.toLowerCase().indexOf(mynick.toLowerCase()) !== -1 && sender !== mynick) { + // TODO: notify user of mentioned - this.tab.setUnreadCount(this.unreadCount); - } + if (!this.active) { + this.setHotStatus(true) + } + } + } + } - switchOff() { - irc.chat.input_handler.searchNicknames = []; + this.tab.setUnreadCount(this.unreadCount) + } - let lFactor = clientdom.letterbox.offsetHeight + clientdom.letterbox.scrollTop; - if(lFactor > clientdom.letterbox.scrollHeight - 100) - this.wasAtBottom = true; - else - this.wasAtBottom = false; + switchOff () { + irc.chat.input_handler.searchNicknames = [] - if(!irc.config.sharedInput) - this.input = clientdom.input.value; + let lFactor = clientdom.letterbox.offsetHeight + clientdom.letterbox.scrollTop + if (lFactor > clientdom.letterbox.scrollHeight - 100) { + this.wasAtBottom = true + } else { + this.wasAtBottom = false + } - this.tab.setActive(false); - this.lastscroll = clientdom.letterbox.scrollTop; - this.active = false; - } + if (!irc.config.sharedInput) { + this.input = clientdom.input.value + } - setAliveStatus(status) { - this.alive = status; - if(this.alive) - this.tab.setTitle(this.title); - else - this.tab.setTitle('('+this.title+')'); - } + this.tab.setActive(false) + this.lastscroll = clientdom.letterbox.scrollTop + this.active = false + } - closeChatBuffer() { - irc.chat.closeChatBuffer(this); - } + setAliveStatus (status) { + this.alive = status + if (this.alive) { + this.tab.setTitle(this.title) + } else { + this.tab.setTitle(el('i', sf('(%s)', this.title))) + } + } + + closeChatBuffer () { + irc.chat.closeChatBuffer(this) + } } class ThemeSelector { - constructor(settings, variable) { - this.settings = settings; - this.variable = variable; - } + constructor (settings, variable) { + this.settings = settings + this.variable = variable + } - set_active_selection(name) { - let all = clientdom.settings.available_themes.querySelectorAll('.theme_button'); - if(all.length > 0) { - for(let a in all) { - if(all[a] && all[a]['className']) { - let elem = all[a]; - if(elem.getAttribute('id') == 'theme-'+name) { - addClass(elem, 'selected'); - } else { - removeClass(elem, 'selected'); - } - } - } - } - } + setActiveSelection (name) { + let all = clientdom.settings.available_themes.querySelectorAll('.theme_button') + if (all.length > 0) { + for (let a in all) { + if (all[a] && all[a]['className']) { + let elem = all[a] + if (elem.getAttribute('id') === 'theme-' + name) { + addClass(elem, 'selected') + } else { + removeClass(elem, 'selected') + } + } + } + } + } - bindButton(button, theme) { - button.onclick = (e) => { - this.settings.themeSelection = theme; - this.set_active_selection(theme); - }; - } + bindButton (button, theme) { + button.onclick = (e) => { + this.settings.themeSelection = theme + this.setActiveSelection(theme) + } + } - render() { - clientdom.settings.available_themes.innerHTML = ''; + render () { + clientdom.settings.available_themes.innerHTML = '' - for(let n in themes.available) { - let theme = themes.available[n]; - let button = composer.theme_selection(n, theme); + for (let n in themes.available) { + let theme = themes.available[n] + let button = composer.themeSelection(n, theme) - clientdom.settings.available_themes.appendChild(button); + clientdom.settings.available_themes.appendChild(button) - this.bindButton(button, n); - } - } + this.bindButton(button, n) + } + } } class AppletChatBuffer extends ChatBuffer { - constructor(appletName, title, frame) { - super('', appletName, title, 'applet'); - this.tab = null; - this.isOpen = false; - this.timeout = null; - this.frame = frame; - } + constructor (appletName, title, frame) { + super('', appletName, title, 'applet') + this.tab = null + this.isOpen = false + this.timeout = null + this.frame = frame + } - closeChatBuffer() { - irc.chat.closeChatBuffer(this); - this.tab = null; - this.isOpen = false; - } + closeChatBuffer () { + irc.chat.closeChatBuffer(this) + this.tab = null + this.isOpen = false + } - open() { - if(this.isOpen) { - irc.chat.render(this); - return; - } + open () { + if (this.isOpen) { + irc.chat.render(this) + return + } - this.tab = new Tab(this); - this.tab.renderTab(); - irc.chat.buffers.push(this); - irc.chat.render(this); - this.isOpen = true; - } + this.tab = new Tab(this) + this.tab.renderTab() + irc.chat.buffers.push(this) + irc.chat.render(this) + this.isOpen = true + } - addMessage(message, sender, type, time) { - // Don't send messages to any applet buffer - return; - } + addMessage (message, sender, type, time) { + // Don't send messages to any applet buffer + } - switchOff() { - this.active = false; - this.tab.setActive(false); - this.frame.style.display = 'none'; - } + switchOff () { + this.active = false + this.tab.setActive(false) + this.frame.style.display = 'none' + } - render() { - this.active = true; - this.tab.setActive(true); - this.frame.style.display = 'block'; - } + render () { + this.active = true + this.tab.setActive(true) + this.frame.style.display = 'block' + } } class Settings extends AppletChatBuffer { - constructor() { - super('settings', 'Settings', clientdom.settings.frame); + constructor () { + super('settings', 'Settings', clientdom.settings.frame) - this.themeSelection = ''; + this.themeSelection = '' - this.themeSelector = new ThemeSelector(this); - this.themeSelector.render(); + this.themeSelector = new ThemeSelector(this) + this.themeSelector.render() - clientdom.settings.save.onclick = (e) => { - this.saveSpecified(); - }; + clientdom.settings.save.onclick = (e) => { + this.saveSpecified() + } - clientdom.settings.open.onclick = (e) => { - this.open(); - }; - } + clientdom.settings.open.onclick = (e) => { + this.open() + } + } - switch_theme() { - if(this.themeSelection != '') { - themes.change_theme(this.themeSelection); - irc.config.theme = this.themeSelection; - this.themeSelector.set_active_selection(this.themeSelection); - } - } + switchTheme () { + if (this.themeSelection !== '') { + themes.change_theme(this.themeSelection) + irc.config.theme = this.themeSelection + this.themeSelector.setActiveSelection(this.themeSelection) + } + } - saveSpecified() { - if(this.timeout) - clearTimeout(this.timeout); + saveSpecified () { + if (this.timeout) { + clearTimeout(this.timeout) + } - this.switch_theme(); + this.switchTheme() - for(let key in irc.config) { - let value = irc.config[key]; - let type = typeof(value); + for (let key in irc.config) { + let value = irc.config[key] + let type = typeof value - if(clientdom.settings[key]) { - if(type == 'boolean') - irc.config[key] = clientdom.settings[key].checked; - else - irc.config[key] = clientdom.settings[key].value; - } - } - clientdom.settings.saveStatus.innerHTML = 'Settings saved!'; + if (clientdom.settings[key]) { + if (type === 'boolean') { + irc.config[key] = clientdom.settings[key].checked + } else { + irc.config[key] = clientdom.settings[key].value + } + } + } + clientdom.settings.saveStatus.innerHTML = span('Settings saved!', ['success']) - if('localStorage' in window) { - window.localStorage['teemant_settings'] = JSON.stringify(irc.config); - } + if ('localStorage' in window) { + window.localStorage['teemant_settings'] = JSON.stringify(irc.config) + } - this.timeout = setTimeout(function() { - clientdom.settings.saveStatus.innerHTML = ''; - }, 3000); - } + this.timeout = setTimeout(function () { + clientdom.settings.saveStatus.innerHTML = '' + }, 3000) + } - setInitialValues() { - if('localStorage' in window) { - if(window.localStorage['teemant_settings']) { - try { - let settings = JSON.parse(window.localStorage.teemant_settings); - for(let key in irc.config) { - let value = irc.config[key]; - let type = typeof(value); + setInitialValues () { + if ('localStorage' in window) { + if (window.localStorage['teemant_settings']) { + try { + let settings = JSON.parse(window.localStorage.teemant_settings) + for (let key in irc.config) { + let value = irc.config[key] + let type = typeof value - if(settings[key]) { - if(key == 'theme') { - this.themeSelection = settings[key]; - continue; - } + if (settings[key]) { + if (key === 'theme') { + this.themeSelection = settings[key] + continue + } - if(type == 'boolean') - clientdom.settings[key].checked = settings[key]; - else - clientdom.settings[key].value = settings[key]; - } - } - this.saveSpecified(); - return; - } catch(e) {} - } - } + if (type === 'boolean') { + clientdom.settings[key].checked = settings[key] + } else { + clientdom.settings[key].value = settings[key] + } + } + } + this.saveSpecified() + return + } catch (e) {} + } + } - for(let key in irc.config) { - let value = irc.config[key]; - let type = typeof(value); + for (let key in irc.config) { + let value = irc.config[key] + let type = typeof value - if(clientdom.settings[key]) { - if(type == 'boolean') - clientdom.settings[key].checked = value; - else - clientdom.settings[key].value = value; - } - } - } + if (clientdom.settings[key]) { + if (type === 'boolean') { + clientdom.settings[key].checked = value + } else { + clientdom.settings[key].value = value + } + } + } + } - render() { - super.render(); - clientdom.chat.className = 'chatarea'; - clientdom.nicklist.innerHTML = ''; - clientdom.topicbar.innerHTML = ''; - clientdom.letterbox.innerHTML = ''; - irc.chat.changeTitle('TeemantIRC - Settings'); - } + render () { + super.render() + clientdom.chat.className = 'chatarea' + clientdom.nicklist.innerHTML = '' + clientdom.topicbar.innerHTML = '' + clientdom.letterbox.innerHTML = '' + irc.chat.changeTitle('TeemantIRC - Settings') + } } class IRCConnector { - constructor() { - this.formLocked = false; - this.canClose = false; - this.defaults = {}; + constructor () { + this.formLocked = false + this.canClose = false + this.defaults = {} - clientdom.connector.form.onsubmit = (e) => { - if(this.formLocked) { - e.preventDefault(); - return false; - } + clientdom.connector.form.onsubmit = (e) => { + if (this.formLocked) { + e.preventDefault() + return false + } - this.formLocked = true; + this.formLocked = true - this.validateForm(e); - }; + this.validateForm(e) + } - clientdom.connector.onkeyup = (e) => { - let key = evt.keyCode || evt.which || evt.charCode || 0; - if(key === 27 && this.canClose) - this.authComplete(); - }; + clientdom.connector.onkeyup = (e) => { + let key = e.keyCode || e.which || e.charCode || 0 + if (key === 27 && this.canClose) { + this.authComplete() + } + } - clientdom.connector.pwtrigger.onclick = (e) => { - this.togglePassword(); - }; - } + clientdom.connector.pwtrigger.onclick = (e) => { + this.togglePassword() + } + } - fillFormFromURI(urlParams) { - if(this.defaults.server) - clientdom.connector.server.value = this.defaults.server; - if(this.defaults.port) - clientdom.connector.port.value = this.defaults.port; - if(this.defaults.ssl) - clientdom.connector.secure.checked = this.defaults.ssl; + fillFormFromURI (urlParams) { + if (this.defaults.server) { + clientdom.connector.server.value = this.defaults.server + } - for(let param in urlParams) { - let value = urlParams[param]; + if (this.defaults.port) { + clientdom.connector.port.value = this.defaults.port + } - switch(param) { - case 'nick': - case 'nickname': - if (validators.nickname(value)) - clientdom.connector.nickname.value = value.replace(/\?/g, rand(10000, 99999)); - break; - case 'secure': - case 'ssl': - if(value == 'true' || value == '1') - clientdom.connector.secure.checked = true; - break; - case 'password': - if(value == 'true' || value == '1') { - clientdom.connector.pwtrigger.checked = true; - this.togglePassword(); - } - break; - case 'server': - case 'host': - if(validators.iporhost(value)) - clientdom.connector.server.value = value; - break; - case 'server_data': - case 'extra': - case 'extras': - case 'connection': - if(value == 'false' || value == '0') - clientdom.connector.server_data.style.display = 'none'; - break; - case 'port': - try { - let ppp = parseInt(value); - clientdom.connector.port.value = ppp; - } catch(e) {} - break; - } - } + if (this.defaults.ssl) { + clientdom.connector.secure.checked = this.defaults.ssl + } - if(window.location.hash) - clientdom.connector.channel.value = window.location.hash; - - if(window.location.pathname.length > 4) { - let t1 = window.location.pathname.substring(1, window.location.pathname.length-1); - let proposed = ''; + for (let param in urlParams) { + let value = urlParams[param] - if(t1.indexOf('/') != -1) { - proposed = t1.split('/'); - proposed = proposed[proposed.length-1]; - } else { - proposed = t1; - } + switch (param) { + case 'nick': + case 'nickname': + if (validators.nickname(value)) { + clientdom.connector.nickname.value = value.replace(/\?/g, rand(10000, 99999)) + } + break + case 'secure': + case 'ssl': + if (value === 'true' || value === '1') { + clientdom.connector.secure.checked = true + } + break + case 'password': + if (value === 'true' || value === '1') { + clientdom.connector.pwtrigger.checked = true + this.togglePassword() + } + break + case 'server': + case 'host': + if (validators.iporhost(value)) { + clientdom.connector.server.value = value + } + break + case 'server_data': + case 'extra': + case 'extras': + case 'connection': + if (value === 'false' || value === '0') { + clientdom.connector.server_data.style.display = 'none' + } + break + case 'port': + try { + let ppp = parseInt(value) + clientdom.connector.port.value = ppp + } catch (e) {} + break + } + } - if(validators.iporhost(proposed)) - clientdom.connector.server.value = proposed; - } - } + if (window.location.hash) { + clientdom.connector.channel.value = window.location.hash + } - defaultTo(data) { - this.defaults = data; - } + if (window.location.pathname.length > 4) { + let t1 = window.location.pathname.substring(1, window.location.pathname.length - 1) + let proposed = '' - getDataFromForm() { - let nickname = clientdom.connector.nickname.value; - let password = clientdom.connector.password.value; - let channel = clientdom.connector.channel.value; - let server = clientdom.connector.server.value; - let port = clientdom.connector.port.value; + if (t1.indexOf('/') !== -1) { + proposed = t1.split('/') + proposed = proposed[proposed.length - 1] + } else { + proposed = t1 + } - if (!validators.nickname(nickname)) { - this.authMessage('Erroneous nickname!', true); - return false; - } + if (validators.iporhost(proposed)) { + clientdom.connector.server.value = proposed + } + } + } - if (channel.indexOf(',') !== -1) { - channel = channel.trim().split(','); + defaultTo (data) { + this.defaults = data + } - for (let t in channel) { - let chan = channel[t]; - - channel[t] = chan.trim(); + getDataFromForm () { + let nickname = clientdom.connector.nickname.value + let password = clientdom.connector.password.value + let channel = clientdom.connector.channel.value + let server = clientdom.connector.server.value + let port = clientdom.connector.port.value - if (chan.indexOf('#') != 0) { - channel[t] = '#'+chan; - } - } - } else if(channel != '') { - channel = channel.trim(); - if (channel.indexOf('#') != 0) { - channel = '#'+channel; - } - channel = [channel]; - } else { - channel = []; - } + if (!validators.nickname(nickname)) { + this.authMessage('Erroneous nickname!', true) + return false + } - if(!validators.iporhost(server)) { - this.authMessage('Erroneous server address!', true); - return false; - } + if (channel.indexOf(',') !== -1) { + channel = channel.trim().split(',') - try { - port = parseInt(port); - } catch(e) { - this.authMessage('Erroneous port!', true); - return false; - } + for (let t in channel) { + let chan = channel[t] - if(port < 10 || port > 65535) { - this.authMessage('Erroneous port!', true); - return false; - } + channel[t] = chan.trim() - if(!clientdom.connector.pwtrigger.checked) - password = ''; + if (chan.indexOf('#') !== 0) { + channel[t] = '#' + chan + } + } + } else if (channel !== '') { + channel = channel.trim() + if (channel.indexOf('#') !== 0) { + channel = '#' + channel + } + channel = [channel] + } else { + channel = [] + } - return {nickname: nickname, - autojoin: channel, - server: server, - port: port, - password: password, - secure: clientdom.connector.secure.checked }; - } + if (!validators.iporhost(server)) { + this.authMessage('Erroneous server address!', true) + return false + } - validateForm(event) { - event.preventDefault(); + try { + port = parseInt(port) + } catch (e) { + this.authMessage('Erroneous port!', true) + return false + } - let data = this.getDataFromForm(); - if(!data) return; + if (port < 10 || port > 65535) { + this.authMessage('Erroneous port!', true) + return false + } - irc.socket.emit('irc_create', data); - return true; - } + if (!clientdom.connector.pwtrigger.checked) { + password = '' + } - authMessage(message, error) { - clientdom.connector.messenger.innerHTML = ''+message+''; - if(error) - this.formLocked = false; - } + return { + nickname: nickname, + autojoin: channel, + server: server, + port: port, + password: password, + secure: clientdom.connector.secure.checked + } + } - togglePassword() { - if(clientdom.connector.pwtrigger.checked) - clientdom.connector.pwbox.style.display = 'block'; - else - clientdom.connector.pwbox.style.display = 'none'; - } + validateForm (event) { + event.preventDefault() - authComplete() { - clientdom.connector.frame.style.display = 'none'; - this.formLocked = false; - } + let data = this.getDataFromForm() + if (!data) return + + irc.socket.emit('irc_create', data) + return true + } + + authMessage (message, error) { + clientdom.connector.messenger.innerHTML = span(message, ['msg', (error ? 'error' : '')]) + if (error) { + this.formLocked = false + } + } + + togglePassword () { + if (clientdom.connector.pwtrigger.checked) { + clientdom.connector.pwbox.style.display = 'block' + } else { + clientdom.connector.pwbox.style.display = 'none' + } + } + + authComplete () { + clientdom.connector.frame.style.display = 'none' + this.formLocked = false + } } class InputHandler { - constructor() { - this.history = []; - this.historyCaret = 0; - this.searchNicknames = []; + constructor () { + this.history = [] + this.historyCaret = 0 + this.searchNicknames = [] - this.index = -1; - this.words = []; - this.last = ''; + this.index = -1 + this.words = [] + this.last = '' - clientdom.input.onkeyup = (evt) => { - let key = evt.keyCode || evt.which || evt.charCode || 0; - if (key == 13) { - this.handleInput(); - return; - } + clientdom.input.onkeyup = (e) => { + let key = e.keyCode || e.which || e.charCode || 0 + if (key === 13) { + this.handleInput() + return + } - this.keyUpHandle(evt, key); - }; + this.keyUpHandle(e, key) + } - clientdom.input.onkeydown = (e) => { - let key = e.keyCode || e.which || e.charCode || 0; - if (e.ctrlKey || e.shiftKey || e.altKey) { - return; - } + clientdom.input.onkeydown = (e) => { + let key = e.keyCode || e.which || e.charCode || 0 + if (e.ctrlKey || e.shiftKey || e.altKey) { + return + } - this.keyDownHandle(e, key); - }; + this.keyDownHandle(e, key) + } - clientdom.input.onfocus = (e) => { - if(irc.config.scrollOnFocus) - clientdom.letterbox.scrollTop = clientdom.letterbox.scrollHeight; - }; + clientdom.input.onfocus = (e) => { + if (irc.config.scrollOnFocus) { + clientdom.letterbox.scrollTop = clientdom.letterbox.scrollHeight + } + } - clientdom.send.onclick = (e) => { - this.handleInput(); - }; - } + clientdom.send.onclick = (e) => { + this.handleInput() + } + } - keyUpHandle(e, key) { - if(key == 38 || key == 40) { - clientdom.input.selectionStart = clientdom.input.value.length; - clientdom.input.selectionEnd = clientdom.input.value.length; - } else if(key == 9) return; - let input = clientdom.input.value; - let word = input.split(/ |\n/).pop(); + keyUpHandle (e, key) { + if (key === 38 || key === 40) { + clientdom.input.selectionStart = clientdom.input.value.length + clientdom.input.selectionEnd = clientdom.input.value.length + } else if (key === 9) return + let input = clientdom.input.value + let word = input.split(/ |\n/).pop() - // Reset iteration. - this.tabCompleteReset(); + // Reset iteration. + this.tabCompleteReset() - // Check for matches if the current word is the last word. - if (clientdom.input.selectionStart == input.length && word.length) { - // Call the match() function to filter the words. - this.tabWords = match(word, this.searchNicknames); - if(input.indexOf(word) == 0) - for(let n in this.tabWords) - this.tabWords[n] += ': '; - } + // Check for matches if the current word is the last word. + if (clientdom.input.selectionStart === input.length && word.length) { + // Call the match() function to filter the words. + this.tabWords = match(word, this.searchNicknames) + if (input.indexOf(word) === 0) { + for (let n in this.tabWords) { + this.tabWords[n] += ': ' + } + } + } + } - } + keyDownHandle (e, key) { + if (key === 38) { + if (this.historyCaret <= 0) { + this.historyCaret = 0 + } else { + this.historyCaret -= 1 + } - keyDownHandle(e, key) { - if(key == 38) { - if(this.historyCaret <= 0) { - this.historyCaret = 0; - } else { - this.historyCaret -= 1; - } + let selection = this.history[this.historyCaret] - let selection = this.history[this.historyCaret]; + if (selection) { + clientdom.input.value = selection + this.tabCompleteReset() + } - if(selection) { - clientdom.input.value = selection; - this.tabCompleteReset(); - } + return + } else if (key === 40) { + if (this.historyCaret >= this.history.length) { + this.historyCaret = this.history.length + } else { + this.historyCaret += 1 + } - return; - } else if(key == 40) { - if(this.historyCaret >= this.history.length) { - this.historyCaret = this.history.length; - } else { - this.historyCaret += 1; - } + let selection = this.history[this.historyCaret] - let selection = this.history[this.historyCaret]; + if (!this.history[this.historyCaret]) selection = '' - if(!this.history[this.historyCaret]) - selection = ''; + clientdom.input.value = selection + this.tabCompleteReset() - clientdom.input.value = selection; - this.tabCompleteReset(); + return + } - return; - } + if (key === 9) { + e.preventDefault() - if(key == 9) { - e.preventDefault(); + this.index++ - this.index++; + // Get next match. + let word = this.tabWords[this.index % this.tabWords.length] + if (!word) { + return + } - // Get next match. - let word = this.tabWords[this.index % this.tabWords.length]; - if (!word) { - return; - } + let value = clientdom.input.value + this.lastWord = this.lastWord || value.split(/ |\n/).pop() - let value = clientdom.input.value; - this.lastWord = this.lastWord || value.split(/ |\n/).pop(); + // Return if the 'minLength' requirement isn't met. + if (this.lastWord.length < 1) return - // Return if the 'minLength' requirement isn't met. - if (this.lastWord.length < 1) - return; + let text = value.substr(0, clientdom.input.selectionStart - this.lastWord.length) + word + clientdom.input.value = text - let text = value.substr(0, clientdom.input.selectionStart - this.lastWord.length) + word; - clientdom.input.value = text; + // Remember the word until next time. + this.lastWord = word + } + } - // Remember the word until next time. - this.lastWord = word; + tabCompleteReset () { + this.index = -1 + this.lastWord = '' + this.tabWords = [] + } - return; - } - } + handleInput () { + let message = clientdom.input.value + let buffer = irc.chat.getActiveChatBuffer() - tabCompleteReset() { - this.index = -1; - this.lastWord = ''; - this.tabWords = []; - } + if (!buffer) return + if (message.trim() === '') return - handleInput() { - let message = clientdom.input.value; - let buffer = irc.chat.getActiveChatBuffer(); + let listargs = message.split(' ') - if(!buffer) return; - if(message.trim() == '') return; + if (listargs[0].indexOf('/') === 0) { + let command = listargs[0].substring(1).toLowerCase() + if (command.toLowerCase() in commands) { + let cmd = commands[command] + if ('execute' in cmd) { + cmd.execute(buffer, this, command, message, listargs) + } + } else { + let foundAliased = null + for (let cmd in commands) { + if (!commands[cmd]['aliases']) continue + if (commands[cmd].aliases.indexOf(command) !== -1) foundAliased = commands[cmd] + } + if (foundAliased) { + foundAliased.execute(buffer, this, command, message, listargs) + } else { + this.commandError(buffer, listargs[0].toUpperCase() + ': Unknown command!') + } + } + } else { + irc.socket.emit('userinput', { command: 'privmsg', server: buffer.server, message: message, arguments: [buffer.name] }) + } - let listargs = message.split(' '); + this.history.push(message) + this.historyCaret = this.history.length + clientdom.input.value = '' + } - if(listargs[0].indexOf('/') == 0) { - let command = listargs[0].substring(1).toLowerCase(); - if(command.toLowerCase() in commands) { - let cmd = commands[command]; - if('execute' in cmd) - cmd.execute(buffer, this, command, message, listargs); - } else { - let foundAliased = null; - for(let cmd in commands) { - if(!commands[cmd]['aliases']) continue; - if(commands[cmd].aliases.indexOf(command) != -1) foundAliased = commands[cmd]; - } - if(foundAliased) - foundAliased.execute(buffer, this, command, message, listargs); - else - this.commandError(buffer, listargs[0].toUpperCase()+': Unknown command!'); - } - - } else { - irc.socket.emit('userinput', {command: 'privmsg', server: buffer.server, message: message, arguments: [buffer.name]}); - } - - this.history.push(message); - this.historyCaret = this.history.length; - clientdom.input.value = ''; - } - - commandError(buffer, message) { - buffer.addMessage(message, null, 'error'); - return true; - } + commandError (buffer, message) { + buffer.addMessage(message, null, 'error') + return true + } } class IRCChatWindow { - constructor() { - this.buffers = []; - this.firstServer = true; - this.currentChatBuffer = null; - this.input_handler = new InputHandler(); - - clientdom.frame.style.display = 'none'; - - clientdom.smsctrig.onclick = (e) => { - toggleClass(clientdom.chat, 'vopentrig'); - }; - } - - destroyAllChatBuffers() { - // Wipe all server data - irc.serverData = {}; - irc.serverChatQueue = {}; - this.buffers = []; - - // Clear tabs - clientdom.tabby.innerHTML = ''; - - // Reset to the defaults - irc.auther.authMessage('Disconnected', true); - clientdom.frame.style.display = 'none'; - this.firstServer = true; - window.onbeforeunload = null; - } - - getChatBufferByName(buffername) { - let result = null; - for (let t in this.buffers) { - let buf = this.buffers[t]; - if(buf.name.toLowerCase() == buffername.toLowerCase()) - result = buf; - } - return result; - } - - getActiveChatBuffer() { - let result = null; - for (let t in this.buffers) { - let buf = this.buffers[t]; - if(buf.active == true) - result = buf; - } - return result; - } - - getServerChatBuffer(server) { - let result = null; - for (let t in this.buffers) { - let buf = this.buffers[t]; - if(buf.server == server) - result = buf; - } - return result; - } - - getChatBuffersByServer(server) { - let result = []; - for (let t in this.buffers) { - let buf = this.buffers[t]; - if(buf.server == server) - result.push(buf); - } - return result; - } - - getChatBufferByServerName(server, channel) { - let result = null; - for (let t in this.buffers) { - let buf = this.buffers[t]; - if(buf.server == server && buf.name.toLowerCase() == channel.toLowerCase()) - result = buf; - } - return result; - } - - getChatBuffersByType(type) { - let result = []; - for (let t in this.buffers) { - let buf = this.buffers[t]; - if(buf.type == type) - result.push(buf); - } - return result; - } - - newServerChatBuffer(serverinfo) { - if(this.firstServer) { - clientdom.frame.style.display = 'block'; - window.onbeforeunload = function(e) { - return 'IRC will disconnect.'; - }; - } - - let prefixes = ''; - - for(let v in serverinfo.supportedModes) { - prefixes += serverinfo.supportedModes[v]; - } - - irc.serverData[serverinfo.address] = { - modeTranslation: serverinfo.supportedModes, - supportedPrefixes: prefixes, - network: serverinfo.network, - my_nick: serverinfo.nickname, - max_channel_length: serverinfo.max_channel_length - }; - - let newServer = new ChatBuffer(serverinfo.address, serverinfo.address, serverinfo.network, 'server'); - this.buffers.push(newServer); - this.render(newServer); - this.firstServer = false; - - if(irc.serverChatQueue[serverinfo.address]) { - for(let a in irc.serverChatQueue[serverinfo.address]) { - let mesg = irc.serverChatQueue[serverinfo.address][a]; - newServer.addMessage(mesg.message, mesg.from, mesg.type, mesg.time); - } - delete irc.serverChatQueue[serverinfo.address]; - } - - } - - createChatBuffer(server, name, type, autoswitch) { - let buf = this.getChatBufferByServerName(server, name); - if(buf) { - if(autoswitch) - this.render(buf); - return buf; - } - - buf = new ChatBuffer(server, name, name, type); - this.buffers.push(buf); - - if(autoswitch) - this.render(buf); - - return buf; - } - - closeChatBuffer(buffer) { - if(buffer.type == 'server') - irc.socket.emit('userinput', {command: 'quit', server: buffer.server, message: 'Server tab closed', arguments: []}); - else if(buffer.type == 'channel' && buffer.alive) - irc.socket.emit('userinput', {command: 'part', server: buffer.server, message: 'Tab closed', arguments: [buffer.name]}); - - let bufIndex = this.buffers.indexOf(buffer); - - if(buffer.active) { - if (bufIndex == 0) { - if(this.buffers[bufIndex+1]) { - this.render(this.buffers[bufIndex+1]); - } - } else { - this.render(this.buffers[bufIndex-1]); - } - } - - buffer.tab.element.remove(); - this.buffers.splice(bufIndex, 1); - - if(this.buffers.length == 0 || (this.buffers.length == 1 && this.buffers[0].type == 'applet')) { - irc.chat.destroyAllChatBuffers(); - irc.auther.authMessage('Create a new connection', false); - irc.auther.canClose = false; - clientdom.connector.frame.style.display = 'block'; - } - } - - messageChatBuffer(name, server, message) { - let buf = this.getChatBufferByServerName(server, name); - - if(buf == null) - buf = this.createChatBuffer(server, name, 'message', false); - - if(message.type == 'privmsg' && message.message.indexOf('\x01ACTION') == 0) { - message.message = message.message.substring(8); - message.type = 'action'; - } - - buf.addMessage(message.message, message.from, message.type); - } - - buildNicklist(channel, server, nicks) { - let buf = this.getChatBufferByServerName(server, channel); - - if(buf == null) - return; - - let channelSendNicks = []; - - for(let n in nicks) { - let nick = {nickname: '', prefix: '', modes: []}; - - if(irc.serverData[buf.server].supportedPrefixes.split('').indexOf(nicks[n].substring(0, 1)) != -1) { - nick.prefix = nicks[n].substring(0, 1); - nick.nickname = nicks[n].substring(1); - nick.modes = [objectGetKey(irc.serverData[buf.server].modeTranslation, nick.prefix)]; - channelSendNicks.push('{0}{1}'.format(nick.prefix, nick.nickname)); - } else { - nick.nickname = nicks[n]; - channelSendNicks.push('{1}'.format(nick.prefix, nick.nickname)); - } - - buf.nicklist.nickAddObject(nick); - } - - buf.addMessage('Nicks {0}: {1}'.format(channel, channelSendNicks.join(', ')), null, 'names'); - - if(buf.active) - buf.nicklist.render(); - } - - nickChange(server, oldNick, newNick) { - let buffers = this.getChatBuffersByServer(server); - - if(irc.serverData[server].my_nick == oldNick) { - irc.serverData[server].my_nick = newNick; - - let activeBuf = this.getActiveChatBuffer(); - - if(activeBuf.server == server) { - clientdom.currentNickname.innerHTML = newNick; - } - } - - for(let i in buffers) { - let buffer = buffers[i]; - - if(buffer.type != 'channel') continue; - if(buffer.nicklist.getNickIndex(oldNick) == null) continue; - - buffer.nicklist.nickChange(oldNick, newNick); - buffer.addMessage('{0} is now known as {1}'.format(oldNick, newNick), null, 'nick'); - } - } - - topicChange(channel, server, topic, changer) { - let buf = this.getChatBufferByServerName(server, channel); - - if (!buf) return; - - buf.topicChange(topic); - if(changer) - buf.addMessage('{0} has changed the topic of {1} to \'{2}\''.format(changer, channel, topic), - null, 'topic'); - else - buf.addMessage('Topic of {0} is \'{1}\''.format(channel, topic), null, 'topic'); - } - - handleQuit(server, user, reason) { - let buffers = this.getChatBuffersByServer(server); - - for(let i in buffers) { - let buffer = buffers[i]; - - if(buffer.type != 'channel') continue; - if(buffer.nicklist.getNickIndex(user.nickname) == null) continue; - - buffer.nicklist.nickRemove(user.nickname); - buffer.addMessage('{0}@{1} has quit {2}'.format(user.username, - user.hostname, reason), user.nickname, 'quit'); - } - } - - handleJoin(server, user, channel) { - let buffer = this.getChatBufferByServerName(server, channel); - - if(!buffer) return; - - if(user.nickname == irc.serverData[server].my_nick) - buffer.setAliveStatus(true); - else - buffer.nicklist.nickAdd(user.nickname); - - buffer.addMessage('{0}@{1} has joined {2}'.format(user.username, - user.hostname, channel), user.nickname, 'join'); - } - - handleLeave(server, user, channel, reason, kicker) { - let buffer = this.getChatBufferByServerName(server, channel); - - if(!buffer) return; - - if(user['nickname']) { - if(user.nickname == irc.serverData[server].my_nick) - buffer.setAliveStatus(false); - } else { - if(user == irc.serverData[server].my_nick) - buffer.setAliveStatus(false); - } - - if(kicker) - buffer.addMessage('has kicked {0} {1}'.format(user, reason), kicker.nickname, 'part'); - else - buffer.addMessage('{0}@{1} has left {2}{3}'.format(user.username, - user.hostname, (reason != null ? ' '+reason+'' : '')), user.nickname, 'part'); - if(kicker) - buffer.nicklist.nickRemove(user); - else - buffer.nicklist.nickRemove(user.nickname); - } - - handleMode(data) { - let buf = null; - if(data.target == irc.serverData[data.server].my_nick) - buf = this.getServerChatBuffer(data.server); - else - buf = this.getChatBufferByServerName(data.server, data.target); - - if(!buf) return; - - if(data.type == 'mode_add') { - buf.nicklist.modeAdded(data.modeTarget, data.mode); - buf.addMessage('set mode {0} +{1} {2}'.format(data.target, - data.mode, data.modeTarget), data.user.nickname, 'mode'); - } else if(data.type == 'mode_del') { - buf.nicklist.modeRemoved(data.modeTarget, data.mode); - buf.addMessage('set mode {0} -{1} {2}'.format(data.target, - data.mode, data.modeTarget), data.user.nickname, 'mode'); - } else { - buf.addMessage('set mode {0} {1}'.format(data.target, - data.message), data.user.nickname, 'mode'); - } - } - - joinChannels(server, channel) { - if (channel.indexOf(',') !== -1) { - channel = channel.trim().split(','); - - for (let t in channel) { - let chan = channel[t]; - - channel[t] = chan.trim(); - - if (chan.indexOf('#') != 0) { - channel[t] = '#'+chan; - } - } - } else if(channel != '') { - channel = channel.trim(); - if (channel.indexOf('#') != 0) { - channel = '#'+channel; - } - channel = [channel]; - } else { - channel = []; - } - - irc.socket.emit('userinput', {command: 'join', server: server, message: '', arguments: channel}); - } - - changeTitle(title) { - // TODO: notify of hot buffers - document.title = title; - irc.documentTitle = title; - } - - render(buffer) { - let activeNow = this.getActiveChatBuffer(); - this.input_handler.tabCompleteReset(); - - if(activeNow) - activeNow.switchOff(); - - buffer.render(); - } + constructor () { + this.buffers = [] + this.firstServer = true + this.currentChatBuffer = null + this.input_handler = new InputHandler() + + clientdom.frame.style.display = 'none' + + clientdom.smsctrig.onclick = (e) => { + toggleClass(clientdom.chat, 'vopentrig') + } + } + + destroyAllChatBuffers () { + // Wipe all server data + irc.serverData = {} + irc.serverChatQueue = {} + this.buffers = [] + + // Clear tabs + clientdom.tabby.innerHTML = '' + + // Reset to the defaults + irc.auther.authMessage('Disconnected', true) + clientdom.frame.style.display = 'none' + this.firstServer = true + window.onbeforeunload = null + } + + getChatBufferByName (buffername) { + let result = null + for (let t in this.buffers) { + let buf = this.buffers[t] + if (buf.name.toLowerCase() === buffername.toLowerCase()) { + result = buf + break + } + } + return result + } + + getActiveChatBuffer () { + let result = null + for (let t in this.buffers) { + let buf = this.buffers[t] + if (buf.active === true) { + result = buf + break + } + } + return result + } + + getServerChatBuffer (server) { + let result = null + for (let t in this.buffers) { + let buf = this.buffers[t] + if (buf.server === server) { + result = buf + break + } + } + return result + } + + getChatBuffersByServer (server) { + let result = [] + for (let t in this.buffers) { + let buf = this.buffers[t] + if (buf.server === server) { + result.push(buf) + } + } + return result + } + + getChatBufferByServerName (server, channel) { + let result = null + for (let t in this.buffers) { + let buf = this.buffers[t] + if (buf.server === server && buf.name.toLowerCase() === channel.toLowerCase()) { + result = buf + break + } + } + return result + } + + getChatBuffersByType (type) { + let result = [] + for (let t in this.buffers) { + let buf = this.buffers[t] + if (buf.type === type) { + result.push(buf) + } + } + return result + } + + newServerChatBuffer (serverinfo) { + if (this.firstServer) { + clientdom.frame.style.display = 'block' + window.onbeforeunload = function (e) { + return 'IRC will disconnect.' + } + } + + let prefixes = '' + + for (let v in serverinfo.supportedModes) { + prefixes += serverinfo.supportedModes[v] + } + + irc.serverData[serverinfo.address] = { + modeTranslation: serverinfo.supportedModes, + supportedPrefixes: prefixes, + network: serverinfo.network, + my_nick: serverinfo.nickname, + max_channel_length: serverinfo.max_channel_length + } + + let newServer = new ChatBuffer(serverinfo.address, serverinfo.address, serverinfo.network, 'server') + this.buffers.push(newServer) + this.render(newServer) + this.firstServer = false + + if (irc.serverChatQueue[serverinfo.address]) { + for (let a in irc.serverChatQueue[serverinfo.address]) { + let mesg = irc.serverChatQueue[serverinfo.address][a] + newServer.addMessage(mesg.message, mesg.from, mesg.type, mesg.time) + } + delete irc.serverChatQueue[serverinfo.address] + } + } + + createChatBuffer (server, name, type, autoswitch) { + let buf = this.getChatBufferByServerName(server, name) + if (buf) { + if (autoswitch) { + this.render(buf) + } + return buf + } + + buf = new ChatBuffer(server, name, name, type) + this.buffers.push(buf) + + if (autoswitch) { + this.render(buf) + } + + return buf + } + + closeChatBuffer (buffer) { + if (buffer.type === 'server') { + irc.socket.emit('userinput', { command: 'quit', server: buffer.server, message: 'Server tab closed', arguments: [] }) + } else if (buffer.type === 'channel' && buffer.alive) { + irc.socket.emit('userinput', { command: 'part', server: buffer.server, message: 'Tab closed', arguments: [buffer.name] }) + } + + let bufIndex = this.buffers.indexOf(buffer) + + if (buffer.active) { + if (bufIndex === 0) { + if (this.buffers[bufIndex + 1]) { + this.render(this.buffers[bufIndex + 1]) + } + } else { + this.render(this.buffers[bufIndex - 1]) + } + } + + buffer.tab.element.remove() + this.buffers.splice(bufIndex, 1) + + if (this.buffers.length === 0 || (this.buffers.length === 1 && this.buffers[0].type === 'applet')) { + irc.chat.destroyAllChatBuffers() + irc.auther.authMessage('Create a new connection', false) + irc.auther.canClose = false + clientdom.connector.frame.style.display = 'block' + } + } + + messageChatBuffer (name, server, message) { + let buf = this.getChatBufferByServerName(server, name) + + if (buf == null) { + buf = this.createChatBuffer(server, name, 'message', false) + } + + if (message.type === 'privmsg' && message.message.indexOf('\x01ACTION') === 0) { + message.message = message.message.substring(8) + message.type = 'action' + } + + buf.addMessage(message.message, message.from, message.type) + } + + buildNicklist (channel, server, nicks) { + let buf = this.getChatBufferByServerName(server, channel) + + if (buf == null) return + + let channelSendNicks = [] + + for (let n in nicks) { + let nick = { nickname: '', prefix: '', modes: [] } + + if (irc.serverData[buf.server].supportedPrefixes.split('').indexOf(nicks[n].substring(0, 1)) !== -1) { + nick.prefix = nicks[n].substring(0, 1) + nick.nickname = nicks[n].substring(1) + nick.modes = [objectGetKey(irc.serverData[buf.server].modeTranslation, nick.prefix)] + channelSendNicks.push(span(nick.prefix, ['prefix']) + span(nick.nickname, ['nick'])) + } else { + nick.nickname = nicks[n] + channelSendNicks.push(span(nick.nickname, ['nick'])) + } + + buf.nicklist.nickAddObject(nick) + } + + buf.addMessage(sf('Nicks %s: %s', span(channel, ['channel']), channelSendNicks.join(', ')), null, 'names') + + if (buf.active) { + buf.nicklist.render() + } + } + + nickChange (server, oldNick, newNick) { + let buffers = this.getChatBuffersByServer(server) + + if (irc.serverData[server].my_nick === oldNick) { + irc.serverData[server].my_nick = newNick + + let activeBuf = this.getActiveChatBuffer() + + if (activeBuf.server === server) { + clientdom.currentNickname.innerHTML = newNick + } + } + + for (let i in buffers) { + let buffer = buffers[i] + + if (buffer.type !== 'channel') continue + if (buffer.nicklist.getNickIndex(oldNick) == null) continue + + buffer.nicklist.nickChange(oldNick, newNick) + buffer.addMessage(sf('%s is now known as %s', oldNick, newNick), null, 'nick') + } + } + + topicChange (channel, server, topic, changer) { + let buf = this.getChatBufferByServerName(server, channel) + + if (!buf) return + + buf.topicChange(topic) + if (changer) { + buf.addMessage(sf('%s has changed the topic of %s to %s', span(changer, ['nick']), channel, topic), + null, 'topic') + } else { + buf.addMessage(sf('Topic of %s is \'%s\'', span(channel, ['channel']), topic), null, 'topic') + } + } + + handleQuit (server, user, reason) { + let buffers = this.getChatBuffersByServer(server) + + for (let i in buffers) { + let buffer = buffers[i] + + if (buffer.type !== 'channel') continue + if (buffer.nicklist.getNickIndex(user.nickname) == null) continue + + buffer.nicklist.nickRemove(user.nickname) + buffer.addMessage(sf('%s has quit %s', span(sf('%s@%s', user.username, user.hostname), ['hostmask']), reason), user.nickname, 'quit') + } + } + + handleJoin (server, user, channel) { + let buffer = this.getChatBufferByServerName(server, channel) + + if (!buffer) return + + if (user.nickname === irc.serverData[server].my_nick) { + buffer.setAliveStatus(true) + } else { + buffer.nicklist.nickAdd(user.nickname) + } + + buffer.addMessage(sf('%s has joined %s', span(sf('%s@%s', user.username, user.hostname), ['hostmask']), channel), user.nickname, 'join') + } + + handleLeave (server, user, channel, reason, kicker) { + let buffer = this.getChatBufferByServerName(server, channel) + + if (!buffer) return + + if (user['nickname']) { + if (irc.serverData[server] && user.nickname === irc.serverData[server].my_nick) { + buffer.setAliveStatus(false) + } + } else { + if (irc.serverData[server] && user === irc.serverData[server].my_nick) { + buffer.setAliveStatus(false) + } + } + + if (kicker) { + buffer.addMessage(sf('has kicked %s %s', span(user, ['nick']), reason), kicker.nickname, 'part') + } else { + buffer.addMessage(sf('%s has left %s %s', span(sf('%s@%s', user.username, user.hostname), ['hostmask']), channel, + (reason != null ? span(reason, ['reason']) : '')), user.nickname, 'part') + } + if (kicker) { + buffer.nicklist.nickRemove(user) + } else { + buffer.nicklist.nickRemove(user.nickname) + } + } + + handleMode (data) { + let buf = null + if (data.target === irc.serverData[data.server].my_nick) { + buf = this.getServerChatBuffer(data.server) + } else { + buf = this.getChatBufferByServerName(data.server, data.target) + } + + if (!buf) return + + if (data.type === 'mode_add') { + buf.nicklist.modeAdded(data.modeTarget, data.mode) + buf.addMessage('set mode ' + span(data.target, ['channel']) + ' ' + span(sf('+%s %s', data.mode, data.modeTarget), ['mode']), + data.user.nickname, 'mode') + } else if (data.type === 'mode_del') { + buf.nicklist.modeRemoved(data.modeTarget, data.mode) + buf.addMessage('set mode ' + span(data.target, ['channel']) + ' ' + span(sf('-%s %s', data.mode, data.modeTarget), ['mode']), + data.user.nickname, 'mode') + } else { + buf.addMessage('set mode ' + span(data.target, ['channel']) + ' ' + span(data.message, ['mode']), + data.user.nickname, 'mode') + } + } + + joinChannels (server, channel) { + if (channel.indexOf(',') !== -1) { + channel = channel.trim().split(',') + + for (let t in channel) { + let chan = channel[t] + + channel[t] = chan.trim() + + if (chan.indexOf('#') !== 0) { + channel[t] = '#' + chan + } + } + } else if (channel !== '') { + channel = channel.trim() + if (channel.indexOf('#') !== 0) { + channel = '#' + channel + } + channel = [channel] + } else { + channel = [] + } + + irc.socket.emit('userinput', { command: 'join', server: server, message: '', arguments: channel }) + } + + changeTitle (title) { + // TODO: notify of hot buffers + document.title = title + irc.documentTitle = title + } + + render (buffer) { + let activeNow = this.getActiveChatBuffer() + this.input_handler.tabCompleteReset() + + if (activeNow) { + activeNow.switchOff() + } + + buffer.render() + } } -/**************************\ -|** **| -|** INITIALIZATION **| -|** **| -\**************************/ +// Initialization -function parseURL() { - let match, - pl = /\+/g, // Regex for replacing addition symbol with a space - search = /([^&=]+)=?([^&]*)/g, - decode = function (s) { return decodeURIComponent(s.replace(pl, ' ')); }, - query = window.location.search.substring(1); +function parseURL () { + let match + let pl = /\+/g // Regex for replacing addition symbol with a space + let search = /([^&=]+)=?([^&]*)/g + let decode = (s) => { return decodeURIComponent(s.replace(pl, ' ')) } + let query = window.location.search.substring(1) - let urlParams = {}; - while (match = search.exec(query)) - urlParams[decode(match[1])] = decode(match[2]); + let urlParams = {} + while ((match = search.exec(query)) != null) { + urlParams[decode(match[1])] = decode(match[2]) + } - irc.auther.fillFormFromURI(urlParams); + irc.auther.fillFormFromURI(urlParams) } -function stopWarnings() { - if(Object.keys(irc.serverData).length == 0) - window.onbeforeunload = null; +function stopWarnings () { + if (Object.keys(irc.serverData).length === 0) { + window.onbeforeunload = null + } } -window.onresize = function() { - if(irc.config.scrollOnResize) - clientdom.letterbox.scrollTop = clientdom.letterbox.scrollHeight; -}; +window.onresize = function () { + if (irc.config.scrollOnResize) { + clientdom.letterbox.scrollTop = clientdom.letterbox.scrollHeight + } +} -window.onload = function() { - irc.primaryFrame = document.querySelector('.ircclient'); +window.onload = function () { + irc.primaryFrame = document.querySelector('.ircclient') - clientdom.settings['frame'] = irc.primaryFrame.querySelector('.settings'); + clientdom.settings['frame'] = irc.primaryFrame.querySelector('.settings') - for(let key in irc.config) { - clientdom.settings[key] = clientdom.settings.frame.querySelector('#s_'+key); - } + for (let key in irc.config) { + clientdom.settings[key] = clientdom.settings.frame.querySelector('#s_' + key) + } - clientdom.settings['available_themes'] = clientdom.settings.frame.querySelector('.available_themes'); - clientdom.settings['save'] = clientdom.settings.frame.querySelector('#save_settings'); - clientdom.settings['saveStatus'] = clientdom.settings.frame.querySelector('#settings_status'); - clientdom.connector['frame'] = irc.primaryFrame.querySelector('#authdialog'); - clientdom.connector['server_data'] = clientdom.connector.frame.querySelector('.server_data'); - clientdom.connector['messenger'] = clientdom.connector.frame.querySelector('#connmsg'); - clientdom.connector['form'] = clientdom.connector.frame.querySelector('#IRCConnector'); - clientdom.connector['nickname'] = clientdom.connector.form.querySelector('#nickname'); - clientdom.connector['password'] = clientdom.connector.form.querySelector('#password'); - clientdom.connector['pwtrigger'] = clientdom.connector.form.querySelector('#password_trig'); - clientdom.connector['pwbox'] = clientdom.connector.form.querySelector('.password_box'); - clientdom.connector['channel'] = clientdom.connector.form.querySelector('#channel'); - clientdom.connector['server'] = clientdom.connector.form.querySelector('#server'); - clientdom.connector['port'] = clientdom.connector.form.querySelector('#port'); - clientdom.connector['secure'] = clientdom.connector.form.querySelector('#secure'); - clientdom['tabby'] = irc.primaryFrame.querySelector('.tabby'); - clientdom['frame'] = irc.primaryFrame.querySelector('#chat'); - clientdom['letterbox'] = clientdom.frame.querySelector('.letterbox'); - clientdom['nicklist'] = clientdom.frame.querySelector('.nicklist'); - clientdom['currentNickname'] = clientdom.frame.querySelector('.my_nickname'); - clientdom['input'] = clientdom.frame.querySelector('.userinput'); - clientdom['send'] = clientdom.frame.querySelector('.sendbutton'); - clientdom['chat'] = clientdom.frame.querySelector('.chatarea'); - clientdom['topicbar'] = clientdom.chat.querySelector('.topicbar'); - clientdom['smsctrig'] = clientdom.chat.querySelector('.smsc-nicklistbtn'); - clientdom.settings['open'] = irc.primaryFrame.querySelector('.open_settings'); + clientdom.settings['available_themes'] = clientdom.settings.frame.querySelector('.available_themes') + clientdom.settings['save'] = clientdom.settings.frame.querySelector('#save_settings') + clientdom.settings['saveStatus'] = clientdom.settings.frame.querySelector('#settings_status') + clientdom.connector['frame'] = irc.primaryFrame.querySelector('#authdialog') + clientdom.connector['server_data'] = clientdom.connector.frame.querySelector('.server_data') + clientdom.connector['messenger'] = clientdom.connector.frame.querySelector('#connmsg') + clientdom.connector['form'] = clientdom.connector.frame.querySelector('#IRCConnector') + clientdom.connector['nickname'] = clientdom.connector.form.querySelector('#nickname') + clientdom.connector['password'] = clientdom.connector.form.querySelector('#password') + clientdom.connector['pwtrigger'] = clientdom.connector.form.querySelector('#password_trig') + clientdom.connector['pwbox'] = clientdom.connector.form.querySelector('.password_box') + clientdom.connector['channel'] = clientdom.connector.form.querySelector('#channel') + clientdom.connector['server'] = clientdom.connector.form.querySelector('#server') + clientdom.connector['port'] = clientdom.connector.form.querySelector('#port') + clientdom.connector['secure'] = clientdom.connector.form.querySelector('#secure') + clientdom['tabby'] = irc.primaryFrame.querySelector('.tabby') + clientdom['frame'] = irc.primaryFrame.querySelector('#chat') + clientdom['letterbox'] = clientdom.frame.querySelector('.letterbox') + clientdom['nicklist'] = clientdom.frame.querySelector('.nicklist') + clientdom['currentNickname'] = clientdom.frame.querySelector('.my_nickname') + clientdom['input'] = clientdom.frame.querySelector('.userinput') + clientdom['send'] = clientdom.frame.querySelector('.sendbutton') + clientdom['chat'] = clientdom.frame.querySelector('.chatarea') + clientdom['topicbar'] = clientdom.chat.querySelector('.topicbar') + clientdom['smsctrig'] = clientdom.chat.querySelector('.smsc-nicklistbtn') + clientdom.settings['open'] = irc.primaryFrame.querySelector('.open_settings') - irc.socket = io.connect(); + irc.socket = io.connect() - irc.settings = new Settings(); - irc.auther = new IRCConnector(); - irc.chat = new IRCChatWindow(); + irc.settings = new Settings() + irc.auther = new IRCConnector() + irc.chat = new IRCChatWindow() - irc.settings.setInitialValues(); + irc.settings.setInitialValues() - irc.socket.on('defaults', function (data) { - irc.auther.defaultTo(data); - parseURL(); - window.onpopstate = parseURL; - }); + irc.socket.on('defaults', function (data) { + irc.auther.defaultTo(data) + parseURL() + window.onpopstate = parseURL + }) - irc.socket.on('connect', function (data) { - irc.socketUp = true; - }); + irc.socket.on('connect', function (data) { + irc.socketUp = true + }) - irc.socket.on('disconnect', function (data) { - irc.socketUp = false; - irc.chat.destroyAllChatBuffers(); - clientdom.connector.frame.style.display = 'block'; - }); + irc.socket.on('disconnect', function (data) { + irc.socketUp = false + irc.chat.destroyAllChatBuffers() + clientdom.connector.frame.style.display = 'block' + }) - // Does everything - irc.socket.on('act_client', function (data) { - if(data['message']) - data.message = validators.escapeHTML(data.message); - if(data['reason']) - data.reason = validators.escapeHTML(data.reason); - - switch(data.type) { - case 'event_connect': - irc.auther.authComplete(); - irc.chat.newServerChatBuffer(data); - break; - case 'event_join_channel': - if(data.user.nickname == irc.serverData[data.server].my_nick) - irc.chat.createChatBuffer(data.server, data.channel, 'channel', true); - irc.chat.handleJoin(data.server, data.user, data.channel); - break; - case 'event_kick_channel': - irc.chat.handleLeave(data.server, data.kickee, data.channel, data.reason, data.user); - break; - case 'event_part_channel': - irc.chat.handleLeave(data.server, data.user, data.channel, data.reason); - break; - case 'event_quit': - irc.chat.handleQuit(data.server, data.user, data.reason); - break; - case 'event_server_quit': - let serverz = irc.chat.getChatBuffersByServer(data.server); - for(let a in serverz) { - let serv = serverz[a]; - serv.addMessage('You are no longer talking on this server.', null, 'error'); - serv.setAliveStatus(false); - } - - if(irc.serverData[data.server]) - delete irc.serverData[data.server]; - stopWarnings(); - break; - case 'message': - if(data.to == irc.serverData[data.server].my_nick) { - irc.chat.messageChatBuffer(data.user.nickname, data.server, {message: data.message, type: data.messageType, from: data.user.nickname}); - } else if(data.to == null) { - let atest = irc.chat.getActiveChatBuffer(); + // Does everything + irc.socket.on('act_client', function (data) { + if (data['message']) { + data.message = validators.escapeHTML(data.message) + } - if(atest.server != data.server) - atest = irc.chat.getServerChatBuffer(data.server); + if (data['reason']) { + data.reason = validators.escapeHTML(data.reason) + } - atest.addMessage(data.message, data.user.nickname, data.messageType); - } else { - irc.chat.messageChatBuffer(data.to, data.server, {message: data.message, type: data.messageType, from: data.user.nickname}); - } - break; - case 'channel_nicks': - irc.chat.buildNicklist(data.channel, data.server, data.nicks); - break; - case 'channel_topic': - if(data['topic'] && data['set_by']) - irc.chat.topicChange(data.channel, data.server, data.topic, data['set_by']); - else if(data['topic']) - irc.chat.topicChange(data.channel, data.server, data.topic, null); - else if(data['set_by']) - irc.chat.messageChatBuffer(data.channel, data.server, {message: 'Topic set by '+data.set_by+' on '+new Date(data.time*1000), type: 'topic', from: null}); - break; - case 'nick_change': - irc.chat.nickChange(data.server, data.nick, data.newNick); - break; - case 'mode_add': - case 'mode_del': - case 'mode': - irc.chat.handleMode(data); - break; - case 'server_message': - if(data['error']) data.messageType = 'error'; - if(irc.chat.getServerChatBuffer(data.server) == null) { - if(!irc.serverChatQueue[data.server]) { - irc.serverChatQueue[data.server] = []; - } else { - irc.serverChatQueue[data.server].push({type: data.messageType, message: data.message, from: data.from || null, time: new Date()}); - } - } else { - irc.chat.messageChatBuffer(data.server, data.server, {message: data.message, type: data.messageType, from: data.from || null}); - } - break; - case 'connect_message': - irc.auther.authMessage(data.message, data.error); - break; - case 'whoisResponse': - whoisMessage(data.whois, irc.chat.getActiveChatBuffer()); - break; - case 'listedchan': - irc.chat.messageChatBuffer(data.server, data.server, {message: ''+data.channel+''+ - ' '+data.users+' '+data.topic+'', - type: 'listentry', from: data.from}); - break; - } - }); -}; + switch (data.type) { + case 'event_connect': + irc.auther.authComplete() + irc.chat.newServerChatBuffer(data) + break + case 'event_join_channel': + if (data.user.nickname === irc.serverData[data.server].my_nick) { + irc.chat.createChatBuffer(data.server, data.channel, 'channel', true) + } + irc.chat.handleJoin(data.server, data.user, data.channel) + break + case 'event_kick_channel': + irc.chat.handleLeave(data.server, data.kickee, data.channel, data.reason, data.user) + break + case 'event_part_channel': + irc.chat.handleLeave(data.server, data.user, data.channel, data.reason) + break + case 'event_quit': + irc.chat.handleQuit(data.server, data.user, data.reason) + break + case 'event_server_quit': + let serverz = irc.chat.getChatBuffersByServer(data.server) + for (let a in serverz) { + let serv = serverz[a] + serv.addMessage('You are no longer talking on this server.', null, 'error') + serv.setAliveStatus(false) + } + + if (irc.serverData[data.server]) { + delete irc.serverData[data.server] + } + stopWarnings() + break + case 'message': + if (data.to === irc.serverData[data.server].my_nick) { + irc.chat.messageChatBuffer(data.user.nickname, data.server, { message: data.message, type: data.messageType, from: data.user.nickname }) + } else if (data.to == null) { + let atest = irc.chat.getActiveChatBuffer() + + if (atest.server !== data.server) { + atest = irc.chat.getServerChatBuffer(data.server) + } + + atest.addMessage(data.message, data.user.nickname, data.messageType) + } else { + irc.chat.messageChatBuffer(data.to, data.server, { message: data.message, type: data.messageType, from: data.user.nickname }) + } + break + case 'channel_nicks': + irc.chat.buildNicklist(data.channel, data.server, data.nicks) + break + case 'channel_topic': + if (data['topic'] && data['set_by']) { + irc.chat.topicChange(data.channel, data.server, data.topic, data['set_by']) + } else if (data['topic']) { + irc.chat.topicChange(data.channel, data.server, data.topic, null) + } else if (data['set_by']) { + irc.chat.messageChatBuffer(data.channel, data.server, { + message: 'Topic set by ' + data.set_by + ' on ' + (new Date(data.time * 1000)), + type: 'topic', + from: null + }) + } + break + case 'nick_change': + irc.chat.nickChange(data.server, data.nick, data.newNick) + break + case 'mode_add': + case 'mode_del': + case 'mode': + irc.chat.handleMode(data) + break + case 'server_message': + if (data['error']) data.messageType = 'error' + if (irc.chat.getServerChatBuffer(data.server) == null) { + if (!irc.serverChatQueue[data.server]) { + irc.serverChatQueue[data.server] = [] + } else { + irc.serverChatQueue[data.server].push({ type: data.messageType, message: data.message, from: data.from || null, time: new Date() }) + } + } else { + irc.chat.messageChatBuffer(data.server, data.server, { message: data.message, type: data.messageType, from: data.from || null }) + } + break + case 'connect_message': + irc.auther.authMessage(data.message, data.error) + break + case 'whoisResponse': + whoisMessage(data.whois, irc.chat.getActiveChatBuffer()) + break + case 'listedchan': + irc.chat.messageChatBuffer(data.server, data.server, + { + message: span(data.channel, ['channel']) + ' ' + + span(data.users, ['usercount']) + ' ' + + span(data.topic, ['topic']), + type: 'listentry', + from: data.from + }) + break + } + }) +} diff --git a/src/js/theme.js b/src/js/theme.js index f2414c2..0c576bc 100644 --- a/src/js/theme.js +++ b/src/js/theme.js @@ -1,46 +1,46 @@ -function swapSheet(name) { - document.querySelector('#theme_stylesheet').setAttribute('href', 'style/'+name+'.css'); +function swapSheet (name) { + document.querySelector('#theme_stylesheet').setAttribute('href', 'style/' + name + '.css') } -const themes = module.exports = { - available: { - default: { - name: 'Default', - type: 'bright', - nick_pallete: { - H: [1, 360], - S: [30, 100], - L: [30, 70] - }, - stylesheet: 'theme_default', - default: true, - colorSamples: { - toolbar: '#00c7e0', - background: '#f5f5f5' - } - }, +window.themes = module.exports = { + available: { + default: { + name: 'Default', + type: 'bright', + nick_pallete: { + H: [1, 360], + S: [30, 100], + L: [30, 70] + }, + stylesheet: 'theme_default', + default: true, + colorSamples: { + toolbar: '#00c7e0', + background: '#f5f5f5' + } + }, - night: { - name: 'Night', - type: 'dark', - nick_pallete: { - H: [1, 360], - S: [30, 100], - L: [50, 100] - }, - stylesheet: 'theme_night', - default: false, - colorSamples: { - toolbar: '#008e8e', - background: '#1d1d1d' - } - } - }, + night: { + name: 'Night', + type: 'dark', + nick_pallete: { + H: [1, 360], + S: [30, 100], + L: [50, 100] + }, + stylesheet: 'theme_night', + default: false, + colorSamples: { + toolbar: '#008e8e', + background: '#1d1d1d' + } + } + }, - change_theme: function(name) { - if(name in themes.available) { - swapSheet(themes.available[name].stylesheet); - window.irc.config.theme = name; - } - } -}; + change_theme: function (name) { + if (name in window.themes.available) { + swapSheet(window.themes.available[name].stylesheet) + window.irc.config.theme = name + } + } +} diff --git a/src/js/utility.js b/src/js/utility.js new file mode 100644 index 0000000..09878fc --- /dev/null +++ b/src/js/utility.js @@ -0,0 +1,185 @@ +import util from 'util' + +// Create element +export function el (element, content, classes, id) { + let _el = document.createElement(element) + _el.innerHTML = content + _el.classList = classes || [] + if (id) _el.id = id + return _el.outerHTML +} + +export function span (content, classes, id) { + return el('span', content, classes, id) +} + +// String format +export function sf () { + return util.format.apply(null, arguments) +} + +export function formatDate (date, format, utc) { + var MMMM = ['\x00', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] + var MMM = ['\x01', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + var dddd = ['\x02', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] + var ddd = ['\x03', 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] + function ii (i, len) { var s = i + ''; len = len || 2; while (s.length < len) s = '0' + s; return s } + + var y = utc ? date.getUTCFullYear() : date.getFullYear() + format = format.replace(/(^|[^\\])yyyy+/g, '$1' + y) + format = format.replace(/(^|[^\\])yy/g, '$1' + y.toString().substr(2, 2)) + format = format.replace(/(^|[^\\])y/g, '$1' + y) + + var M = (utc ? date.getUTCMonth() : date.getMonth()) + 1 + format = format.replace(/(^|[^\\])MMMM+/g, '$1' + MMMM[0]) + format = format.replace(/(^|[^\\])MMM/g, '$1' + MMM[0]) + format = format.replace(/(^|[^\\])MM/g, '$1' + ii(M)) + format = format.replace(/(^|[^\\])M/g, '$1' + M) + + var d = utc ? date.getUTCDate() : date.getDate() + format = format.replace(/(^|[^\\])dddd+/g, '$1' + dddd[0]) + format = format.replace(/(^|[^\\])ddd/g, '$1' + ddd[0]) + format = format.replace(/(^|[^\\])dd/g, '$1' + ii(d)) + format = format.replace(/(^|[^\\])d/g, '$1' + d) + + var H = utc ? date.getUTCHours() : date.getHours() + format = format.replace(/(^|[^\\])HH+/g, '$1' + ii(H)) + format = format.replace(/(^|[^\\])H/g, '$1' + H) + + var h = H > 12 ? H - 12 : H === 0 ? 12 : H + format = format.replace(/(^|[^\\])hh+/g, '$1' + ii(h)) + format = format.replace(/(^|[^\\])h/g, '$1' + h) + + var m = utc ? date.getUTCMinutes() : date.getMinutes() + format = format.replace(/(^|[^\\])mm+/g, '$1' + ii(m)) + format = format.replace(/(^|[^\\])m/g, '$1' + m) + + var s = utc ? date.getUTCSeconds() : date.getSeconds() + format = format.replace(/(^|[^\\])ss+/g, '$1' + ii(s)) + format = format.replace(/(^|[^\\])s/g, '$1' + s) + + var f = utc ? date.getUTCMilliseconds() : date.getMilliseconds() + format = format.replace(/(^|[^\\])fff+/g, '$1' + ii(f, 3)) + f = Math.round(f / 10) + format = format.replace(/(^|[^\\])ff/g, '$1' + ii(f)) + f = Math.round(f / 10) + format = format.replace(/(^|[^\\])f/g, '$1' + f) + + var T = H < 12 ? 'AM' : 'PM' + format = format.replace(/(^|[^\\])TT+/g, '$1' + T) + format = format.replace(/(^|[^\\])T/g, '$1' + T.charAt(0)) + + var t = T.toLowerCase() + format = format.replace(/(^|[^\\])tt+/g, '$1' + t) + format = format.replace(/(^|[^\\])t/g, '$1' + t.charAt(0)) + + var tz = -date.getTimezoneOffset() + var K = utc || !tz ? 'Z' : tz > 0 ? '+' : '-' + if (!utc) { + tz = Math.abs(tz) + var tzHrs = Math.floor(tz / 60) + var tzMin = tz % 60 + K += ii(tzHrs) + ':' + ii(tzMin) + } + format = format.replace(/(^|[^\\])K/g, '$1' + K) + + var day = (utc ? date.getUTCDay() : date.getDay()) + 1 + format = format.replace(new RegExp(dddd[0], 'g'), dddd[day]) + format = format.replace(new RegExp(ddd[0], 'g'), ddd[day]) + + format = format.replace(new RegExp(MMMM[0], 'g'), MMMM[M]) + format = format.replace(new RegExp(MMM[0], 'g'), MMM[M]) + + format = format.replace(/\\(.)/g, '$1') + + return format +} + +export function rand (randgen, min, max) { + return parseInt(randgen() * (max - min + 1), 10) + min +} + +export function removeStr (arr, str) { + let index = arr.indexOf(str) + + if (index > -1) { + arr.splice(index, 1) + return arr + } + + return arr +} + +export function grep (items, callback) { + let filtered = [] + for (let i in items) { + let item = items[i] + let cond = callback(item) + if (cond) { + filtered.push(item) + } + } + + return filtered +} + +export function match (word, array) { + return grep( + array, + function (w) { + return w.toLowerCase().indexOf(word.toLowerCase()) === 0 + } + ) +} + +export function serialize (obj) { + let str = [] + + for (let p in obj) { + if (obj.hasOwnProperty(p)) { + str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p])) + } + } + + return str.join('&') +} + +export function removeClass (element, cl) { + let classList = element.className.split(' ') + + if (classList.indexOf(cl) !== -1) { + classList.splice(classList.indexOf(cl), 1) + } + + element.className = classList.join(' ') +} + +export function addClass (element, cl) { + let classList = element.className.split(' ') + + if (classList.indexOf(cl) !== -1) { + return + } + + classList.push(cl) + element.className = classList.join(' ') +} + +export function toggleClass (element, cl) { + let classList = element.className.split(' ') + if (classList.indexOf(cl) !== -1) { + removeClass(element, cl) + } else { + addClass(element, cl) + } +} + +export function objectGetKey (obj, value) { + let key = null + for (let f in obj) { + if (obj[f] === value) { + key = f + } + } + return key +} diff --git a/server/config.js b/src/server/config.js similarity index 100% rename from server/config.js rename to src/server/config.js diff --git a/teemant.js b/src/server/index.js old mode 100755 new mode 100644 similarity index 90% rename from teemant.js rename to src/server/index.js index 93c21ef..757ecda --- a/teemant.js +++ b/src/server/index.js @@ -1,20 +1,19 @@ -#!/usr/bin/env node 'use strict' import express from 'express' import path from 'path' import sockio from 'socket.io' import dns from 'dns' -import pkginfo from './package.json' -import irclib from './server/irc' -import webirc from './server/webirc' +import pkginfo from '../package.json' +import irclib from './irc' +import webirc from './webirc' -import config from './server/config' -import logger from './server/logger' +import config from './config' +import logger from './logger' const app = express() const router = express.Router() -const pubdir = path.join(__dirname, 'build') +const pubdir = path.join(__dirname, 'public') const port = config.server.port || 8080 const cacheAge = 365 * 24 * 60 * 60 * 1000 @@ -36,16 +35,16 @@ let customCTCPs = { process.stdin.resume() router.get('/', function (req, res) { - res.sendFile(path.join(pubdir, '/document/index.html')) + res.sendFile(path.join(pubdir, '/index.html')) }) router.get('/:server?*', function (req, res) { - res.sendFile(path.join(pubdir, '/document/index.html')) + res.sendFile(path.join(pubdir, '/index.html')) }) app.use('/', express.static(pubdir, { maxAge: cacheAge })) app.use('/', express.static(path.join(pubdir, 'icons'), { maxAge: cacheAge })) -app.use('/', express.static(path.join(__dirname, 'static'), { maxAge: cacheAge })) +app.use('/', express.static(path.join(pubdir, 'static'), { maxAge: cacheAge })) app.use('/:server', express.static(pubdir, { maxAge: cacheAge })) app.use('/:server', express.static(path.join(pubdir, 'icons'), { maxAge: cacheAge })) @@ -116,7 +115,7 @@ io.sockets.on('connection', function (socket) { delete connections[socket.id] - logger.debugLog('clientID: {0} disconnect'.format(socket.id)) + logger.debugLog(`clientID: ${socket.id} disconnect`) }) socket.on('error', (e) => { @@ -130,7 +129,12 @@ io.sockets.on('connection', function (socket) { logger.debugLog(`[${socket.id}] ->`, data) - serv.handler.handleUserLine(data) + try { + serv.handler.handleUserLine(data) + } catch (e) { + console.error('An error occured while handling message from client', data) + console.error(e.stack) + } }) socket.on('irc_create', function (connectiondata) { diff --git a/server/irc/index.js b/src/server/irc/index.js similarity index 100% rename from server/irc/index.js rename to src/server/irc/index.js diff --git a/server/irc/irc.js b/src/server/irc/irc.js similarity index 96% rename from server/irc/irc.js rename to src/server/irc/irc.js index 9d99f0e..50f3bde 100644 --- a/server/irc/irc.js +++ b/src/server/irc/irc.js @@ -471,7 +471,7 @@ class IRCConnectionHandler { // start whois queue list = { nickname: line.arguments[1], - hostmask: '%s!%s@%s'.format(line.arguments[1], line.arguments[2], line.arguments[3]), + hostmask: util.format('%s!%s@%s', line.arguments[1], line.arguments[2], line.arguments[3]), realname: line.trailing || '' } this.whoisManage(line.arguments[1], list) @@ -652,7 +652,7 @@ class IRCConnection extends EventEmitter { buffer = data.pop() data.forEach((line) => { if (line.indexOf('PING') === 0) { - this.socket.write('PONG %s\r\n', line.substring(4)) + this.write('PONG %s\r\n', line.substring(4)) return } @@ -660,7 +660,13 @@ class IRCConnection extends EventEmitter { let parsed = parse(line) this.emit('line', parsed) - this.handler.handleServerLine(parsed) + + try { + this.handler.handleServerLine(parsed) + } catch (e) { + console.error('An error occured while handling parsed server line', parsed) + console.error(e.stack) + } }) }) @@ -676,7 +682,7 @@ class IRCConnection extends EventEmitter { authenticate () { if (this.config.password) { - this.socket.write('PASS %s\r\n', this.config.password) + this.write('PASS %s\r\n', this.config.password) } if (this.extras.authenticationSteps) { @@ -686,8 +692,8 @@ class IRCConnection extends EventEmitter { } } - this.socket.write('USER %s 8 * :%s\r\n', this.config.username, this.config.realname) - this.socket.write('NICK %s\r\n', this.config.nickname) + this.write('USER %s 8 * :%s\r\n', this.config.username, this.config.realname) + this.write('NICK %s\r\n', this.config.nickname) } disconnect (message) { @@ -697,7 +703,7 @@ class IRCConnection extends EventEmitter { } this.queue['close'] = true - this.socket.write('QUIT :%s\r\n', (message != null ? message : this.globalConfig.default_quit_msg)) + this.write('QUIT :%s\r\n', (message != null ? message : this.globalConfig.default_quit_msg)) } write () { diff --git a/server/irc/parser.js b/src/server/irc/parser.js similarity index 100% rename from server/irc/parser.js rename to src/server/irc/parser.js diff --git a/server/logger.js b/src/server/logger.js similarity index 100% rename from server/logger.js rename to src/server/logger.js diff --git a/server/webirc.js b/src/server/webirc.js similarity index 97% rename from server/webirc.js rename to src/server/webirc.js index cd7fcd4..dd9498e 100644 --- a/server/webirc.js +++ b/src/server/webirc.js @@ -2,8 +2,8 @@ import dns from 'dns' import fs from 'fs' import path from 'path' -import config from './server/config' -import logger from './server/logger' +import config from './config' +import logger from './logger' const webircDataPath = path.join(__dirname, '..', 'webirc.data.json') diff --git a/src/style/layout.styl b/src/style/layout.styl index daaee99..882f8f6 100644 --- a/src/style/layout.styl +++ b/src/style/layout.styl @@ -181,7 +181,7 @@ body margin-left: 4px; border-radius: 5px; vertical-align: bottom; - .emojione + .emoji width: 20px; height: 20px; vertical-align: sub; diff --git a/webpack.config.js b/webpack.config.js index 59ca256..f70e360 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,13 +2,14 @@ const webpack = require('webpack') const path = require('path') +const CopyWebpackPlugin = require('copy-webpack-plugin') module.exports = { entry: { main: './src/js/main' }, output: { - path: path.join(__dirname, 'dist', 'js'), + path: path.join(__dirname, 'app', 'public'), filename: '[name].js' }, module: { @@ -22,10 +23,6 @@ module.exports = { presets: [ '@babel/preset-env' ] } } - }, - { - test: /src\/style\/.+\.styl$/, - loader: 'file-loader?name=./dist/style/[name].css!css-loader!stylus-loader' } ] }, @@ -33,7 +30,15 @@ module.exports = { new webpack.ProvidePlugin({ // Detect and inject _: 'underscore' - }) + }), + new CopyWebpackPlugin([{ + from: 'src/document/index.html', + to: '.' + }, + { + from: 'static', + to: 'static' + }]) ], devtool: 'inline-source-map' }