Consistency / some clean-up
This commit is contained in:
parent
6c6aa85bf4
commit
43a136d293
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "teemantirc",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "teemantirc",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.2",
|
||||
"description": "A Web-based IRC client",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -4,17 +4,16 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TeemantIRC</title>
|
||||
<script src="//twemoji.maxcdn.com/2/twemoji.min.js?11.2"></script>
|
||||
<script src="//twemoji.maxcdn.com/v/latest/twemoji.min.js" crossorigin="anonymous"></script>
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" type="text/css">
|
||||
<link rel="stylesheet" type="text/css" href="style/layout.css">
|
||||
<link rel="stylesheet" type="text/css" href="style/theme_default.css" id="theme_stylesheet">
|
||||
</head>
|
||||
<!-- Codepony Diamond -->
|
||||
<body>
|
||||
<section class="ircclient">
|
||||
<div class="coverwindow" id="authdialog">
|
||||
<div class="wrapper">
|
||||
<h1 class="grade1">Teemant</h1>
|
||||
<h1 class="grade1" title="Teemant"><img src="/image/diamond.svg" alt="Teemant"/></h1>
|
||||
<i class="grade3" id="connmsg">Think of a nickname</i>
|
||||
<form action="" id="IRCConnector">
|
||||
<label for="nickname">Nickname</label>
|
||||
|
166
src/js/irc.js
166
src/js/irc.js
@ -1,4 +1,5 @@
|
||||
/* global WebSocket */
|
||||
/* eslint-disable no-control-regex */
|
||||
import { EventEmitter } from 'events'
|
||||
import parse from './parser'
|
||||
import { format } from 'util'
|
||||
@ -69,7 +70,7 @@ class FakeSocket {
|
||||
}
|
||||
}
|
||||
|
||||
async function _waitOpen (s) {
|
||||
async function waitForSocketOpen (s) {
|
||||
let o
|
||||
let c
|
||||
|
||||
@ -92,7 +93,7 @@ async function _waitOpen (s) {
|
||||
return true
|
||||
}
|
||||
|
||||
async function _trySocket (address, port, ssl, useTranslator) {
|
||||
async function createSocket (address, port, ssl, useTranslator) {
|
||||
// Attempt a direct ws connection
|
||||
let proto = 'ws'
|
||||
if (ssl) proto = 'wss'
|
||||
@ -104,10 +105,10 @@ async function _trySocket (address, port, ssl, useTranslator) {
|
||||
}
|
||||
let tSock = new WebSocket(proto + '://' + conn)
|
||||
try {
|
||||
await _waitOpen(tSock)
|
||||
await waitForSocketOpen(tSock)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return _trySocket(address, port, ssl, true)
|
||||
return createSocket(address, port, ssl, true)
|
||||
}
|
||||
|
||||
tSock.open = true
|
||||
@ -123,7 +124,7 @@ async function _trySocket (address, port, ssl, useTranslator) {
|
||||
|
||||
translatorSocket = new WebSocket(`ws${window.location.protocol === 'https:' ? 's' : ''}://${window.location.host}`)
|
||||
console.log('Opening translator socket')
|
||||
await _waitOpen(translatorSocket)
|
||||
await waitForSocketOpen(translatorSocket)
|
||||
return new FakeSocket(address, translatorSocket)
|
||||
}
|
||||
|
||||
@ -152,11 +153,11 @@ class IRCConnectionHandler {
|
||||
break
|
||||
case 'quit':
|
||||
this.conn.write('%s :%s', data.command.toUpperCase(), (data.message === ''
|
||||
? this.conn.globalConfig.default_quit_msg : data.message))
|
||||
? this.conn.defaultParams.default_quit_msg : data.message))
|
||||
break
|
||||
case 'privmsg':
|
||||
this.conn.write('PRIVMSG %s :%s', data.arguments[0], data.message)
|
||||
this.conn.emit('pass_to_client', {
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'message',
|
||||
messageType: 'privmsg',
|
||||
to: data.arguments[0],
|
||||
@ -169,7 +170,7 @@ class IRCConnectionHandler {
|
||||
break
|
||||
case 'notice':
|
||||
this.conn.write('NOTICE %s :%s', data.arguments[0], data.message)
|
||||
this.conn.emit('pass_to_client', {
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'message',
|
||||
messageType: 'notice',
|
||||
to: data.arguments[0],
|
||||
@ -193,9 +194,9 @@ class IRCConnectionHandler {
|
||||
}
|
||||
|
||||
this.conn.write('PRIVMSG %s :\x01%s\x01', data.arguments[0], ctcpmsg)
|
||||
this.conn.emit('pass_to_client', {
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'message',
|
||||
messageType: 'ctcp_request',
|
||||
messageType: 'ctcpRequest',
|
||||
to: this.conn.config.nickname,
|
||||
user: {
|
||||
nickname: data.arguments[0]
|
||||
@ -209,7 +210,7 @@ class IRCConnectionHandler {
|
||||
}
|
||||
if (data.targetType === 'channel' || data.targetType === 'message') {
|
||||
this.conn.write('PRIVMSG %s :%s', data.target, data.message)
|
||||
this.conn.emit('pass_to_client', {
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'message',
|
||||
messageType: 'privmsg',
|
||||
to: data.target,
|
||||
@ -283,7 +284,12 @@ class IRCConnectionHandler {
|
||||
let list = null
|
||||
switch (line.command) {
|
||||
case 'error':
|
||||
this.conn.emit('connerror', { type: 'irc_error', raw: line.raw })
|
||||
this.conn.emit('connectionError', new Error('IRCError' + line.raw))
|
||||
break
|
||||
case 'PONG':
|
||||
this.conn.ping = Date.now() - this.conn.pingSent
|
||||
this.conn.pingSent = 0
|
||||
this.conn.emit('ping', this.conn.ping)
|
||||
break
|
||||
case '001':
|
||||
this.conn.data.actualServer = line.user.hostname
|
||||
@ -321,16 +327,16 @@ class IRCConnectionHandler {
|
||||
break
|
||||
case 'JOIN':
|
||||
if (line.trailing) {
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'event_join_channel',
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'joinChannel',
|
||||
user: line.user,
|
||||
channel: line.trailing,
|
||||
server: serverName
|
||||
})
|
||||
} else {
|
||||
for (let i in line.arguments) {
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'event_join_channel',
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'joinChannel',
|
||||
user: line.user,
|
||||
channel: line.arguments[i],
|
||||
server: serverName
|
||||
@ -339,8 +345,8 @@ class IRCConnectionHandler {
|
||||
}
|
||||
break
|
||||
case 'PART':
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'event_part_channel',
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'partChannel',
|
||||
user: line.user,
|
||||
channel: line.arguments[0],
|
||||
reason: line.trailing,
|
||||
@ -348,8 +354,8 @@ class IRCConnectionHandler {
|
||||
})
|
||||
break
|
||||
case 'QUIT':
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'event_quit',
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'quit',
|
||||
user: line.user,
|
||||
reason: line.trailing,
|
||||
server: serverName
|
||||
@ -375,8 +381,8 @@ class IRCConnectionHandler {
|
||||
case '366':
|
||||
if (!this.conn.queue['names']) break
|
||||
if (this.conn.queue['names'][line.arguments[1]]) {
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'channel_nicks',
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'nicks',
|
||||
channel: line.arguments[1],
|
||||
nicks: this.conn.queue['names'][line.arguments[1]],
|
||||
server: serverName
|
||||
@ -395,7 +401,7 @@ class IRCConnectionHandler {
|
||||
}
|
||||
|
||||
if (line.user.nickname !== '') {
|
||||
this.conn.emit('pass_to_client', {
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'message',
|
||||
messageType: 'privmsg',
|
||||
to: line.arguments[0],
|
||||
@ -404,8 +410,8 @@ class IRCConnectionHandler {
|
||||
server: serverName
|
||||
})
|
||||
} else {
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'server_message',
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'serverMessage',
|
||||
messageType: 'privmsg',
|
||||
message: line.trailing,
|
||||
server: serverName,
|
||||
@ -423,9 +429,9 @@ class IRCConnectionHandler {
|
||||
message = Math.floor(Date.now() / 1000) - composethis[1] + 's'
|
||||
}
|
||||
|
||||
this.conn.emit('pass_to_client', {
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'message',
|
||||
messageType: 'ctcp_response',
|
||||
messageType: 'ctcpResponse',
|
||||
to: line.arguments[0],
|
||||
user: line.user,
|
||||
message: message,
|
||||
@ -435,7 +441,7 @@ class IRCConnectionHandler {
|
||||
}
|
||||
|
||||
if (line.user.nickname !== '') {
|
||||
this.conn.emit('pass_to_client', {
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'message',
|
||||
messageType: 'notice',
|
||||
to: line.arguments[0],
|
||||
@ -444,8 +450,8 @@ class IRCConnectionHandler {
|
||||
server: serverName
|
||||
})
|
||||
} else {
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'server_message',
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'serverMessage',
|
||||
messageType: 'notice',
|
||||
message: line.trailing,
|
||||
server: serverName,
|
||||
@ -458,35 +464,35 @@ class IRCConnectionHandler {
|
||||
this.conn.config.nickname = line.arguments[0]
|
||||
}
|
||||
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'nick_change', nick: line.user.nickname, newNick: line.arguments[0], server: serverName
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'nick', nick: line.user.nickname, newNick: line.arguments[0], server: serverName
|
||||
})
|
||||
break
|
||||
case 'KICK':
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'event_kick_channel', user: line.user, channel: line.arguments[0], reason: line.trailing, kickee: line.arguments[1], server: serverName
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'kickedFromChannel', user: line.user, channel: line.arguments[0], reason: line.trailing, kickee: line.arguments[1], server: serverName
|
||||
})
|
||||
break
|
||||
case 'TOPIC':
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'channel_topic', channel: line.arguments[0], set_by: line.user.nickname, topic: line.trailing, server: serverName
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'topic', channel: line.arguments[0], setBy: line.user.nickname, topic: line.trailing, server: serverName
|
||||
})
|
||||
break
|
||||
case '332':
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'channel_topic', channel: line.arguments[1], topic: line.trailing, server: serverName
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'topic', channel: line.arguments[1], topic: line.trailing, server: serverName
|
||||
})
|
||||
break
|
||||
case '333':
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'channel_topic', channel: line.arguments[1], set_by: line.arguments[2], time: (line.arguments[3] || line.trailing), server: serverName
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'topic', channel: line.arguments[1], setBy: line.arguments[2], time: (line.arguments[3] || line.trailing), server: serverName
|
||||
})
|
||||
break
|
||||
case '375':
|
||||
case '372':
|
||||
case '376':
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'server_message', messageType: 'motd', message: line.trailing, server: serverName, from: realServerName
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'serverMessage', messageType: 'motd', message: line.trailing, server: serverName, from: realServerName
|
||||
})
|
||||
break
|
||||
case '006':
|
||||
@ -500,16 +506,16 @@ class IRCConnectionHandler {
|
||||
case '351':
|
||||
case '381':
|
||||
case '489':
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'server_message', messageType: 'regular', message: line.trailing, server: serverName, from: realServerName
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'serverMessage', messageType: 'regular', message: line.trailing, server: serverName, from: realServerName
|
||||
})
|
||||
break
|
||||
case '252':
|
||||
case '254':
|
||||
case '396':
|
||||
case '042':
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'server_message', messageType: 'regular', message: line.arguments[1] + ' ' + line.trailing, server: serverName, from: realServerName
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'serverMessage', messageType: 'regular', message: line.arguments[1] + ' ' + line.trailing, server: serverName, from: realServerName
|
||||
})
|
||||
break
|
||||
case '501':
|
||||
@ -519,7 +525,7 @@ class IRCConnectionHandler {
|
||||
case '482':
|
||||
case '331':
|
||||
case '432':
|
||||
this.conn.emit('pass_to_client', {
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'message',
|
||||
to: null,
|
||||
message: line.arguments[1] + ': ' + line.trailing,
|
||||
@ -556,8 +562,8 @@ class IRCConnectionHandler {
|
||||
for (let i in modes) {
|
||||
let mode = modes[i]
|
||||
if (this.conn.data.supportedModes[mode]) {
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'mode_' + (method === '+' ? 'add' : 'del'),
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'mode' + (method === '+' ? 'Add' : 'Del'),
|
||||
target: line.arguments[0],
|
||||
mode: mode,
|
||||
modeTarget: line.arguments[2 + parseInt(i)],
|
||||
@ -575,7 +581,7 @@ class IRCConnectionHandler {
|
||||
}
|
||||
|
||||
if (pass.length > 0) {
|
||||
this.conn.emit('pass_to_client', {
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'mode',
|
||||
target: line.arguments[0],
|
||||
message: method + pass.join(''),
|
||||
@ -622,7 +628,7 @@ class IRCConnectionHandler {
|
||||
case '312':
|
||||
list = {
|
||||
server: line.arguments[2],
|
||||
server_name: line.trailing || ''
|
||||
serverName: line.trailing || ''
|
||||
}
|
||||
this.whoisManage(line.arguments[1], list)
|
||||
break
|
||||
@ -666,7 +672,7 @@ class IRCConnectionHandler {
|
||||
case '318':
|
||||
if (!this.conn.queue.whois || !this.conn.queue.whois[line.arguments[1]]) return
|
||||
|
||||
this.conn.emit('pass_to_client', {
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'whoisResponse',
|
||||
whois: this.conn.queue.whois[line.arguments[1]],
|
||||
server: serverName,
|
||||
@ -676,8 +682,8 @@ class IRCConnectionHandler {
|
||||
delete this.conn.queue.whois[line.arguments[1]]
|
||||
break
|
||||
case '321':
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'listedchan',
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'listedChannel',
|
||||
channel: 'Channel',
|
||||
users: 'Users',
|
||||
topic: 'Topic',
|
||||
@ -686,8 +692,8 @@ class IRCConnectionHandler {
|
||||
})
|
||||
break
|
||||
case '322':
|
||||
this.conn.emit('pass_to_client', {
|
||||
type: 'listedchan',
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'listedChannel',
|
||||
channel: line.arguments[1],
|
||||
users: line.arguments[2],
|
||||
topic: line.trailing,
|
||||
@ -699,27 +705,37 @@ class IRCConnectionHandler {
|
||||
// might come in the future, who knows
|
||||
this.conn.write('CAP END')
|
||||
break
|
||||
default:
|
||||
let argc = line.arguments
|
||||
if (argc.indexOf(this.conn.config.nickname) === 0) argc = argc.slice(1)
|
||||
this.conn.emit('fromServer', {
|
||||
type: 'serverMessage',
|
||||
messageType: 'unknown',
|
||||
message: (argc.length ? argc.join(' ') + ' :' : '') + line.trailing,
|
||||
server: serverName,
|
||||
from: realServerName
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IRCConnection extends EventEmitter {
|
||||
constructor (providedInfo, globalConfig, extras) {
|
||||
constructor (providedInfo, defaultParams, extras) {
|
||||
super()
|
||||
|
||||
this.globalConfig = globalConfig
|
||||
this.defaultParams = defaultParams
|
||||
this.extras = extras || { authenticationSteps: [], ctcps: {} }
|
||||
this.config = {
|
||||
nickname: 'teemant',
|
||||
username: globalConfig.username,
|
||||
realname: globalConfig.realname,
|
||||
username: defaultParams.username,
|
||||
realname: defaultParams.realname,
|
||||
server: 'localhost',
|
||||
port: 6667,
|
||||
autojoin: [],
|
||||
secure: globalConfig.secure_by_default,
|
||||
secure: defaultParams.secure_by_default,
|
||||
password: '',
|
||||
address: providedInfo.server,
|
||||
rejectUnauthorized: globalConfig.rejectUnauthorizedCertificates
|
||||
rejectUnauthorized: defaultParams.rejectUnauthorizedCertificates
|
||||
}
|
||||
|
||||
for (let a in providedInfo) {
|
||||
@ -739,21 +755,19 @@ class IRCConnection extends EventEmitter {
|
||||
maxChannelLength: 64,
|
||||
supportedModes: {}
|
||||
}
|
||||
this.authorizationError = ''
|
||||
this.queue = {}
|
||||
this.pingSent = 0
|
||||
this.ping = 0
|
||||
}
|
||||
|
||||
connect () {
|
||||
async function wrapped (argument) {
|
||||
try {
|
||||
this.socket = await _trySocket(this.config.server, this.config.port, this.config.secure)
|
||||
} catch (e) {
|
||||
this.emit('connerror', { type: 'sock_error', message: 'A socket error occured.' })
|
||||
throw e
|
||||
}
|
||||
this.socket = await createSocket(this.config.server, this.config.port, this.config.secure)
|
||||
|
||||
this.socket.addEventListener('message', (e) => {
|
||||
let line = e.data
|
||||
|
||||
// Handle from-server pings
|
||||
if (line.indexOf('PING') === 0) {
|
||||
this.write('PONG %s', line.substring(4))
|
||||
return
|
||||
@ -774,7 +788,7 @@ class IRCConnection extends EventEmitter {
|
||||
|
||||
this.socket.addEventListener('close', (data) => {
|
||||
if (!this.queue['close']) {
|
||||
this.emit('closed', { type: 'sock_closed', raw: data, message: 'Connection closed.' })
|
||||
this.emit('connectionClosed', new Error('Socket has been closed.'))
|
||||
}
|
||||
|
||||
this.connected = false
|
||||
@ -804,35 +818,37 @@ class IRCConnection extends EventEmitter {
|
||||
this.write('NICK %s', this.config.nickname)
|
||||
|
||||
this.bindFinal()
|
||||
this.sendPing()
|
||||
}
|
||||
|
||||
bindFinal () {
|
||||
this.on('userinput', (data) => {
|
||||
this.on('userInput', (data) => {
|
||||
return this.handler.handleUserLine(data)
|
||||
})
|
||||
|
||||
this.once('authenticated', () => this.sendPing())
|
||||
}
|
||||
|
||||
sendPing () {
|
||||
if (!this.connected) return
|
||||
this.write('PING :' + this.data.actualServer)
|
||||
this.pingSent = Date.now()
|
||||
setTimeout(() => this.sendPing(), 5000)
|
||||
}
|
||||
|
||||
disconnect (message) {
|
||||
if (!this.connected) {
|
||||
this.emit('connerror', { type: 'sock_closed', message: 'Connection already closed.' })
|
||||
this.emit('connectionError', new Error('SocketError: Socket is already closed.'))
|
||||
return
|
||||
}
|
||||
|
||||
this.queue['close'] = true
|
||||
this.write('QUIT :%s', (message != null ? message : this.globalConfig.default_quit_msg))
|
||||
this.write('QUIT :%s', (message != null ? message : this.defaultParams.default_quit_msg))
|
||||
}
|
||||
|
||||
write () {
|
||||
let message = format.apply(null, arguments)
|
||||
if (!this.connected) {
|
||||
return this.emit('connerror', { type: 'sock_closed', message: 'Connection is closed.' })
|
||||
return this.emit('connectionError', new Error('SocketError: Socket is closed.'))
|
||||
}
|
||||
|
||||
this.socket.send(message + '\r\n')
|
||||
|
153
src/js/main.js
153
src/js/main.js
@ -38,13 +38,16 @@ const customCTCPs = {
|
||||
},
|
||||
SOURCE: function (data, connection) {
|
||||
return 'https://gitlab.icynet.eu/IcyNetwork/teemant'
|
||||
},
|
||||
TIME: function (data, connection) {
|
||||
return new Date()
|
||||
}
|
||||
}
|
||||
|
||||
const clientdom = { connector: {}, settings: {} }
|
||||
|
||||
const colorizer = {
|
||||
get_random_color: function (nickname) {
|
||||
getRandomColor: function (nickname) {
|
||||
let themefunc = themes.available[irc.config.theme].nick_pallete
|
||||
|
||||
let randgen = seedrandom(nickname)
|
||||
@ -61,9 +64,8 @@ const colorizer = {
|
||||
|
||||
// Utilities
|
||||
|
||||
const validators = {}
|
||||
|
||||
validators.iporhost = function (str) {
|
||||
const 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)) {
|
||||
@ -73,26 +75,22 @@ validators.iporhost = function (str) {
|
||||
}
|
||||
|
||||
return valid
|
||||
}
|
||||
|
||||
validators.nickname = function (str) {
|
||||
},
|
||||
nickname: function (str) {
|
||||
if (str.match(/[a-z_\-\[\]\\^{}|`][a-z0-9_\-\[\]\\^{}|`]*/i)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
validators.escapeHTML = function (str) {
|
||||
},
|
||||
escapeHTML: function (str) {
|
||||
return str.replace(/</g, '<').replace(/>/, '>')
|
||||
}
|
||||
}
|
||||
|
||||
const processors = {
|
||||
inline_color: function (text) {
|
||||
// TODO: Figure out how to make hex colors behave
|
||||
// const hexRegex = /(^|[^&])(\#[0-9a-f]{6};?)(?!\w)/gmi
|
||||
inlineColor: function (text) {
|
||||
const rgbRegex = /(.?)(rgba?\((?:\s*\d+\s*,){2}\s*\d+\s*(?:,\s*[\d.]+\s*)?\);?)/gmi
|
||||
const substitute = '$1$2 <div class="color_sample" style="background-color:$2"></div>'
|
||||
// text = text.replace(hexRegex, substitute)
|
||||
text = text.replace(rgbRegex, substitute)
|
||||
return text
|
||||
},
|
||||
@ -156,9 +154,9 @@ function whoisMessage (whoisData, buffer) {
|
||||
break
|
||||
case 'server':
|
||||
let adfd = sf('is on %s', span(whoisData[key], ['server', 'nick']))
|
||||
if (whoisData['server_name']) {
|
||||
if (whoisData['serverName']) {
|
||||
adfd += ' '
|
||||
adfd += span(validators.escapeHTML(whoisData['server_name']), ['hostmask'])
|
||||
adfd += span(validators.escapeHTML(whoisData['serverName']), ['hostmask'])
|
||||
}
|
||||
messages.push(adfd)
|
||||
break
|
||||
@ -198,7 +196,7 @@ let composer = {
|
||||
message = colorizer.strip(message)
|
||||
}
|
||||
|
||||
message = processors.inline_color(message)
|
||||
message = processors.inlineColor(message)
|
||||
message = processors.channelify(message)
|
||||
message = processors.linkify(message)
|
||||
message = processors.emojify(message)
|
||||
@ -220,11 +218,11 @@ let composer = {
|
||||
case 'join':
|
||||
element.innerHTML += arrowin + ' ' + span(span(sender, ['actionee', 'nick']) + ' ' + message, ['content'])
|
||||
break
|
||||
case 'ctcp_response':
|
||||
case 'ctcpResponse':
|
||||
element.innerHTML += asterisk + ' CTCP response from ' + span(sender, ['actionee', 'nick']) + ' '
|
||||
element.innerHTML += span(message, ['content'])
|
||||
break
|
||||
case 'ctcp_request':
|
||||
case 'ctcpRequest':
|
||||
element.innerHTML += asterisk + ' CTCP request to ' + span(sender, ['actionee', 'nick']) + ' '
|
||||
element.innerHTML += span(message, ['content'])
|
||||
break
|
||||
@ -241,7 +239,7 @@ let composer = {
|
||||
if (sender) {
|
||||
let sndr1 = element.querySelector('.sender')
|
||||
if (sndr1) {
|
||||
sndr1.style.color = colorizer.get_random_color(sndr1.innerHTML)
|
||||
sndr1.style.color = colorizer.getRandomColor(sndr1.innerHTML)
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,7 +247,7 @@ let composer = {
|
||||
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)
|
||||
sndr2[a].style.color = colorizer.getRandomColor(sndr2[a].innerHTML)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -286,7 +284,7 @@ let composer = {
|
||||
if (defaultNick) params.nickname = defaultNick
|
||||
if (port && port !== 6667) params.port = port
|
||||
if (useSSL) params.secure = 1
|
||||
if (hideServerData) params.server_data = 0
|
||||
if (hideServerData) params.extras = 0
|
||||
if (Object.keys(params).length > 0) builder += '?' + serialize(params)
|
||||
|
||||
if (channels) {
|
||||
@ -318,7 +316,7 @@ let composer = {
|
||||
function sendToServer (server, obj) {
|
||||
let i = irc.handler.getClientByActualServer(server)
|
||||
if (!i) throw new Error('Invalid connection ' + server)
|
||||
i.emit('userinput', obj)
|
||||
i.emit('userInput', obj)
|
||||
}
|
||||
|
||||
// onclick food
|
||||
@ -509,7 +507,7 @@ let commands = {
|
||||
arguments: [buffer.name]
|
||||
})
|
||||
},
|
||||
description: '<message> - act as yourself',
|
||||
description: '<message> - Act as yourself. (CTCP ACTION)',
|
||||
aliases: ['me']
|
||||
},
|
||||
|
||||
@ -524,14 +522,14 @@ let commands = {
|
||||
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
|
||||
let mynick = irc.serverData[buffer.server].userNick
|
||||
buffer.addMessage('Your nickname is: <span class="nick">' + mynick + '</span>', null, 'help')
|
||||
}
|
||||
return
|
||||
}
|
||||
sendToServer(buffer.server, { command: 'nick', message: '', arguments: listargs.splice(1) })
|
||||
},
|
||||
description: '- List all channels on the current server.',
|
||||
description: '[<nickname>] - Set/display your nickname.',
|
||||
aliases: ['nickname']
|
||||
},
|
||||
|
||||
@ -587,6 +585,14 @@ let commands = {
|
||||
description: '- Create a new connection.'
|
||||
},
|
||||
|
||||
ping: {
|
||||
execute: function (buffer, handler, command, message, listargs) {
|
||||
let server = irc.handler.getClientByActualServer(buffer.server)
|
||||
buffer.addMessage('Connection latency: ' + server.ping + 'ms', null, 'help')
|
||||
},
|
||||
aliases: ['latency', 'lag']
|
||||
},
|
||||
|
||||
help: {
|
||||
execute: function (buffer, handler, command, message, listargs) {
|
||||
if (!listargs[1]) {
|
||||
@ -677,7 +683,7 @@ class Nicklist {
|
||||
}
|
||||
|
||||
if (irc.config.colorNicklist) {
|
||||
construct += '<span class="nickname" style="color: ' + colorizer.get_random_color(nick.nickname) + ';">' + nick.nickname + '</span>'
|
||||
construct += '<span class="nickname" style="color: ' + colorizer.getRandomColor(nick.nickname) + ';">' + nick.nickname + '</span>'
|
||||
} else {
|
||||
construct += span(nick.nickname, ['nickname'])
|
||||
}
|
||||
@ -700,7 +706,7 @@ class Nicklist {
|
||||
this.appendToList(nick)
|
||||
}
|
||||
|
||||
irc.chat.input_handler.searchNicknames = this.simplifiedNicksList
|
||||
irc.chat.inputHandler.searchNicknames = this.simplifiedNicksList
|
||||
}
|
||||
|
||||
nickAdd (nickname) {
|
||||
@ -965,7 +971,7 @@ class ChatBuffer {
|
||||
clientdom.letterbox.scrollTop = this.lastscroll
|
||||
}
|
||||
|
||||
clientdom.currentNickname.innerHTML = irc.serverData[this.server] ? irc.serverData[this.server].my_nick
|
||||
clientdom.currentNickname.innerHTML = irc.serverData[this.server] ? irc.serverData[this.server].userNick
|
||||
: el('i', 'Disconnected')
|
||||
|
||||
irc.chat.changeTitle('TeemantIRC - ' + this.title)
|
||||
@ -1001,7 +1007,7 @@ class ChatBuffer {
|
||||
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
|
||||
let mynick = irc.serverData[this.server].userNick
|
||||
if ((meta.type === 'privmsg' || meta.type === 'notice' || meta.type === 'action') &&
|
||||
meta.message.toLowerCase().indexOf(mynick.toLowerCase()) !== -1 && meta.sender !== mynick) {
|
||||
addClass(mesgConstr, 'mentioned')
|
||||
@ -1040,7 +1046,7 @@ class ChatBuffer {
|
||||
} else {
|
||||
this.unreadCount += 1
|
||||
if (irc.serverData[this.server]) {
|
||||
let mynick = irc.serverData[this.server].my_nick
|
||||
let mynick = irc.serverData[this.server].userNick
|
||||
if ((type === 'privmsg' || type === 'notice' || type === 'action') &&
|
||||
message.toLowerCase().indexOf(mynick.toLowerCase()) !== -1 && sender !== mynick) {
|
||||
// TODO: notify user of mentioned
|
||||
@ -1056,7 +1062,7 @@ class ChatBuffer {
|
||||
}
|
||||
|
||||
switchOff () {
|
||||
irc.chat.input_handler.searchNicknames = []
|
||||
irc.chat.inputHandler.searchNicknames = []
|
||||
|
||||
let lFactor = clientdom.letterbox.offsetHeight + clientdom.letterbox.scrollTop
|
||||
if (lFactor > clientdom.letterbox.scrollHeight - 100) {
|
||||
@ -1196,16 +1202,14 @@ class Settings extends AppletChatBuffer {
|
||||
|
||||
switchTheme () {
|
||||
if (this.themeSelection !== '') {
|
||||
themes.change_theme(this.themeSelection)
|
||||
themes.changeTheme(this.themeSelection)
|
||||
irc.config.theme = this.themeSelection
|
||||
this.themeSelector.setActiveSelection(this.themeSelection)
|
||||
}
|
||||
}
|
||||
|
||||
saveSpecified () {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout)
|
||||
}
|
||||
if (this.timeout) clearTimeout(this.timeout)
|
||||
|
||||
this.switchTheme()
|
||||
|
||||
@ -1317,29 +1321,26 @@ class ConnectionHandler {
|
||||
|
||||
bindAll (srv, client) {
|
||||
let server = client.data.actualServer
|
||||
client.on('closed', () => {
|
||||
this.clients[srv] = null
|
||||
})
|
||||
|
||||
client.on('pass_to_client', (data) => {
|
||||
client.on('fromServer', (data) => {
|
||||
switch (data.type) {
|
||||
case 'event_join_channel':
|
||||
if (data.user.nickname === irc.serverData[server].my_nick) {
|
||||
case 'joinChannel':
|
||||
if (data.user.nickname === irc.serverData[server].userNick) {
|
||||
irc.chat.createChatBuffer(server, data.channel, 'channel', true)
|
||||
}
|
||||
irc.chat.handleJoin(server, data.user, data.channel)
|
||||
break
|
||||
case 'event_kick_channel':
|
||||
case 'kickedFromChannel':
|
||||
irc.chat.handleLeave(server, data.kickee, data.channel, data.reason, data.user)
|
||||
break
|
||||
case 'event_part_channel':
|
||||
case 'partChannel':
|
||||
irc.chat.handleLeave(server, data.user, data.channel, data.reason)
|
||||
break
|
||||
case 'event_quit':
|
||||
case 'quit':
|
||||
irc.chat.handleQuit(server, data.user, data.reason)
|
||||
break
|
||||
case 'message':
|
||||
if (data.to === irc.serverData[server].my_nick) {
|
||||
if (data.to === irc.serverData[server].userNick) {
|
||||
irc.chat.messageChatBuffer(data.user.nickname, server, { message: data.message, type: data.messageType, from: data.user.nickname })
|
||||
} else if (data.to == null) {
|
||||
let atest = irc.chat.getActiveChatBuffer()
|
||||
@ -1353,31 +1354,31 @@ class ConnectionHandler {
|
||||
irc.chat.messageChatBuffer(data.to, server, { message: data.message, type: data.messageType, from: data.user.nickname })
|
||||
}
|
||||
break
|
||||
case 'channel_nicks':
|
||||
case 'nicks':
|
||||
irc.chat.buildNicklist(data.channel, server, data.nicks)
|
||||
break
|
||||
case 'channel_topic':
|
||||
if (data['topic'] && data['set_by']) {
|
||||
irc.chat.topicChange(data.channel, server, data.topic, data['set_by'])
|
||||
case 'topic':
|
||||
if (data['topic'] && data['setBy']) {
|
||||
irc.chat.topicChange(data.channel, server, data.topic, data['setBy'])
|
||||
} else if (data['topic']) {
|
||||
irc.chat.topicChange(data.channel, server, data.topic, null)
|
||||
} else if (data['set_by']) {
|
||||
} else if (data['setBy']) {
|
||||
irc.chat.messageChatBuffer(data.channel, server, {
|
||||
message: 'Topic set by ' + data.set_by + ' on ' + (new Date(data.time * 1000)),
|
||||
message: 'Topic set by ' + data.setBy + ' on ' + (new Date(data.time * 1000)),
|
||||
type: 'topic',
|
||||
from: null
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'nick_change':
|
||||
case 'nick':
|
||||
irc.chat.nickChange(server, data.nick, data.newNick)
|
||||
break
|
||||
case 'mode_add':
|
||||
case 'mode_del':
|
||||
case 'modeAdd':
|
||||
case 'modeDel':
|
||||
case 'mode':
|
||||
irc.chat.handleMode(server, data)
|
||||
break
|
||||
case 'server_message':
|
||||
case 'serverMessage':
|
||||
if (data['error']) data.messageType = 'error'
|
||||
if (irc.chat.getServerChatBuffer(server) == null) {
|
||||
if (!irc.serverChatQueue[server]) {
|
||||
@ -1389,13 +1390,13 @@ class ConnectionHandler {
|
||||
irc.chat.messageChatBuffer(server, server, { message: data.message, type: data.messageType, from: data.from || null })
|
||||
}
|
||||
break
|
||||
case 'connect_message':
|
||||
case 'connectMessage':
|
||||
irc.auther.authMessage(data.message, data.error)
|
||||
break
|
||||
case 'whoisResponse':
|
||||
whoisMessage(data.whois, irc.chat.getActiveChatBuffer())
|
||||
break
|
||||
case 'listedchan':
|
||||
case 'listedChannel':
|
||||
irc.chat.messageChatBuffer(server, server,
|
||||
{
|
||||
message: span(data.channel, ['channel']) + ' ' +
|
||||
@ -1408,7 +1409,7 @@ class ConnectionHandler {
|
||||
}
|
||||
})
|
||||
|
||||
client.on('closed', () => {
|
||||
client.on('connectionClosed', () => {
|
||||
let serverz = irc.chat.getChatBuffersByServer(server)
|
||||
for (let a in serverz) {
|
||||
let serv = serverz[a]
|
||||
@ -1421,6 +1422,7 @@ class ConnectionHandler {
|
||||
}
|
||||
|
||||
stopWarnings()
|
||||
this.clients[srv] = null
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1495,7 +1497,7 @@ class IRCConnector {
|
||||
clientdom.connector.server.value = value
|
||||
}
|
||||
break
|
||||
case 'server_data':
|
||||
case 'serverinfo':
|
||||
case 'extra':
|
||||
case 'extras':
|
||||
case 'connection':
|
||||
@ -1809,7 +1811,7 @@ class IRCChatWindow {
|
||||
this.buffers = []
|
||||
this.firstServer = true
|
||||
this.currentChatBuffer = null
|
||||
this.input_handler = new InputHandler()
|
||||
this.inputHandler = new InputHandler()
|
||||
|
||||
clientdom.frame.style.display = 'none'
|
||||
|
||||
@ -1908,7 +1910,7 @@ class IRCChatWindow {
|
||||
if (this.firstServer) {
|
||||
clientdom.frame.style.display = 'block'
|
||||
window.onbeforeunload = function (e) {
|
||||
return 'IRC will disconnect.'
|
||||
return 'You will be disconnected from all the channels you\'re currently in.'
|
||||
}
|
||||
}
|
||||
|
||||
@ -1922,8 +1924,8 @@ class IRCChatWindow {
|
||||
modeTranslation: serverinfo.data.supportedModes,
|
||||
supportedPrefixes: prefixes,
|
||||
network: serverinfo.data.network,
|
||||
my_nick: serverinfo.config.nickname,
|
||||
max_channel_length: serverinfo.data.max_channel_length
|
||||
userNick: serverinfo.config.nickname,
|
||||
maxChannelLength: serverinfo.data.maxChannelLength
|
||||
}
|
||||
|
||||
let newServer = new ChatBuffer(serverinfo.data.actualServer, serverinfo.data.actualServer, serverinfo.data.network, 'server')
|
||||
@ -2038,8 +2040,8 @@ class IRCChatWindow {
|
||||
nickChange (server, oldNick, newNick) {
|
||||
let buffers = this.getChatBuffersByServer(server)
|
||||
|
||||
if (irc.serverData[server].my_nick === oldNick) {
|
||||
irc.serverData[server].my_nick = newNick
|
||||
if (irc.serverData[server].userNick === oldNick) {
|
||||
irc.serverData[server].userNick = newNick
|
||||
|
||||
let activeBuf = this.getActiveChatBuffer()
|
||||
|
||||
@ -2092,7 +2094,7 @@ class IRCChatWindow {
|
||||
|
||||
if (!buffer) return
|
||||
|
||||
if (user.nickname === irc.serverData[server].my_nick) {
|
||||
if (user.nickname === irc.serverData[server].userNick) {
|
||||
buffer.setAliveStatus(true)
|
||||
} else {
|
||||
buffer.nicklist.nickAdd(user.nickname)
|
||||
@ -2107,11 +2109,11 @@ class IRCChatWindow {
|
||||
if (!buffer) return
|
||||
|
||||
if (user['nickname']) {
|
||||
if (irc.serverData[server] && user.nickname === irc.serverData[server].my_nick) {
|
||||
if (irc.serverData[server] && user.nickname === irc.serverData[server].userNick) {
|
||||
buffer.setAliveStatus(false)
|
||||
}
|
||||
} else {
|
||||
if (irc.serverData[server] && user === irc.serverData[server].my_nick) {
|
||||
if (irc.serverData[server] && user === irc.serverData[server].userNick) {
|
||||
buffer.setAliveStatus(false)
|
||||
}
|
||||
}
|
||||
@ -2131,7 +2133,7 @@ class IRCChatWindow {
|
||||
|
||||
handleMode (server, data) {
|
||||
let buf = null
|
||||
if (data.target === irc.serverData[server].my_nick) {
|
||||
if (data.target === irc.serverData[server].userNick) {
|
||||
buf = this.getServerChatBuffer(server)
|
||||
} else {
|
||||
buf = this.getChatBufferByServerName(server, data.target)
|
||||
@ -2139,11 +2141,11 @@ class IRCChatWindow {
|
||||
|
||||
if (!buf) return
|
||||
|
||||
if (data.type === 'mode_add') {
|
||||
if (data.type === 'modeAdd') {
|
||||
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') {
|
||||
} else if (data.type === 'modeDel') {
|
||||
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')
|
||||
@ -2189,7 +2191,7 @@ class IRCChatWindow {
|
||||
|
||||
render (buffer) {
|
||||
let activeNow = this.getActiveChatBuffer()
|
||||
this.input_handler.tabCompleteReset()
|
||||
this.inputHandler.tabCompleteReset()
|
||||
|
||||
if (activeNow) {
|
||||
activeNow.switchOff()
|
||||
@ -2256,7 +2258,7 @@ window.onload = function () {
|
||||
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['currentNickname'] = clientdom.frame.querySelector('.userNickname')
|
||||
clientdom['input'] = clientdom.frame.querySelector('.userinput')
|
||||
clientdom['send'] = clientdom.frame.querySelector('.sendbutton')
|
||||
clientdom['chat'] = clientdom.frame.querySelector('.chatarea')
|
||||
@ -2265,12 +2267,11 @@ window.onload = function () {
|
||||
clientdom.settings['open'] = irc.primaryFrame.querySelector('.open_settings')
|
||||
|
||||
irc.settings = new Settings()
|
||||
irc.settings.setInitialValues()
|
||||
irc.handler = new ConnectionHandler()
|
||||
irc.auther = new IRCConnector()
|
||||
irc.chat = new IRCChatWindow()
|
||||
|
||||
irc.settings.setInitialValues()
|
||||
|
||||
parseURL()
|
||||
window.onpopstate = parseURL
|
||||
}
|
||||
|
@ -1,73 +0,0 @@
|
||||
const imgs = ['.png', '.jpg', '.jpeg', '.svg']
|
||||
const embeds = [
|
||||
{
|
||||
match: /youtu.?be\//is,
|
||||
exec: function (pgurl) {
|
||||
let dat = pgurl.match(/(?:be|com)\/([^?&#]+)/i)
|
||||
if (!dat) {
|
||||
dat = pgurl.match('[\\?&]v=([^&#]*)')
|
||||
}
|
||||
|
||||
if (!dat) return null
|
||||
|
||||
return 'https://www.youtube.com/embed/' + dat[1] + '?autoplay=1'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
export function handleUrlElement (elem) {
|
||||
let cover = null
|
||||
let ext = elem.href.split('.')
|
||||
ext = ext[ext.length - 1]
|
||||
|
||||
if (ext && imgs.indexOf(ext) !== -1) {
|
||||
cover = document.createElement('img')
|
||||
cover.src = elem.href
|
||||
}
|
||||
|
||||
if (!cover) {
|
||||
for (let a in embeds) {
|
||||
if (cover) break
|
||||
let fn = embeds[a]
|
||||
if (elem.href.match(fn.match)) {
|
||||
let r = fn.exec(elem.href)
|
||||
if (r) {
|
||||
cover = document.createElement('iframe')
|
||||
cover.src = r
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cover) {
|
||||
cover.className = 'preview'
|
||||
|
||||
let contelem = document.createElement('span')
|
||||
contelem.className = 'preview-box'
|
||||
|
||||
let contbutton = document.createElement('button')
|
||||
contbutton.className = 'preview-btn'
|
||||
contbutton.innerHTML = 'Show Preview'
|
||||
|
||||
elem.parentNode.appendChild(contelem)
|
||||
|
||||
contelem.appendChild(elem)
|
||||
contelem.appendChild(contbutton)
|
||||
|
||||
contbutton.addEventListener('click', function (e) {
|
||||
e.preventDefault()
|
||||
|
||||
if (cover.parentNode) {
|
||||
cover.remove()
|
||||
contbutton.innerHTML = 'Show Preview'
|
||||
} else {
|
||||
contelem.appendChild(cover)
|
||||
contbutton.innerHTML = 'Hide Preview'
|
||||
}
|
||||
}, false)
|
||||
|
||||
return contelem
|
||||
}
|
||||
|
||||
return elem
|
||||
}
|
@ -37,7 +37,7 @@ window.themes = module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
change_theme: function (name) {
|
||||
changeTheme: function (name) {
|
||||
if (name in window.themes.available) {
|
||||
swapSheet(window.themes.available[name].stylesheet)
|
||||
window.irc.config.theme = name
|
||||
|
@ -19,6 +19,14 @@ body
|
||||
margin-top: 0
|
||||
display: block
|
||||
text-align: center
|
||||
position: relative
|
||||
img
|
||||
max-width: 64px
|
||||
top: -50px;
|
||||
position: absolute
|
||||
left: 0
|
||||
right: 0
|
||||
margin: auto
|
||||
|
||||
.grade2
|
||||
margin-bottom: 0
|
||||
|
Reference in New Issue
Block a user