1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/thelounge_ynh.git synced 2024-09-03 20:35:54 +02:00

Upgrade repo

This commit is contained in:
root 2018-05-01 12:25:53 +02:00
parent 4d1a8424c2
commit 50efae14e3
155 changed files with 793 additions and 29770 deletions

View file

@ -1,6 +1,39 @@
The Lounge The Lounge for YunoHost
---------- ====================
<h1 align="center">
<img
width="300"
alt="The Lounge"
src="https://github.com/thelounge/thelounge/raw/master/client/img/logo-vertical-transparent-bg.svg?sanitize=true">
</h1>
[![Linter](https://api.travis-ci.org/Rafi594/thelounge_ynh.svg?branch=master)
[![Integration level](https://dash.yunohost.org/integration/thelounge.svg)](https://ci-apps.yunohost.org/jenkins/job/thelounge%20%28Community%29/lastBuild/consoleFull)
[![Install The Lounge with YunoHost](https://install-app.yunohost.org/install-with-yunohost.png)](https://install-app.yunohost.org/?app=thelounge)
> *This package allow you to install The Lounge quickly and simply on a YunoHost server.
If you don't have YunoHost, please see [here](https://yunohost.org/#/install) to know how to install and enjoy it.*
Overview
--------
The Lounge is a self hosted IRC client.
**Shipped version:** Latest stable version
YunoHost specific features
--------------------------
### Multi-users support
Supported with LDAP.
### Supported architectures
- Tested on x86_64
- Tested on ARM
The Lounge is a web IRC client that you host on your own server.
See https://thelounge.github.io/

41
check_process Normal file
View file

@ -0,0 +1,41 @@
# See here for more informations
# https://github.com/YunoHost/package_check#syntax-check_process-file
# Move this file from check_process.default to check_process when you have filled it.
;; Test complet
; Manifest
domain="domain.tld" (DOMAIN)
path="/path" (PATH)
is_public=1 (PUBLIC|public=1|private=0)
password="pass"
port="9009" (PORT)
; Checks
pkg_linter=1
setup_sub_dir=1
setup_root=1
setup_nourl=0
setup_private=1
setup_public=1
upgrade=1
backup_restore=0
multi_instance=1
incorrect_path=1
port_already_use=1
change_url=0
;;; Levels
Level 1=auto
Level 2=auto
Level 3=auto
# Level 4:
Level 4=1 # https://github.com/Rafi594/thelounge_ynh/blob/master/conf/config.js#L391-L452
# Level 5:
Level 5=auto
Level 6=auto
Level 7=auto
Level 8=0
Level 9=0
Level 10=0
;;; Options
Email=
Notification=none

6
conf/app.src Normal file
View file

@ -0,0 +1,6 @@
SOURCE_URL=https://github.com/thelounge/thelounge/archive/v2.7.1.tar.gz
SOURCE_SUM=e7b66fe1dfef4583668ccf36214184f767fa381076ca21b09479b6e59251f81d
SOURCE_SUM_PRG=sha256sum
ARCH_FORMAT=tar.gz
SOURCE_IN_SUBDIR=true
SOURCE_FILENAME=

View file

@ -8,7 +8,7 @@ module.exports = {
// Set to 'false' to enable users. // Set to 'false' to enable users.
// //
// @type boolean // @type boolean
// @default true // @default false
// //
public: false, public: false,
@ -16,10 +16,12 @@ module.exports = {
// IP address or hostname for the web server to listen on. // IP address or hostname for the web server to listen on.
// Setting this to undefined will listen on all interfaces. // Setting this to undefined will listen on all interfaces.
// //
// For UNIX domain sockets, use unix:/absolute/path/to/file.sock.
//
// @type string // @type string
// @default undefined // @default undefined
// //
host: "127.0.0.1", host: undefined,
// //
// Set the port to listen on. // Set the port to listen on.
@ -27,7 +29,7 @@ module.exports = {
// @type int // @type int
// @default 9000 // @default 9000
// //
port: 9191, port: 9009,
// //
// Set the local IP to bind to for outgoing connections. Leave to undefined // Set the local IP to bind to for outgoing connections. Leave to undefined
@ -49,22 +51,12 @@ module.exports = {
// //
// Set the default theme. // Set the default theme.
// Find out how to add new themes at https://thelounge.github.io/docs/plugins/themes.html
// //
// @type string // @type string
// @default "themes/example.css" // @default "example"
// //
theme: "themes/morning.css", theme: "example",
//
// Autoload users
//
// When this setting is enabled, your 'users/' folder will be monitored. This is useful
// if you want to add/remove users while the server is running.
//
// @type boolean
// @default true
//
autoload: true,
// //
// Prefetch URLs // Prefetch URLs
@ -75,18 +67,35 @@ module.exports = {
// @type boolean // @type boolean
// @default false // @default false
// //
prefetch: true, prefetch: false,
//
// Store and proxy prefetched images and thumbnails.
// This improves security and privacy by not exposing client IP address,
// and always loading images from The Lounge instance and making all assets secure,
// which in result fixes mixed content warnings.
//
// If storage is enabled, The Lounge will fetch and store images and thumbnails
// in the `${THELOUNGE_HOME}/storage` folder.
//
// Images are deleted when they are no longer referenced by any message (controlled by maxHistory),
// and the folder is cleaned up on every The Lounge restart.
//
// @type boolean
// @default false
//
prefetchStorage: false,
// //
// Prefetch URLs Image Preview size limit // Prefetch URLs Image Preview size limit
// //
// If prefetch is enabled, The Lounge will only display content under the maximum size. // If prefetch is enabled, The Lounge will only display content under the maximum size.
// Default value is 512 (in kB) // Specified value is in kilobytes. Default value is 2048 kilobytes.
// //
// @type int // @type int
// @default 512 // @default 2048
// //
prefetchMaxImageSize: 512, prefetchMaxImageSize: 2048,
// //
// Display network // Display network
@ -109,6 +118,17 @@ module.exports = {
// //
lockNetwork: false, lockNetwork: false,
//
// Hex IP
//
// If enabled, clients' username will be set to their IP encoded has hex.
// This is done to share the real user IP address with the server for host masking purposes.
//
// @type boolean
// @default false
//
useHexIp: false,
// //
// WEBIRC support // WEBIRC support
// //
@ -128,6 +148,19 @@ module.exports = {
// @default null // @default null
webirc: null, webirc: null,
//
// Message logging
// Logging is also controlled per user individually (logs variable)
// Leave the array empty to disable all logging globally
//
// text: Text file per network/channel in user folder
// sqlite: Messages are stored in SQLite, this allows them to be reloaded on server restart
//
// @type array
// @default ["sqlite", "text"]
//
messageStorage: ["sqlite", "text"],
// //
// Log settings // Log settings
// //
@ -152,7 +185,7 @@ module.exports = {
// @type string // @type string
// @default "UTC+00:00" // @default "UTC+00:00"
// //
timezone: "UTC+00:00" timezone: "UTC+00:00",
}, },
// //
@ -160,11 +193,11 @@ module.exports = {
// //
// Defines the maximum number of history lines that will be kept in // Defines the maximum number of history lines that will be kept in
// memory per channel/query, in order to reduce the memory usage of // memory per channel/query, in order to reduce the memory usage of
// the server. Negative means unlimited. // the server. Setting this to -1 will keep unlimited amount.
// //
// @type integer // @type integer
// @default -1 // @default 10000
maxHistory: -1, maxHistory: 10000,
// //
// Default values for the 'Connect' form. // Default values for the 'Connect' form.
@ -213,21 +246,35 @@ module.exports = {
// //
tls: true, tls: true,
//
// Enable certificate verification
//
// If true, the server certificate is verified against
// the list of supplied CAs by your node.js installation.
//
// @type boolean
// @default true
//
rejectUnauthorized: true,
// //
// Nick // Nick
// //
// @type string // Percent sign (%) will be replaced into a random number from 0 to 9.
// @default "lounge-user" // For example, Guest%%% will become Guest123 on page load.
// //
nick: "lounge-user", // @type string
// @default "thelounge%%"
//
nick: "thelounge%%",
// //
// Username // Username
// //
// @type string // @type string
// @default "lounge-user" // @default "thelounge"
// //
username: "lounge-user", username: "thelounge",
// //
// Real Name // Real Name
@ -244,7 +291,7 @@ module.exports = {
// @type string // @type string
// @default "#thelounge" // @default "#thelounge"
// //
join: "#thelounge" join: "#thelounge",
}, },
// //
@ -287,9 +334,26 @@ module.exports = {
// @example "sslcert/key-cert.pem" // @example "sslcert/key-cert.pem"
// @default "" // @default ""
// //
certificate: "" certificate: "",
//
// Path to the CA bundle.
//
// @type string
// @example "sslcert/bundle.pem"
// @default ""
//
ca: "",
}, },
//
// Default quit and part message if none is provided.
//
// @type string
// @default "The Lounge - https://thelounge.chat"
//
leaveMessage: "The Lounge - https://thelounge.chat",
// //
// Run The Lounge with identd support. // Run The Lounge with identd support.
// //
@ -311,7 +375,7 @@ module.exports = {
// @type int // @type int
// @default 113 // @default 113
// //
port: 113 port: 113,
}, },
// //
@ -329,6 +393,30 @@ module.exports = {
// @type object // @type object
// @default {} // @default {}
// //
// The authentication process works as follows:
//
// 1. Lounge connects to the LDAP server with its system credentials
// 2. It performs a LDAP search query to find the full DN associated to the
// user requesting to log in.
// 3. Lounge tries to connect a second time, but this time using the user's
// DN and password. Auth is validated iff this connection is successful.
//
// The search query takes a couple of parameters in `searchDN`:
// - a base DN `searchDN/base`. Only children nodes of this DN will be likely
// to be returned;
// - a search scope `searchDN/scope` (see LDAP documentation);
// - the query itself, build as (&(<primaryKey>=<username>) <filter>)
// where <username> is the user name provided in the log in request,
// <primaryKey> is provided by the config and <fitler> is a filtering complement
// also given in the config, to filter for instance only for nodes of type
// inetOrgPerson, or whatever LDAP search allows.
//
// Alternatively, you can specify the `bindDN` parameter. This will make the lounge
// ignore searchDN options and assume that the user DN is always:
// <bindDN>,<primaryKey>=<username>
// where <username> is the user name provided in the log in request, and <bindDN>
// and <primaryKey> are provided by the config.
//
ldap: { ldap: {
// //
// Enable LDAP user authentication // Enable LDAP user authentication
@ -345,8 +433,9 @@ module.exports = {
// //
url: "ldap://127.0.0.1", url: "ldap://127.0.0.1",
// //
// LDAP base dn // LDAP base dn, alternative to searchDN
// //
// @type string // @type string
// //
@ -359,13 +448,6 @@ module.exports = {
// @default "uid" // @default "uid"
// //
primaryKey: "uid" primaryKey: "uid"
},
// Enables extra debugging output. Turn this on if you experience }
// IRC connection issues and want to file a bug report.
//
// @type boolean
// @default false
//
debug: false,
}; };

View file

@ -1,17 +1,14 @@
location LOCATIONTOCHANGE { location ^~ __PATH__/ {
if ($scheme = http) { proxy_pass http://127.0.0.1:__PORT__/;
rewrite ^ https://$server_name$request_uri? permanent; proxy_http_version 1.1;
} proxy_set_header Connection "upgrade";
proxy_pass http://localhost:9191/; proxy_set_header Upgrade $http_upgrade;
proxy_http_version 1.1; proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Connection "upgrade";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header X-Forwarded-For $remote_addr;
# by default nginx times out connections in one minute # by default nginx times out connections in one minute
proxy_read_timeout 1d; proxy_read_timeout 1d;
# Include SSOWAT user panel. # Include SSOWAT user panel.
include conf.d/yunohost_panel.conf.inc; include conf.d/yunohost_panel.conf.inc;
more_clear_input_headers 'Accept-Encoding'; more_clear_input_headers 'Accept-Encoding';
} }

View file

@ -1,11 +1,17 @@
[Unit]
Description=The Lounge IRC client
After=__APP__.service
[Service] [Service]
ExecStart=/usr/bin/node /var/www/thelounge/index.js Type=simple
Restart=always ExecStart=/usr/bin/thelounge start --home /home/yunohost.app/__APP__/
StandardOutput=syslog User=__APP__
StandardError=syslog Group=__APP__
SyslogIdentifier=thelounge Restart=on-failure
User=thelounge RestartSec=5
Environment=NODE_ENV=production StartLimitInterval=60s
StartLimitBurst=3
[Install] [Install]
WantedBy=multi-user.target WantedBy=default.target

View file

@ -1,17 +1,19 @@
{ {
"name": "The Lounge", "name": "The Lounge",
"id": "thelounge", "id": "thelounge",
"packaging_format": 1,
"description": { "description": {
"en": "Web IRC client", "en": "The Lounge is a web IRC client.",
"fr": "Client Web IRC" "fr": "The Lounge est un client web IRC."
}, },
"version": "1.0",
"url": "https://thelounge.chat/",
"license": "free",
"maintainer": { "maintainer": {
"name": "beudbeud", "name": "rafi59",
"email": "beudbeud@beudibox.fr" "email": "rafi59_dev@srvmaison.fr.nf",
"url": "http://blog.rafi59.codelib.re/"
}, },
"multi_instance": "false",
"previous_maintainers": { "previous_maintainers": {
"name": "beudbeud", "name": "beudbeud",
"email": "beudbeud@beudibox.fr" "email": "beudbeud@beudibox.fr"
@ -24,17 +26,16 @@
"nginx", "nginx",
"mysql" "mysql"
], ],
"arguments": { "arguments": {
"install" : [ "install" : [
{ {
"name": "domain", "name": "domain",
"type": "domain", "type": "domain",
"ask": { "ask": {
"en": "Choose a domain for The Lounge", "en": "Choose a domain name for The Lounge",
"fr": "Choisissez un domaine pour The Lounge" "fr": "Choisissez un nom de domaine pour The Lounge"
}, },
"example": "domain.org" "example": "example.com"
}, },
{ {
"name": "path", "name": "path",
@ -48,13 +49,14 @@
}, },
{ {
"name": "is_public", "name": "is_public",
"type": "boolean",
"ask": { "ask": {
"en": "Is it a public instance ?", "en": "Is it a public application?",
"fr": "Est-ce une instance publique ?" "fr": "Est-ce une application publique ?"
}, },
"choices": ["Yes", "No"], "default": true
"default": "No"
} }
] ]
} }
} }

166
scripts/_common.sh Normal file
View file

@ -0,0 +1,166 @@
#!/bin/bash
# INFOS
# n (Node version management) utilise la variable PATH pour stocker le path de la version de node à utiliser.
# C'est ainsi qu'il change de version
# ynh_install_nodejs installe la version de nodejs demandée en argument, avec n
# ynh_use_nodejs active une version de nodejs dans le script courant
# 3 variables sont mises à disposition, et 2 sont stockées dans la config de l'app
# - nodejs_path: Le chemin absolu de cette version de node
# Utilisé pour des appels directs à node.
# - nodejs_version: Simplement le numéro de version de nodejs pour cette application
# - nodejs_use_version: Un alias pour charger une version de node dans le shell courant.
# Utilisé pour démarrer un service ou un script qui utilise node ou npm
# Dans ce cas, c'est $PATH qui contient le chemin de la version de node. Il doit être propagé sur les autres shell si nécessaire.
n_install_dir="/opt/node_n"
node_version_path="/opt/node_n/n/versions/node"
# N_PREFIX est le dossier de n, il doit être chargé dans les variables d'environnement pour n.
export N_PREFIX="$n_install_dir"
ynh_install_n () {
echo "Installation of N - Node.js version management" >&2
# Build an app.src for n
mkdir -p "../conf"
echo "SOURCE_URL=https://github.com/tj/n/archive/v2.1.7.tar.gz
SOURCE_SUM=2ba3c9d4dd3c7e38885b37e02337906a1ee91febe6d5c9159d89a9050f2eea8f" > "../conf/n.src"
# Download and extract n
ynh_setup_source "$n_install_dir/git" n
# Install n
(cd "$n_install_dir/git"
PREFIX=$N_PREFIX make install 2>&1)
}
ynh_use_nodejs () {
nodejs_version=$(ynh_app_setting_get $app nodejs_version)
load_n_path="[[ :$PATH: == *\":$n_install_dir/bin:\"* ]] || PATH=\"$n_install_dir/bin:$PATH\"; N_PREFIX="$n_install_dir""
nodejs_use_version="$n_install_dir/bin/n -q $nodejs_version"
# "Load" a version of node
eval $load_n_path; $nodejs_use_version
# Get the absolute path of this version of node
nodejs_path="$(n bin $nodejs_version)"
# Make an alias for node use
ynh_node_exec="eval $load_n_path; n use $nodejs_version"
}
ynh_install_nodejs () {
# Use n, https://github.com/tj/n to manage the nodejs versions
nodejs_version="6"
local n_install_script="https://git.io/n-install"
# Create $n_install_dir
mkdir -p "$n_install_dir"
# Load n path in PATH
CLEAR_PATH="$n_install_dir/bin:$PATH"
# Remove /usr/local/bin in PATH in case of node has already setup.
PATH=$(echo $CLEAR_PATH | sed 's@/usr/local/bin:@@')
# Move an existing node binary, to avoid to block n.
test -x /usr/bin/node && mv /usr/bin/node /usr/bin/node_n
test -x /usr/bin/npm && mv /usr/bin/npm /usr/bin/npm_n
# If n is not previously setup, install it
if ! test n --version > /dev/null 2>&1
then
ynh_install_n
fi
# Modify the default N_PREFIX in n script
ynh_replace_string "^N_PREFIX=\${N_PREFIX-.*}$" "N_PREFIX=\${N_PREFIX-$N_PREFIX}" "$n_install_dir/bin/n"
# Restore /usr/local/bin in PATH
PATH=$CLEAR_PATH
# And replace the old node binary.
test -x /usr/bin/node_n && mv /usr/bin/node_n /usr/bin/node
test -x /usr/bin/npm_n && mv /usr/bin/npm_n /usr/bin/npm
# Install the requested version of nodejs
n $nodejs_version
# Find the last "real" version for this major version of node.
real_nodejs_version=$(find $node_version_path/$nodejs_version* -maxdepth 0 | sort --version-sort | tail --lines=1)
real_nodejs_version=$(basename $real_nodejs_version)
# Create a symbolic link for this major version. If the file doesn't already exist
if [ ! -e "$node_version_path/$nodejs_version" ]
then
ln --symbolic --force --no-target-directory $node_version_path/$real_nodejs_version $node_version_path/$nodejs_version
fi
# Store the ID of this app and the version of node requested for it
echo "$YNH_APP_ID:$nodejs_version" | tee --append "$n_install_dir/ynh_app_version"
# Store nodejs_version into the config of this app
ynh_app_setting_set $app nodejs_version $nodejs_version
# Build the update script and set the cronjob
ynh_cron_upgrade_node
ynh_use_nodejs
}
ynh_remove_nodejs () {
ynh_use_nodejs
# Remove the line for this app
sed --in-place "/$YNH_APP_ID:$nodejs_version/d" "$n_install_dir/ynh_app_version"
# If none another app uses this version of nodejs, remove it.
if ! grep --quiet "$nodejs_version" "$n_install_dir/ynh_app_version"
then
n rm $nodejs_version
fi
# If none another app uses n, remove n
if [ ! -s "$n_install_dir/ynh_app_version" ]
then
ynh_secure_remove "$n_install_dir"
ynh_secure_remove "/usr/local/n"
sed --in-place "/N_PREFIX/d" /root/.bashrc
fi
}
ynh_cron_upgrade_node () {
# Build the update script
cat > "$n_install_dir/node_update.sh" << EOF
#!/bin/bash
version_path="$node_version_path"
n_install_dir="$n_install_dir"
# Log the date
date
# List all real installed version of node
all_real_version="\$(find \$version_path/* -maxdepth 0 -type d | sed "s@\$version_path/@@g")"
# Keep only the major version number of each line
all_real_version=\$(echo "\$all_real_version" | sed 's/\..*\$//')
# Remove double entries
all_real_version=\$(echo "\$all_real_version" | sort --unique)
# Read each major version
while read version
do
echo "Update of the version \$version"
sudo \$n_install_dir/bin/n \$version
# Find the last "real" version for this major version of node.
real_nodejs_version=\$(find \$version_path/\$version* -maxdepth 0 | sort --version-sort | tail --lines=1)
real_nodejs_version=\$(basename \$real_nodejs_version)
# Update the symbolic link for this version
sudo ln --symbolic --force --no-target-directory \$version_path/\$real_nodejs_version \$version_path/\$version
done <<< "\$(echo "\$all_real_version")"
EOF
chmod +x "$n_install_dir/node_update.sh"
# Build the cronjob
cat > "/etc/cron.daily/node_update" << EOF
#!/bin/bash
$n_install_dir/node_update.sh >> $n_install_dir/node_update.log
EOF
chmod +x "/etc/cron.daily/node_update"
}

1
scripts/_variables Normal file
View file

@ -0,0 +1 @@
nodejs_version=8

View file

@ -1,68 +1,176 @@
#!/bin/bash #!/bin/bash
set -e #=================================================
# GENERIC START
#=================================================
# IMPORT GENERIC HELPERS
#=================================================
# Retrieve arguments source _variables
domain=$1 source _common.sh
path=$2 source /usr/share/yunohost/helpers
is_public=$3
final_path=/var/www/thelounge
# Check domain/path availability #=================================================
sudo yunohost app checkurl $domain$path -a thelounge # MANAGE SCRIPT FAILURE
path=${path%/} #=================================================
# Install dependencies # Exit if an error occurs during the execution of the script
sudo apt-get update ynh_abort_if_errors
sudo apt-get install nodejs -y
# Create user #=================================================
sudo useradd -d $final_path thelounge\ # RETRIEVE ARGUMENTS FROM THE MANIFEST
|| echo "User already created" #=================================================
# Modify the random username domain=$YNH_APP_ARG_DOMAIN
user=$(dd if=/dev/urandom bs=1 count=200 2> /dev/null | tr -c -d 'A-Za-z0-9' | sed -n 's/\(.\{24\}\).*/\1/p') path_url=$YNH_APP_ARG_PATH
sed -i "s@USERTOCHANGE@$user@g" ../conf/user.json is_public=$YNH_APP_ARG_IS_PUBLIC
sed -i "s@PATHTOCHANGE@$path/@g" ../conf/config.js
# HACK: Change the socket.io path in the sources # This is a multi-instance app, meaning it can be installed several times independently
sed -i "s@PATHTOCHANGE@$path@g" ../sources/client/js/lounge.js # The id of the app as stated in the manifest is available as $YNH_APP_ID
# The instance number is available as $YNH_APP_INSTANCE_NUMBER (equals "1", "2", ...)
# The app instance name is available as $YNH_APP_INSTANCE_NAME
# - the first time the app is installed, YNH_APP_INSTANCE_NAME = ynhexample
# - the second time the app is installed, YNH_APP_INSTANCE_NAME = ynhexample__2
# - ynhexample__{N} for the subsequent installations, with N=3,4, ...
# The app instance name is probably what you are interested the most, since this is
# guaranteed to be unique. This is a good unique identifier to define installation path,
# db names, ...
app=$YNH_APP_INSTANCE_NAME
# Copy files to the right place #=================================================
sudo mkdir -p $final_path/.lounge/users # CHECK IF THE APP CAN BE INSTALLED WITH THESE ARGS
sudo cp ../conf/config.js $final_path/.lounge/ #=================================================
sudo cp ../conf/user.json $final_path/.lounge/users/$user.json
sudo cp -a ../sources/* $final_path/
# Set permissions final_path=/var/www/$app
sudo chown -hR thelounge $final_path config_path=/home/yunohost.app/$app/
test ! -e "$final_path" || ynh_die "This path already contains a folder"
# Install dependencies # Normalize the url path syntax
sudo su - thelounge -c "cd $final_path && /usr/bin/npm install" path_url=$(ynh_normalize_url_path $path_url)
# Modify Nginx configuration file and copy it to Nginx conf directory # Check web path availability
if [[ "$path" == "" ]]; then ynh_webpath_available $domain $path_url
sed -i "s@LOCATIONTOCHANGE@/@g" ../conf/nginx.conf # Register (book) web path
else ynh_webpath_register $app $domain $path_url
sed -i "s@LOCATIONTOCHANGE@$path@g" ../conf/nginx.conf
#=================================================
# STORE SETTINGS FROM MANIFEST
#=================================================
ynh_app_setting_set $app domain $domain
ynh_app_setting_set $app path $path_url
ynh_app_setting_set $app is_public $is_public
#=================================================
# STANDARD MODIFICATIONS
#=================================================
# FIND AND OPEN A PORT
#=================================================
# Find a free port
port=$(ynh_find_port 9009)
# Open this port
yunohost firewall allow --no-upnp TCP $port 2>&1
ynh_app_setting_set $app port $port
#=================================================
# INSTALL DEPENDENCIES
#=================================================
ynh_install_nodejs $nodejs_version
#=================================================
# DOWNLOAD, CHECK AND UNPACK SOURCE
#=================================================
ynh_app_setting_set $app final_path $final_path
mkdir -p $final_path
mkdir -p $config_path
# Download, check integrity, uncompress and patch the source from app.src
ynh_setup_source $final_path
#=================================================
# NGINX CONFIGURATION
#=================================================
# Create a dedicated nginx config
ynh_add_nginx_config
#=================================================
# CREATE DEDICATED USER
#=================================================
# Create a system user
ynh_system_user_create $app
#=================================================
# SETUP SYSTEMD
#=================================================
# Create a dedicated systemd config
ynh_add_systemd_config
#=================================================
# INSTALL THE LOUNGE
#=================================================
npm -g install $final_path
#=================================================
# MODIFY A CONFIG FILE
#=================================================
cp -a ../conf/config.js $config_path
#=================================================
# STORE THE CHECKSUM OF THE CONFIG FILE
#=================================================
# Calculate and store the config file checksum into the app settings
ynh_store_file_checksum "$config_path/config.js"
#=================================================
# GENERIC FINALIZATION
#=================================================
# SECURE FILES AND DIRECTORIES
#=================================================
# Set permissions to app files
chown -R $app: $final_path
chown -R $app: $config_path
#=================================================
# SETUP LOGROTATE
#=================================================
# Use logrotate to manage application logfile(s)
ynh_use_logrotate
#=================================================
# ADVERTISE SERVICE IN ADMIN PANEL
#=================================================
#=================================================
# SETUP SSOWAT
#=================================================
if [ $is_public -eq 0 ]
then # Remove the public access
ynh_app_setting_delete $app skipped_uris
fi fi
sudo cp ../conf/nginx.conf /etc/nginx/conf.d/$domain.d/thelounge.conf
# Copy systemd service
sudo cp ../conf/systemd.service /etc/systemd/system/thelounge.service
# Add YunoHost service
sudo yunohost service add thelounge -l /var/log/syslog
# Make app public if necessary # Make app public if necessary
sudo yunohost app setting thelounge is_public -v "$is_public" if [ $is_public -eq 1 ]
if [ "$is_public" = "Yes" ];
then then
sudo yunohost app setting thelounge unprotected_uris -v "/" # unprotected_uris allows SSO credentials to be passed anyway.
ynh_app_setting_set $app unprotected_uris "/"
fi fi
# Reload Nginx, thelounge and regenerate SSOwat conf #=================================================
sudo service nginx reload # RELOAD NGINX
sudo systemctl enable thelounge #=================================================
sudo systemctl start thelounge || sudo systemctl restart thelounge systemctl start thelounge
sudo yunohost app ssowatconf sleep 20
systemctl reload nginx

View file

@ -1,16 +1,88 @@
#!/bin/bash #!/bin/bash
domain=$(sudo yunohost app setting thelounge domain) #=================================================
# GENERIC START
#=================================================
# IMPORT GENERIC HELPERS
#=================================================
sudo systemctl stop thelounge source _common.sh
sudo systemctl disable thelounge source /usr/share/yunohost/helpers
sudo yunohost service remove thelounge
sudo rm -rf /var/www/thelounge #=================================================
sudo rm -f /etc/nginx/conf.d/$domain.d/thelounge.conf # LOAD SETTINGS
sudo rm -f /etc/systemd/system/thelounge.service #=================================================
sudo userdel thelounge app=$YNH_APP_INSTANCE_NAME
sudo service nginx reload domain=$(ynh_app_setting_get $app domain)
sudo yunohost app ssowatconf port=$(ynh_app_setting_get $app port)
final_path=$(ynh_app_setting_get $app final_path)
#=================================================
# STANDARD REMOVE
#=================================================
# STOP AND REMOVE SERVICE
#=================================================
# Remove the dedicated systemd config
ynh_remove_systemd_config
#=================================================
# REMOVE SERVICE FROM ADMIN PANEL
#=================================================
if yunohost service status | grep -q $app
then
echo "Remove $app service"
yunohost service remove $app
fi
#=================================================
# REMOVE APP MAIN DIR
#=================================================
# Remove the app directory securely
ynh_secure_remove "$final_path"
#=================================================
# REMOVE NGINX CONFIGURATION
#=================================================
# Remove the dedicated nginx config
ynh_remove_nginx_config
#=================================================
# REMOVE LOGROTATE CONFIGURATION
#=================================================
# Remove the app-specific logrotate config
ynh_remove_logrotate
#=================================================
# CLOSE A PORT
#=================================================
if yunohost firewall list | grep -q "\- $port$"
then
echo "Close port $port"
yunohost firewall disallow TCP $port 2>&1
fi
#=================================================
# SPECIFIC REMOVE
#=================================================
ynh_use_nodejs
npm uninstall $app
ynh_remove_nodejs
#=================================================
# GENERIC FINALIZATION
#=================================================
# REMOVE DEDICATED USER
#=================================================
# Delete a system user
ynh_system_user_delete $app

View file

@ -1,55 +1,154 @@
#!/bin/bash #!/bin/bash
set -e #=================================================
# GENERIC START
#=================================================
# IMPORT GENERIC HELPERS
#=================================================
# Retrieve arguments source _common.sh
domain=$(sudo yunohost app setting thelounge domain) source /usr/share/yunohost/helpers
path=$(sudo yunohost app setting thelounge path) source _variables
is_public=$(sudo yunohost app setting thelounge is_public)
final_path=/var/www/thelounge
# Remove trailing "/" from the path #=================================================
path=${path%/} # LOAD SETTINGS
#=================================================
# HACK: Change the socket.io path in the sources app=$YNH_APP_INSTANCE_NAME
sed -i "s@PATHTOCHANGE@$path@g" ../sources/client/js/thelounge.js
# Copy files to the right place domain=$(ynh_app_setting_get $app domain)
sudo mkdir -p $final_path/.thelounge/users path_url=$(ynh_app_setting_get $app path)
sed -i "s@PATHTOCHANGE@$path/@g" ../conf/config.js is_public=$(ynh_app_setting_get $app is_public)
sudo cp ../conf/config.js $final_path/.thelounge/ final_path=$(ynh_app_setting_get $app final_path)
sudo cp -a ../sources/* $final_path/ port=$(ynh_app_setting_get $app port)
# Set permissions #=================================================
sudo chown -hR thelounge $final_path # ENSURE DOWNWARD COMPATIBILITY
#=================================================
# Install dependencies # Fix is_public as a boolean value
sudo su -c "cd $final_path && /usr/bin/npm install --production" thelounge if [ "$is_public" = "Yes" ]; then
ynh_app_setting_set $app is_public 1
# Modify Nginx configuration file and copy it to Nginx conf directory is_public=1
if [[ "$path" == "" ]]; then elif [ "$is_public" = "No" ]; then
sed -i "s@LOCATIONTOCHANGE@/@g" ../conf/nginx.conf ynh_app_setting_set $app is_public 0
else is_public=0
sed -i "s@LOCATIONTOCHANGE@$path@g" ../conf/nginx.conf
fi fi
sudo cp ../conf/nginx.conf /etc/nginx/conf.d/$domain.d/thelounge.conf
# Copy systemd service # If final_path doesn't exist, create it
sudo cp ../conf/systemd.service /etc/systemd/system/thelounge.service if [ -z $final_path ]; then
final_path=/var/www/$app
ynh_app_setting_set $app final_path $final_path
fi
# Add YunoHost service # If config_path doesn't exist, create it
sudo yunohost service add thelounge -l /var/log/syslog \ if [ -z $config_path ]; then
|| echo "Service already exist" config_path=/home/yunohost.app/$app
ynh_app_setting_set $app config_path $config_path
fi
#=================================================
# BACKUP BEFORE UPGRADE THEN ACTIVE TRAP
#=================================================
# Backup the current version of the app
ynh_backup_before_upgrade
ynh_clean_setup () {
# restore it if the upgrade fails
ynh_restore_upgradebackup
}
# Exit if an error occurs during the execution of the script
ynh_abort_if_errors
#=================================================
# CHECK THE PATH
#=================================================
# Normalize the URL path syntax
path_url=$(ynh_normalize_url_path $path_url)
#=================================================
# STANDARD UPGRADE STEPS
#=================================================
# DOWNLOAD, CHECK AND UNPACK SOURCE
#=================================================
# Upgrade Nodejs
ynh_install_nodejs
# Download, check integrity, uncompress and patch the source from app.src
ynh_setup_source $final_path
ynh_use_nodejs
npm -g upgrade thelounge $final_path
#=================================================
# NGINX CONFIGURATION
#=================================================
# Create a dedicated nginx config
ynh_add_nginx_config
#=================================================
# CREATE DEDICATED USER
#=================================================
# Create a system user
ynh_system_user_create $app
#=================================================
# SPECIFIC UPGRADE
#=================================================
# ...
#=================================================
# Verify the checksum and backup the file if it's different
ynh_backup_if_checksum_is_different "$final_path/CONFIG_FILE"
# Recalculate and store the config file checksum into the app settings
ynh_store_file_checksum "$config_path/config.js"
#=================================================
# SETUP LOGROTATE
#=================================================
# Use logrotate to manage app-specific logfile(s)
ynh_use_logrotate --non-append
#=================================================
# SETUP SYSTEMD
#=================================================
# Create a dedicated systemd config
ynh_add_systemd_config
#=================================================
# GENERIC FINALIZATION
#=================================================
# SECURE FILES AND DIRECTORIES
#=================================================
# Set right permissions for curl installation
chown -R $app: $final_path
chown -R $app: $config_path
#=================================================
# SETUP SSOWAT
#=================================================
if [ $is_public -eq 0 ]
then # Remove the public access
ynh_app_setting_delete $app skipped_uris
fi
# Make app public if necessary # Make app public if necessary
sudo yunohost app setting thelounge is_public -v "$is_public" if [ $is_public -eq 1 ]
if [ "$is_public" = "Yes" ];
then then
sudo yunohost app setting thelounge unprotected_uris -v "/" # unprotected_uris allows SSO credentials to be passed anyway
ynh_app_setting_set $app unprotected_uris "/"
fi fi
# Reload Nginx, thelounge and regenerate SSOwat conf #=================================================
sudo service nginx reload # RELOAD NGINX
sudo systemctl enable thelounge #=================================================
sudo systemctl restart thelounge || sudo systemctl start thelounge
sudo yunohost app ssowatconf systemctl restart thelounge
systemctl reload nginx

View file

@ -1,3 +0,0 @@
language: node_js
node_js:
- "0.10"

View file

@ -1,808 +0,0 @@
# Change Log
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
<!--
Use the following template for each new release, built on recommendations from http://keepachangelog.com/.
```md
## vX.Y.Z - YYYY-MM-DD
[See the full changelog](https://github.com/thelounge/lounge/compare/vPRE.VIO.US...vX.Y.Z)
OPTIONAL DESCRIPTION, ANNOUNCEMENT, ...
### Added
### Changed
### Deprecated
### Removed
### Fixed
### Security
```
All sections are explained on the link above, they are all optional, and each of them should contain a list of PRs formatted as such:
```md
- Description ([#PR_NUMBER](https://github.com/thelounge/lounge/pull/PR_NUMBER) by [@GITHUB_USERNAME](https://github.com/GITHUB_USERNAME))
```
Don't forget to thank the PR authors in a commit comment, and copy/paste the release content as-is in GitHub releases: https://github.com/thelounge/lounge/releases
-->
## v2.1.0 - 2016-10-17
[See the full changelog](https://github.com/thelounge/lounge/compare/v2.0.1...v2.1.0)
Here comes another release with some nice additions!
While the administrators will notice some bug fixes, most of the changes are client-side: support for `/list`, a slideout menu on mobile, editing one's nick from the UI, wallops message handling.
Enjoy!
### Added
- Implement `/list` ([#258](https://github.com/thelounge/lounge/pull/258) by [@maxpoulin64](https://github.com/maxpoulin64))
- Add touch slideout menu for mobile ([#400](https://github.com/thelounge/lounge/pull/400) by [@maxpoulin64](https://github.com/maxpoulin64))
- Display extra steps when loading the app ([#637](https://github.com/thelounge/lounge/pull/637) by [@xPaw](https://github.com/xPaw))
- Display localized timestamp in title of message times ([#660](https://github.com/thelounge/lounge/pull/660) by [@astorije](https://github.com/astorije))
- Changing nick in the UI ([#551](https://github.com/thelounge/lounge/pull/551) by [@astorije](https://github.com/astorije))
- Add hostmasks in logs when possible ([#670](https://github.com/thelounge/lounge/pull/670) by [@astorije](https://github.com/astorije))
- Display wallops in server window ([#658](https://github.com/thelounge/lounge/pull/658) by [@xPaw](https://github.com/xPaw))
### Changed
- Make use of multi-prefix cap and remove NAMES spam on mode changes ([#632](https://github.com/thelounge/lounge/pull/632) by [@xPaw](https://github.com/xPaw))
- Strict mode for all JS files ([#684](https://github.com/thelounge/lounge/pull/684) by [@astorije](https://github.com/astorije))
- Enforce more ESLint rules ([#681](https://github.com/thelounge/lounge/pull/681) by [@xPaw](https://github.com/xPaw))
- Use CI caches for downloaded files instead of installed ones ([#687](https://github.com/thelounge/lounge/pull/687) by [@astorije](https://github.com/astorije))
- Consolidate version numbers throughout all interfaces ([#592](https://github.com/thelounge/lounge/pull/592) by [@williamboman](https://github.com/williamboman))
- Replace lodash's each/map with ES5 native forEach/map ([#689](https://github.com/thelounge/lounge/pull/689) by [@astorije](https://github.com/astorije))
### Removed
- Remove all font files except WOFF ([#682](https://github.com/thelounge/lounge/pull/682) by [@xPaw](https://github.com/xPaw))
### Fixed
- Themes: Fixed CSS rule selectors for highlight messages ([#652](https://github.com/thelounge/lounge/pull/652) by [@DamonGant](https://github.com/DamonGant))
- Fix unhandled message color in default and Crypto themes ([#653](https://github.com/thelounge/lounge/pull/653) by [@MaxLeiter](https://github.com/MaxLeiter))
- Check if SSL key and certificate files exist ([#673](https://github.com/thelounge/lounge/pull/673) by [@toXel](https://github.com/toXel))
- Fix loading fonts in Microsoft Edge ([#683](https://github.com/thelounge/lounge/pull/683) by [@xPaw](https://github.com/xPaw))
- Fill in prefixLookup on network initialization ([#647](https://github.com/thelounge/lounge/pull/647) by [@nornagon](https://github.com/nornagon))
- Fix nick changes not being properly reported in the logs ([#685](https://github.com/thelounge/lounge/pull/685) by [@astorije](https://github.com/astorije))
- Fix memory and reference shuffling when creating models ([#664](https://github.com/thelounge/lounge/pull/664) by [@xPaw](https://github.com/xPaw))
## v2.0.1 - 2016-09-28
[See the full changelog](https://github.com/thelounge/lounge/compare/v2.0.0...v2.0.1)
This is a minor house-keeping release with mostly two sets of changes.
First, a few bugs were fixed, including one simply preventing The Lounge to run in Safari's private browsing.
Additionally, the developer experience has been made a tiny bit better, with better documentation, lighter dependencies and simpler theme creation.
### Changed
- Add info on README about how to run from source, how to upgrade ([#621](https://github.com/thelounge/lounge/pull/621) by [@astorije](https://github.com/astorije))
- Move uglify invocation into npm scripts and remove grunt ([#628](https://github.com/thelounge/lounge/pull/628) by [@nornagon](https://github.com/nornagon))
- Move Shout theme borders to example theme ([#359](https://github.com/thelounge/lounge/pull/359) by [@xPaw](https://github.com/xPaw))
- Update developer dependencies ([#639](https://github.com/thelounge/lounge/pull/639) by [@xPaw](https://github.com/xPaw))
### Fixed
- Remove -ms-transform and add missed -webkit-transform ([#629](https://github.com/thelounge/lounge/pull/629) by [@xPaw](https://github.com/xPaw))
- Ensure localStorage cannot fail because of quota or Safari private browsing ([#625](https://github.com/thelounge/lounge/pull/625) by [@astorije](https://github.com/astorije))
- Disable pull-to-refresh on mobile that conflicts with scrolling the message list ([#618](https://github.com/thelounge/lounge/pull/618) by [@astorije](https://github.com/astorije))
- Handle stderr when using edit or config command ([#622](https://github.com/thelounge/lounge/pull/622) by [@MaxLeiter](https://github.com/MaxLeiter))
## v2.0.0 - 2016-09-24
[See the full changelog](https://github.com/thelounge/lounge/compare/v1.5.0...v2.0.0)
After more than 5 months in the works, v2.0.0 is finally happening, and it's shipping with lots of new and enhanced features! 🎉
First of all, the backend IRC library is completely different, which was the first step to deciding on a major release.
This change brings many improvements and fixes, including support for auto-reconnection! This also allows us to easily improve our [IRCv3 compliance](http://ircv3.net/software/clients.html#web-clients).
Main changes on the server include support for WEBIRC, oidentd and LDAP. On the client, users will notice a lot of improvements about reporting unseen activity (notifications, markers, etc.), support for custom highlights, a new loading page, an auto-expanding message input, a theme selector, and more.
Administrators should note that the channel list format in user configuration files has changed. The old format is deprecated, but it will be automatically converted when the server starts (support may or may not be removed later). Additionally, The Lounge now only runs on Node v4 and up.
The above is only a small subset of changes. A more detailed list can be found below.
The following list features the most noticeable changes only, and more details can be found on all [v2.0.0 pre-releases](https://www.github.com/thelounge/lounge/releases).
### Added
- Add tooltips on every clickable icons ([#540](https://github.com/thelounge/lounge/pull/540) by [@astorije](https://github.com/astorije))
- Add debug config option for `irc-framework` debug log ([#547](https://github.com/thelounge/lounge/pull/547) by [@maxpoulin64](https://github.com/maxpoulin64))
- Client-side theme selector ([#568](https://github.com/thelounge/lounge/pull/568) by [@astorije](https://github.com/astorije))
- LDAP support ([#477](https://github.com/thelounge/lounge/pull/477) by [@thisisdarshan](https://github.com/thisisdarshan) and [@lindskogen](https://github.com/lindskogen))
- Add custom highlights ([#425](https://github.com/thelounge/lounge/pull/425) by [@YaManicKill](https://github.com/YaManicKill))
- Add auto-grow textarea support ([#379](https://github.com/thelounge/lounge/pull/379) by [@maxpoulin64](https://github.com/maxpoulin64))
- Display unhandled numerics on the client ([#286](https://github.com/thelounge/lounge/pull/286) by [@xPaw](https://github.com/xPaw))
- A proper unread marker ([#332](https://github.com/thelounge/lounge/pull/332) by [@xPaw](https://github.com/xPaw))
- Add information on the About section of the client ([#497](https://github.com/thelounge/lounge/pull/497) by [@astorije](https://github.com/astorije))
- Add a red dot to the mobile menu icon when being notified ([#486](https://github.com/thelounge/lounge/pull/486) by [@astorije](https://github.com/astorije))
- Add "The Lounge" label to the landing pages ([#487](https://github.com/thelounge/lounge/pull/487) by [@astorije](https://github.com/astorije))
- Display network name on Connect page when network is locked and info is hidden ([#488](https://github.com/thelounge/lounge/pull/488) by [@astorije](https://github.com/astorije))
- Display a loading message instead of blank page ([#386](https://github.com/thelounge/lounge/pull/386) by [@xPaw](https://github.com/xPaw))
- Fall back to LOUNGE_HOME env variable when using the CLI ([#402](https://github.com/thelounge/lounge/pull/402) by [@williamboman](https://github.com/williamboman))
- Enable auto reconnection ([#254](https://github.com/thelounge/lounge/pull/254) by [@xPaw](https://github.com/xPaw))
- Add "!" modechar for admin ([#354](https://github.com/thelounge/lounge/pull/354) by [@omnicons](https://github.com/omnicons))
- Add support for oidentd spoofing ([#256](https://github.com/thelounge/lounge/pull/256) by [@maxpoulin64](https://github.com/maxpoulin64))
- Log enabled capabilities ([#272](https://github.com/thelounge/lounge/pull/272) by [@xPaw](https://github.com/xPaw))
- Add support for `~` home folder expansion ([#284](https://github.com/thelounge/lounge/pull/284) by [@maxpoulin64](https://github.com/maxpoulin64))
- Document supported node version ([#280](https://github.com/thelounge/lounge/pull/280) by [@xPaw](https://github.com/xPaw))
- Implement WEBIRC ([#240](https://github.com/thelounge/lounge/pull/240) by [@maxpoulin64](https://github.com/maxpoulin64))
- Add `manifest.json` for nicer mobile experience ([#310](https://github.com/thelounge/lounge/pull/310) by [@xPaw](https://github.com/xPaw))
### Changed
- Cache loaded config and merge it with defaults ([#387](https://github.com/thelounge/lounge/pull/387) by [@xPaw](https://github.com/xPaw))
- Ignore unnecessary files at release time ([#499](https://github.com/thelounge/lounge/pull/499) by [@astorije](https://github.com/astorije))
- Improve font icon management, sizing and sharpness ([#493](https://github.com/thelounge/lounge/pull/493) by [@astorije](https://github.com/astorije))
- Maintain scroll position after loading previous messages ([#496](https://github.com/thelounge/lounge/pull/496) by [@davibe](https://github.com/davibe))
- Perform node version check as soon as possible ([#409](https://github.com/thelounge/lounge/pull/409) by [@xPaw](https://github.com/xPaw))
- Prepend http protocol to www. links in chat ([#410](https://github.com/thelounge/lounge/pull/410) by [@xPaw](https://github.com/xPaw))
- Change default configuration for `host` to allow OS to decide and use both IPv4 and IPv6 ([#432](https://github.com/thelounge/lounge/pull/432) by [@maxpoulin64](https://github.com/maxpoulin64))
- Do not hide timestamps on small viewports ([#376](https://github.com/thelounge/lounge/pull/376) by [@xPaw](https://github.com/xPaw))
- Drop `slate-irc`, switch to `irc-framework` ([#167](https://github.com/thelounge/lounge/pull/167) by [@xPaw](https://github.com/xPaw))
- Improve sticky scroll ([#262](https://github.com/thelounge/lounge/pull/262) by [@xPaw](https://github.com/xPaw))
- Minor wording changes for better clarity ([#305](https://github.com/thelounge/lounge/pull/305) by [@astorije](https://github.com/astorije))
- Improve nick highlights ([#327](https://github.com/thelounge/lounge/pull/327) by [@xPaw](https://github.com/xPaw))
- CSS classes in themes for nick colors ([#325](https://github.com/thelounge/lounge/pull/325) by [@astorije](https://github.com/astorije))
- Replace all concatenated paths with Node's path.join ([#307](https://github.com/thelounge/lounge/pull/307) by [@astorije](https://github.com/astorije))
### Deprecated
- Store channels in array format in user configuration files, deprecating previous format ([#417](https://github.com/thelounge/lounge/pull/417) by [@xPaw](https://github.com/xPaw))
### Removed
- Disable tooltips on mobile to prevent them to stay after clicking ([#612](https://github.com/thelounge/lounge/pull/612) by [@astorije](https://github.com/astorije))
- Remove Docker-related files now that we have a dedicated repository ([#288](https://github.com/thelounge/lounge/pull/288) by [@astorije](https://github.com/astorije))
- Remove JavaScript scrollbar library ([#429](https://github.com/thelounge/lounge/pull/429) by [@xPaw](https://github.com/xPaw))
- Remove navigator.standalone detection ([#427](https://github.com/thelounge/lounge/pull/427) by [@xPaw](https://github.com/xPaw))
- Do not increase font size on highlight in morning theme ([#321](https://github.com/thelounge/lounge/pull/321) by [@xPaw](https://github.com/xPaw))
### Fixed
- Remove font family redundancy, fix missed fonts, remove Open Sans ([#562](https://github.com/thelounge/lounge/pull/562) by [@astorije](https://github.com/astorije))
- Stop propagation when hiding the chat through click/tapping the chat ([#455](https://github.com/thelounge/lounge/pull/455) by [@williamboman](https://github.com/williamboman))
- Improve click handling on users and inline channels ([#366](https://github.com/thelounge/lounge/pull/366) by [@xPaw](https://github.com/xPaw))
- Only load config if it exists ([#461](https://github.com/thelounge/lounge/pull/461) by [@xPaw](https://github.com/xPaw))
- Send user to lobby of deleted chan when parting from active chan ([#489](https://github.com/thelounge/lounge/pull/489) by [@astorije](https://github.com/astorije))
- Set title attribute on topic on initial page load ([#515](https://github.com/thelounge/lounge/pull/515) by [@williamboman](https://github.com/williamboman))
- Save user's channels when they sort the channel list ([#401](https://github.com/thelounge/lounge/pull/401) by [@xPaw](https://github.com/xPaw))
- Turn favicon red on page load if there are highlights ([#344](https://github.com/thelounge/lounge/pull/344) by [@xPaw](https://github.com/xPaw))
- Keep chat stickied to the bottom on resize ([#346](https://github.com/thelounge/lounge/pull/346) by [@maxpoulin64](https://github.com/maxpoulin64))
- Only increase unread counter for whitelisted actions ([#273](https://github.com/thelounge/lounge/pull/273) by [@xPaw](https://github.com/xPaw))
- Parse CTCP replies ([#278](https://github.com/thelounge/lounge/pull/278) by [@xPaw](https://github.com/xPaw))
- Do not count your own messages as unread ([#279](https://github.com/thelounge/lounge/pull/279) by [@xPaw](https://github.com/xPaw))
- Do not display incorrect nick when switching to a non connected network ([#252](https://github.com/thelounge/lounge/pull/252) by [@xPaw](https://github.com/xPaw))
- Keep autocompletion sort whenever user list updates ([#217](https://github.com/thelounge/lounge/pull/217) by [@xPaw](https://github.com/xPaw))
- Save user when parting channels ([#297](https://github.com/thelounge/lounge/pull/297) by [@xPaw](https://github.com/xPaw))
- Add labels in connect window ([#300](https://github.com/thelounge/lounge/pull/300) by [@xPaw](https://github.com/xPaw))
- Add missing `aria-label` on icon buttons ([#303](https://github.com/thelounge/lounge/pull/303) by [@astorije](https://github.com/astorije))
- Fix missing colors in action messages ([#317](https://github.com/thelounge/lounge/pull/317) by [@astorije](https://github.com/astorije))
- Don't falsely report failed write if it didn't fail ([`e6990e0`](https://github.com/thelounge/lounge/commit/e6990e0fc7641d18a5bcbabddca1aacf2254ae52) by [@xPaw](https://github.com/xPaw))
- Fix sending messages starting with a space ([#320](https://github.com/thelounge/lounge/pull/320) by [@maxpoulin64](https://github.com/maxpoulin64))
- Fix notifications in query windows ([#334](https://github.com/thelounge/lounge/pull/334) by [@xPaw](https://github.com/xPaw))
### Security
- Implement user token persistency ([#370](https://github.com/thelounge/lounge/pull/370) by [@xPaw](https://github.com/xPaw))
- Restrict access to the home directory by default ([#205](https://github.com/thelounge/lounge/pull/205) by [@maxpoulin64](https://github.com/maxpoulin64))
- Add security headers to minimize XSS damage ([#292](https://github.com/thelounge/lounge/pull/292) by [@xPaw](https://github.com/xPaw))
- Do not write user configs outside of the app's users directory ([#238](https://github.com/thelounge/lounge/pull/238) by [@williamboman](https://github.com/williamboman))
- Don't check for existing password emptiness ([#315](https://github.com/thelounge/lounge/pull/315) by [@maxpoulin64](https://github.com/maxpoulin64))
## v2.0.0-rc.2 - 2016-09-21 [Pre-release]
[See the full changelog](https://github.com/thelounge/lounge/compare/v2.0.0-rc.1...v2.0.0-rc.2)
This release candidate only fixes a UI bug affecting iOS 8 users, introduced in v2.0.0-pre.5.
### Fixed
- Fix flexboxes to work on iOS 8 ([#626](https://github.com/thelounge/lounge/pull/626) by [@Gilles123](https://github.com/Gilles123))
## v2.0.0-rc.1 - 2016-09-17 [Pre-release]
[See the full changelog](https://github.com/thelounge/lounge/compare/v2.0.0-pre.7...v2.0.0-rc.1)
Prior to this release, users of Safari 10 were not able to access The Lounge anymore, because of a conscious change the WebKit made to their support of CSP, as [explained here](https://webkit.org/blog/6830/a-refined-content-security-policy/). This release addresses this issue.
Another notable change is the removal of tooltips on mobiles, as hovering states on mobile devices breaks in different kind of ways. Hopefully there will be a better solution in the future, or better support across mobiles.
This is also the first release candidate for v2.0.0. This means only critical bug fixes will be merged before releasing v2.0.0.
### Changed
- Explicitly authorize websockets in CSP header ([#597](https://github.com/thelounge/lounge/pull/597) by [@astorije](https://github.com/astorije))
### Removed
- Disable tooltips on mobile to prevent them to stay after clicking ([#612](https://github.com/thelounge/lounge/pull/612) by [@astorije](https://github.com/astorije))
### Fixed
- Fix small input text on Morning and Zenburn ([#601](https://github.com/thelounge/lounge/pull/601) by [@astorije](https://github.com/astorije))
- Fix a left margin appearing on all non-default themes ([#615](https://github.com/thelounge/lounge/pull/615) by [@astorije](https://github.com/astorije))
## v2.0.0-pre.7 - 2016-09-08 [Pre-release]
[See the full changelog](https://github.com/thelounge/lounge/compare/v2.0.0-pre.6...v2.0.0-pre.7)
This prerelease fixes a lot of bugs on both the server and the client. It also adds a theme selector on the client and connection debug log level on the server. Additionally, custom highlights are now case-insensitive.
### Added
- Add tooltips on every clickable icons ([#540](https://github.com/thelounge/lounge/pull/540) by [@astorije](https://github.com/astorije))
- Add debug config option for `irc-framework` debug log ([#547](https://github.com/thelounge/lounge/pull/547) by [@maxpoulin64](https://github.com/maxpoulin64))
- Client-side theme selector ([#568](https://github.com/thelounge/lounge/pull/568) by [@astorije](https://github.com/astorije))
### Changed
- Use our logger instead of `console.log` and `console.error` for LDAP logs ([#552](https://github.com/thelounge/lounge/pull/552) by [@astorije](https://github.com/astorije))
- Make custom highlights case-insensitive ([#565](https://github.com/thelounge/lounge/pull/565) by [@astorije](https://github.com/astorije))
- Bump `request` dependency to 2.74.0 ([#563](https://github.com/thelounge/lounge/pull/563) by [@astorije](https://github.com/astorije))
- Mention wiki in README ([#548](https://github.com/thelounge/lounge/pull/548) by [@MaxLeiter](https://github.com/MaxLeiter))
- Support ES6 features in JS linting outside of client code ([#593](https://github.com/thelounge/lounge/pull/593) by [@williamboman](https://github.com/williamboman))
### Fixed
- Fix token persistency across server refreshes ([#553](https://github.com/thelounge/lounge/pull/553) by [@astorije](https://github.com/astorije))
- Make sure input height is reset when submitting with icon ([#555](https://github.com/thelounge/lounge/pull/555) by [@astorije](https://github.com/astorije))
- Fix webirc and 4-in-6 addresses ([#535](https://github.com/thelounge/lounge/pull/535) by [@maxpoulin64](https://github.com/maxpoulin64))
- Allow long URLs to break onto next line on Chrome ([#576](https://github.com/thelounge/lounge/pull/576) by [@astorije](https://github.com/astorije))
- Make sure users with wrong tokens are locked out instead of crashing the app ([#570](https://github.com/thelounge/lounge/pull/570) by [@astorije](https://github.com/astorije))
- Remove font family redundancy, fix missed fonts, remove Open Sans ([#562](https://github.com/thelounge/lounge/pull/562) by [@astorije](https://github.com/astorije))
- Do not set app orientation in manifest to use user setting at OS level ([#587](https://github.com/thelounge/lounge/pull/587) by [@astorije](https://github.com/astorije))
- Move border-radius from `#main` to `.window elements` to fix radius once and for all ([#572](https://github.com/thelounge/lounge/pull/572) by [@astorije](https://github.com/astorije))
## v2.0.0-pre.6 - 2016-08-10 [Pre-release]
[See the full changelog](https://github.com/thelounge/lounge/compare/v2.0.0-pre.5...v2.0.0-pre.6)
LDAP! That's all there is to be found in this pre-release, but it should please some administrators out there. Big thanks to [@thisisdarshan](https://github.com/thisisdarshan) and [@lindskogen](https://github.com/lindskogen) for sticking with us on this one.
This feature will remain in beta version until the official v2.0.0 release.
### Added
- LDAP support ([#477](https://github.com/thelounge/lounge/pull/477) by [@thisisdarshan](https://github.com/thisisdarshan) and [@lindskogen](https://github.com/lindskogen))
## v2.0.0-pre.5 - 2016-08-07 [Pre-release]
[See the full changelog](https://github.com/thelounge/lounge/compare/v2.0.0-pre.4...v2.0.0-pre.5)
What an exciting release! It's been in the works for more than a month, but the perks are worth the wait.
On the user side, some long-awaited new features can now be found: The Lounge can now track custom highlights, it comes with an auto-expanding text field, and an unread message marker helps keeping track of what happened when you were not watching. A lot of improvements and various bug fixes have been made to the UI.
Note that scrollbar look-and-feel is now delegated to the browser and OS. Use the custom CSS editor and your OS settings to customize them.
Administrators will notice a different format for channels in the user configuration files, and the Docker-related files have been moved to [a dedicated repository](https://github.com/thelounge/docker-lounge). Many bugs have been solved on the server as well.
### Added
- Add custom highlights ([#425](https://github.com/thelounge/lounge/pull/425) by [@YaManicKill](https://github.com/YaManicKill))
- Add auto-grow textarea support ([#379](https://github.com/thelounge/lounge/pull/379) by [@maxpoulin64](https://github.com/maxpoulin64))
- Display unhandled numerics on the client ([#286](https://github.com/thelounge/lounge/pull/286) by [@xPaw](https://github.com/xPaw))
- A proper unread marker ([#332](https://github.com/thelounge/lounge/pull/332) by [@xPaw](https://github.com/xPaw))
- Add information on the About section of the client ([#497](https://github.com/thelounge/lounge/pull/497) by [@astorije](https://github.com/astorije))
- Add a red dot to the mobile menu icon when being notified ([#486](https://github.com/thelounge/lounge/pull/486) by [@astorije](https://github.com/astorije))
- Add "The Lounge" label to the landing pages ([#487](https://github.com/thelounge/lounge/pull/487) by [@astorije](https://github.com/astorije))
- Display network name on Connect page when network is locked and info is hidden ([#488](https://github.com/thelounge/lounge/pull/488) by [@astorije](https://github.com/astorije))
### Changed
- Store channels in array format in user configuration files ([#417](https://github.com/thelounge/lounge/pull/417) by [@xPaw](https://github.com/xPaw))
- Cache loaded config and merge it with defaults ([#387](https://github.com/thelounge/lounge/pull/387) by [@xPaw](https://github.com/xPaw))
- Ignore unnecessary files at release time ([#499](https://github.com/thelounge/lounge/pull/499) by [@astorije](https://github.com/astorije))
- Improve font icon management, sizing and sharpness ([#493](https://github.com/thelounge/lounge/pull/493) by [@astorije](https://github.com/astorije))
- Maintain scroll position after loading previous messages ([#496](https://github.com/thelounge/lounge/pull/496) by [@davibe](https://github.com/davibe))
### Removed
- Remove Docker-related files ([#288](https://github.com/thelounge/lounge/pull/288) by [@astorije](https://github.com/astorije))
- Remove JavaScript scrollbar library ([#429](https://github.com/thelounge/lounge/pull/429) by [@xPaw](https://github.com/xPaw))
### Fixed
- Fix storing the updated authentication token ([#437](https://github.com/thelounge/lounge/pull/437) by [@williamboman](https://github.com/williamboman))
- Update `irc-framework` to 2.3.0 to fix a bug occurring when posting messages starting with a colon ([#449](https://github.com/thelounge/lounge/pull/449) by [@xPaw](https://github.com/xPaw))
- Update `irc-framework` to 2.4.0 to fix a buffering issue ([#451](https://github.com/thelounge/lounge/pull/451) by [@maxpoulin64](https://github.com/maxpoulin64))
- Only auto join actual channels ([#453](https://github.com/thelounge/lounge/pull/453) by [@xPaw](https://github.com/xPaw))
- Only trigger custom highlights for non-self messages and notices ([#454](https://github.com/thelounge/lounge/pull/454) by [@xPaw](https://github.com/xPaw))
- Stop propagation when hiding the chat through click/tapping the chat ([#455](https://github.com/thelounge/lounge/pull/455) by [@williamboman](https://github.com/williamboman))
- Improve click handling on users and inline channels ([#366](https://github.com/thelounge/lounge/pull/366) by [@xPaw](https://github.com/xPaw))
- Update `irc-framework` to 2.5.0 to fix reconnection counter not being reset ([#451](https://github.com/thelounge/lounge/pull/451) by [@xPaw](https://github.com/xPaw))
- Register irc-framework events before connecting ([#458](https://github.com/thelounge/lounge/pull/458) by [@xPaw](https://github.com/xPaw))
- Only load config if it exists ([#461](https://github.com/thelounge/lounge/pull/461) by [@xPaw](https://github.com/xPaw))
- Fix window layout a bit ([#465](https://github.com/thelounge/lounge/pull/465) by [@maxpoulin64](https://github.com/maxpoulin64))
- Fix slight bugs introduced by #379 and #465 ([#467](https://github.com/thelounge/lounge/pull/467) by [@maxpoulin64](https://github.com/maxpoulin64))
- Prevent the app from crashing when no theme is specified ([#474](https://github.com/thelounge/lounge/pull/474) by [@astorije](https://github.com/astorije))
- Fix unread marker disappearing when opacity set to 1 ([#471](https://github.com/thelounge/lounge/pull/471) by [@astorije](https://github.com/astorije))
- Fix breaking layout when switching portrait/landscape modes ([#478](https://github.com/thelounge/lounge/pull/478) by [@astorije](https://github.com/astorije))
- Fix chat not being "stickied" to the bottom when joining channel ([#484](https://github.com/thelounge/lounge/pull/484) by [@williamboman](https://github.com/williamboman))
- Add self info to TOGGLE messages to prevent unread marker to render for oneself ([#473](https://github.com/thelounge/lounge/pull/473) by [@astorije](https://github.com/astorije))
- Send user to lobby of deleted chan when parting from active chan ([#489](https://github.com/thelounge/lounge/pull/489) by [@astorije](https://github.com/astorije))
- Use `min-height` of textarea when computing auto-resize after deleting a char ([#504](https://github.com/thelounge/lounge/pull/504) by [@astorije](https://github.com/astorije))
- Set title attribute on topic on initial page load ([#515](https://github.com/thelounge/lounge/pull/515) by [@williamboman](https://github.com/williamboman))
- Make sure git commit check for the About section would not send stderr to the console ([#516](https://github.com/thelounge/lounge/pull/516) by [@astorije](https://github.com/astorije))
- Create a single function to render networks to reduce code duplication ([#445](https://github.com/thelounge/lounge/pull/445) by [@xPaw](https://github.com/xPaw))
- Reset the unread marker on channel change ([#527](https://github.com/thelounge/lounge/pull/527) by [@maxpoulin64](https://github.com/maxpoulin64))
- Fix accidentally removed border-radius ([#537](https://github.com/thelounge/lounge/pull/537) by [@astorije](https://github.com/astorije))
- Fix font size in themes for new textarea ([#536](https://github.com/thelounge/lounge/pull/536) by [@maxpoulin64](https://github.com/maxpoulin64))
- Restore padding and height of message input pre-textarea era ([#539](https://github.com/thelounge/lounge/pull/539) by [@astorije](https://github.com/astorije))
- Prevent Ctrl-Tab from triggering tab completion ([#541](https://github.com/thelounge/lounge/pull/541) by [@hho](https://github.com/hho))
## v2.0.0-pre.4 - 2016-06-29 [Pre-release]
[See the full changelog](https://github.com/thelounge/lounge/compare/v2.0.0-pre.3...v2.0.0-pre.4)
This pre-release adds a loading window, helpful on slow connections.
It also implements token persistency, ensuring users do not have to authenticate at every server restart. As a side effect, security is improved by forcing logging out users on all devices when changing their password.
All generated URLs are now HTTP by default, except when explicitly set to HTTPS. For example, `www.example.com` will link to `http://www.example.com`. One needs to share `https://www.example.com` to point others to a HTTPS location.
As a few users have been having issues when running The Lounge with a non-supported Node.js version, we now detect it early to avoid cryptic errors.
This pre-release also adds minor UI improvements, and fixes from the previous version.
While The Lounge still needs a lot of efforts to be fully accessible, this version slightly improves accessibility on clickable nickname.
Internally, we now keep track of our code coverage, which we do not enforce strictly at the moment.
### Added
- Add code coverage ([#408](https://github.com/thelounge/lounge/pull/408) by [@astorije](https://github.com/astorije))
- Display a loading message instead of blank page ([#386](https://github.com/thelounge/lounge/pull/386) by [@xPaw](https://github.com/xPaw))
### Changed
- Perform node version check as soon as possible ([#409](https://github.com/thelounge/lounge/pull/409) by [@xPaw](https://github.com/xPaw))
- Prepend http protocol to www. links in chat ([#410](https://github.com/thelounge/lounge/pull/410) by [@xPaw](https://github.com/xPaw))
- Use tabs when saving user configs ([#418](https://github.com/thelounge/lounge/pull/418) by [@xPaw](https://github.com/xPaw))
- Do not display the sidebar on sign-in page ([#420](https://github.com/thelounge/lounge/pull/420) by [@astorije](https://github.com/astorije))
- Make style of loading page similar to other pages ([#423](https://github.com/thelounge/lounge/pull/423) by [@astorije](https://github.com/astorije))
- Change default configuration for `host` to allow OS to decide and use both IPv4 and IPv6 ([#432](https://github.com/thelounge/lounge/pull/432) by [@maxpoulin64](https://github.com/maxpoulin64))
- Change nicks from links to spans everywhere ([#428](https://github.com/thelounge/lounge/pull/428) by [@xPaw](https://github.com/xPaw))
- Increase join delay at connection to 1000ms ([#434](https://github.com/thelounge/lounge/pull/434) by [@williamboman](https://github.com/williamboman))
### Removed
- Remove navigator.standalone detection ([#427](https://github.com/thelounge/lounge/pull/427) by [@xPaw](https://github.com/xPaw))
### Fixed
- Do not lose authentication token when the connection gets lost ([#369](https://github.com/thelounge/lounge/pull/369) by [@xPaw](https://github.com/xPaw))
- Fix crash in public mode ([#413](https://github.com/thelounge/lounge/pull/413) by [@maxpoulin64](https://github.com/maxpoulin64))
- Do not print user loaded message in public mode ([#415](https://github.com/thelounge/lounge/pull/415) by [@xPaw](https://github.com/xPaw))
- Fix focusing input when clicking chat container on the client ([#364](https://github.com/thelounge/lounge/pull/364) by [@williamboman](https://github.com/williamboman))
- Fix channel join regression and fix possibly joining parted channels ([#411](https://github.com/thelounge/lounge/pull/411) by [@xPaw](https://github.com/xPaw))
### Security
- Implement user token persistency ([#370](https://github.com/thelounge/lounge/pull/370) by [@xPaw](https://github.com/xPaw))
## v2.0.0-pre.3 - 2016-06-15 [Pre-release]
[See the full changelog](https://github.com/thelounge/lounge/compare/v2.0.0-pre.2...v2.0.0-pre.3)
This release introduces a few internal changes as well as two noticeable ones. When using the CLI, the home path can now be set with the `LOUNGE_HOME` environment variable, to avoid repeating `--home` over and over. On the client, sorting channels will now be saved in the user configuration.
### Added
- Fall back to LOUNGE_HOME env variable when using the CLI ([#402](https://github.com/thelounge/lounge/pull/402) by [@williamboman](https://github.com/williamboman))
### Changed
- Rename package variable to pkg, as "package" is reserved. ([#399](https://github.com/thelounge/lounge/pull/399) by [@hogofwar](https://github.com/hogofwar))
- Capitalise constructor Oidentd ([#396](https://github.com/thelounge/lounge/pull/396) by [@hogofwar](https://github.com/hogofwar))
- Bump stylelint and update Travis CI configuration to include OSX builds and package caching ([#403](https://github.com/thelounge/lounge/pull/403) by [@xPaw](https://github.com/xPaw))
### Removed
- Supersede `mkdirp` with `fs-extra` ([#390](https://github.com/thelounge/lounge/pull/390) by [@hogofwar](https://github.com/hogofwar))
- Remove redundant variables ([#397](https://github.com/thelounge/lounge/pull/397) by [@hogofwar](https://github.com/hogofwar))
### Fixed
- Save user's channels when they sort the channel list ([#401](https://github.com/thelounge/lounge/pull/401) by [@xPaw](https://github.com/xPaw))
- Fix description of `host` and `bind` config options ([#378](https://github.com/thelounge/lounge/pull/378) by [@maxpoulin64](https://github.com/maxpoulin64))
## v2.0.0-pre.2 - 2016-06-09 [Pre-release]
[See the full changelog](https://github.com/thelounge/lounge/compare/v2.0.0-pre.1...v2.0.0-pre.2)
This pre-release adds a very, very long-awaited feature: auto-reconnection! It also extends our support of ident with oidentd, shows timestamps on small screens and fix bugs around notifications and sticky scroll.
### Added
- Enable auto reconnection ([#254](https://github.com/thelounge/lounge/pull/254) by [@xPaw](https://github.com/xPaw))
- Add "!" modechar for admin ([#354](https://github.com/thelounge/lounge/pull/354) by [@omnicons](https://github.com/omnicons))
- Add CI tool for Windows builds ([#367](https://github.com/thelounge/lounge/pull/367) by [@astorije](https://github.com/astorije))
- Add support for oidentd spoofing ([#256](https://github.com/thelounge/lounge/pull/256) by [@maxpoulin64](https://github.com/maxpoulin64))
### Changed
- Update Font Awesome to v4.6.3 ([#355](https://github.com/thelounge/lounge/pull/355) by [@MaxLeiter](https://github.com/MaxLeiter))
- Do not hide timestamps on small viewports ([#376](https://github.com/thelounge/lounge/pull/376) by [@xPaw](https://github.com/xPaw))
- Fetch Font Awesome from npm instead of embedded in repo ([#361](https://github.com/thelounge/lounge/pull/361) by [@astorije](https://github.com/astorije))
- Cache npm modules on appveyor ([#381](https://github.com/thelounge/lounge/pull/381) by [@xPaw](https://github.com/xPaw))
- Update eslint and enforce key-spacing ([#384](https://github.com/thelounge/lounge/pull/384) by [@xPaw](https://github.com/xPaw))
- Use `npm-run-all` in npm scripts for testing and linting ([#375](https://github.com/thelounge/lounge/pull/375) by [@williamboman](https://github.com/williamboman))
- Upload test results on appveyor builds ([#382](https://github.com/thelounge/lounge/pull/382) by [@xPaw](https://github.com/xPaw))
### Fixed
- Turn favicon red on page load if there are highlights ([#344](https://github.com/thelounge/lounge/pull/344) by [@xPaw](https://github.com/xPaw))
- Do not send completely empty messages ([#345](https://github.com/thelounge/lounge/pull/345) by [@maxpoulin64](https://github.com/maxpoulin64))
- Make sure npm test script gets run on AppVeyor ([#372](https://github.com/thelounge/lounge/pull/372) by [@astorije](https://github.com/astorije))
- Keep chat stickied to the bottom on resize ([#346](https://github.com/thelounge/lounge/pull/346) by [@maxpoulin64](https://github.com/maxpoulin64))
## v2.0.0-pre.1 - 2016-05-22 [Pre-release]
[See the full changelog](https://github.com/thelounge/lounge/compare/v1.5.0...v2.0.0-pre.1)
This is a pre-release to allow early adopters to use The Lounge with [`irc-framework`](https://github.com/kiwiirc/irc-framework) as our underlying IRC library instead of [`slate`](https://github.com/slate/slate-irc). This change itself solves a lot of issues and adds many features, most of them [listed here](https://github.com/thelounge/lounge/pull/167#issue-139286868): IRCv3 compliance, user feedback improvement, etc.
It also adds WEBIRC support, a better server logging capability, a web app manifest, improves the sticky scroll, and fixes a ton of bugs.
### Added
- Log enabled capabilities ([#272](https://github.com/thelounge/lounge/pull/272) by [@xPaw](https://github.com/xPaw))
- Add global logging helper ([#257](https://github.com/thelounge/lounge/pull/257) by [@xPaw](https://github.com/xPaw))
- Add support for `~` home folder expansion ([#284](https://github.com/thelounge/lounge/pull/284) by [@maxpoulin64](https://github.com/maxpoulin64))
- Document supported node version ([#280](https://github.com/thelounge/lounge/pull/280) by [@xPaw](https://github.com/xPaw))
- Add support for echo-message and znc.in/self-message caps ([#270](https://github.com/thelounge/lounge/pull/270) by [@xPaw](https://github.com/xPaw))
- Implement WEBIRC ([#240](https://github.com/thelounge/lounge/pull/240) by [@maxpoulin64](https://github.com/maxpoulin64))
- Add `manifest.json` for nicer mobile experience ([#310](https://github.com/thelounge/lounge/pull/310) by [@xPaw](https://github.com/xPaw))
### Changed
- Drop `slate-irc`, switch to `irc-framework` ([#167](https://github.com/thelounge/lounge/pull/167) by [@xPaw](https://github.com/xPaw))
- Create a single helper function to write messages ([#266](https://github.com/thelounge/lounge/pull/266) by [@xPaw](https://github.com/xPaw))
- Update dependencies ([#281](https://github.com/thelounge/lounge/pull/281) by [@xPaw](https://github.com/xPaw))
- Improve sticky scroll ([#262](https://github.com/thelounge/lounge/pull/262) by [@xPaw](https://github.com/xPaw))
- Change license link to point at our license file ([#290](https://github.com/thelounge/lounge/pull/290) by [@xPaw](https://github.com/xPaw))
- Stricter eslint rule for curly brackets ([#291](https://github.com/thelounge/lounge/pull/291) by [@xPaw](https://github.com/xPaw))
- Bump patch version of lodash to 4.11.2 ([#306](https://github.com/thelounge/lounge/pull/306) by [@astorije](https://github.com/astorije))
- Minor wording changes for better clarity ([#305](https://github.com/thelounge/lounge/pull/305) by [@astorije](https://github.com/astorije))
- Improve tests execution ([#260](https://github.com/thelounge/lounge/pull/260) by [@maxpoulin64](https://github.com/maxpoulin64))
- Update irc-framework ([#324](https://github.com/thelounge/lounge/pull/324) by [@xPaw](https://github.com/xPaw))
- Do not ignore our handlebars plugins in ESLint ([#329](https://github.com/thelounge/lounge/pull/329) by [@xPaw](https://github.com/xPaw))
- Improve nick highlights ([#327](https://github.com/thelounge/lounge/pull/327) by [@xPaw](https://github.com/xPaw))
- CSS classes in themes for nick colors ([#325](https://github.com/thelounge/lounge/pull/325) by [@astorije](https://github.com/astorije))
- Replace all concatenated paths with Node's path.join ([#307](https://github.com/thelounge/lounge/pull/307) by [@astorije](https://github.com/astorije))
### Removed
- Do not increase font size on highlight in morning theme ([#321](https://github.com/thelounge/lounge/pull/321) by [@xPaw](https://github.com/xPaw))
### Fixed
- Only increase unread counter for whitelisted actions ([#273](https://github.com/thelounge/lounge/pull/273) by [@xPaw](https://github.com/xPaw))
- Parse CTCP replies ([#278](https://github.com/thelounge/lounge/pull/278) by [@xPaw](https://github.com/xPaw))
- Do not count your own messages as unread ([#279](https://github.com/thelounge/lounge/pull/279) by [@xPaw](https://github.com/xPaw))
- Use lowercase global to avoid a deprecation warning in Node.js 6 ([`d9a0dd9`](https://github.com/thelounge/lounge/commit/d9a0dd9406e8fb22d7a5ee1ed4ed7aa8e5f0fa01) by [@xPaw](https://github.com/xPaw))
- Do not display incorrect nick when switching to a non connected network ([#252](https://github.com/thelounge/lounge/pull/252) by [@xPaw](https://github.com/xPaw))
- Keep autocompletion sort whenever user list updates ([#217](https://github.com/thelounge/lounge/pull/217) by [@xPaw](https://github.com/xPaw))
- Make sure app does not crash when webirc is not defined in the configuration ([#294](https://github.com/thelounge/lounge/pull/294) by [@astorije](https://github.com/astorije))
- Save user when parting channels ([#297](https://github.com/thelounge/lounge/pull/297) by [@xPaw](https://github.com/xPaw))
- Add labels in connect window ([#300](https://github.com/thelounge/lounge/pull/300) by [@xPaw](https://github.com/xPaw))
- Add missing `aria-label` on icon buttons ([#303](https://github.com/thelounge/lounge/pull/303) by [@astorije](https://github.com/astorije))
- Fix unread counter not being formatted on page load ([#308](https://github.com/thelounge/lounge/pull/308) by [@xPaw](https://github.com/xPaw))
- Fix wrong CSS for disabled colored nicknames on themes ([#318](https://github.com/thelounge/lounge/pull/318) by [@astorije](https://github.com/astorije))
- Fix missing colors in action messages ([#317](https://github.com/thelounge/lounge/pull/317) by [@astorije](https://github.com/astorije))
- Don't falsely report failed write if it didn't fail ([`e6990e0`](https://github.com/thelounge/lounge/commit/e6990e0fc7641d18a5bcbabddca1aacf2254ae52) by [@xPaw](https://github.com/xPaw))
- Fix sending messages starting with a space ([#320](https://github.com/thelounge/lounge/pull/320) by [@maxpoulin64](https://github.com/maxpoulin64))
- Fix notifications in query windows ([#334](https://github.com/thelounge/lounge/pull/334) by [@xPaw](https://github.com/xPaw))
### Security
- Restrict access to the home directory by default ([#205](https://github.com/thelounge/lounge/pull/205) by [@maxpoulin64](https://github.com/maxpoulin64))
- Update demo link to HTTPS ([#302](https://github.com/thelounge/lounge/pull/302) by [@MaxLeiter](https://github.com/MaxLeiter))
- Add security headers to minimize XSS damage ([#292](https://github.com/thelounge/lounge/pull/292) by [@xPaw](https://github.com/xPaw))
- Do not write user configs outside of the app's users directory ([#238](https://github.com/thelounge/lounge/pull/238) by [@williamboman](https://github.com/williamboman))
- Don't check for existing password emptiness ([#315](https://github.com/thelounge/lounge/pull/315) by [@maxpoulin64](https://github.com/maxpoulin64))
## v1.5.0 - 2016-04-13
[See the full changelog](https://github.com/thelounge/lounge/compare/v1.4.3...v1.5.0)
With this release, administrators can now define a maximum size for channel history.
While this is not optimal nor the definitive solution, it aims at reducing stability issues where The Lounge would crash after filling up the server's memory.
Other changes noticeable by users include removing custom print styles and preventing sequences of white spaces to collapse into one.
### Added
- Add config option to limit in-memory history size ([#243](https://github.com/thelounge/lounge/pull/243) by [@maxpoulin64](https://github.com/maxpoulin64))
### Changed
- Do not parse link titles for IRC formatting ([#245](https://github.com/thelounge/lounge/pull/245) by [@xPaw](https://github.com/xPaw))
- Display multiple white spaces properly ([#239](https://github.com/thelounge/lounge/pull/239) by [@maxpoulin64](https://github.com/maxpoulin64))
- Reword password prompt of `add` and `reset` CLI commands ([#230](https://github.com/thelounge/lounge/pull/230) by [@williamboman](https://github.com/williamboman))
### Removed
- Remove print styles ([#228](https://github.com/thelounge/lounge/pull/228) by [@xPaw](https://github.com/xPaw))
## v1.4.3 - 2016-04-02
[See the full changelog](https://github.com/thelounge/lounge/compare/v1.4.2...v1.4.3)
This PR fixes a bug introduced in v1.3.0 which prevents deleting disconnected networks from users' configuration files.
### Fixed
- Fix not being able to remove networks from user config ([#233](https://github.com/thelounge/lounge/pull/233) by [@xPaw](https://github.com/xPaw))
## v1.4.2 - 2016-03-31
[See the full changelog](https://github.com/thelounge/lounge/compare/v1.4.1...v1.4.2)
This PR fixes a bug introduced in v1.4.1 causing timestamps to use most of the screen.
### Fixed
- Hide options will now remove the entire row ([#227](https://github.com/thelounge/lounge/pull/227) by [@xPaw](https://github.com/xPaw))
## v1.4.1 - 2016-03-28
[See the full changelog](https://github.com/thelounge/lounge/compare/v1.4.0...v1.4.1)
As of this release, running `/query nick` will simply open a chat window with user `nick`, instead of calling `whois` for this user.
### Changed
- Remove `join`, `nick` and `whois` inputs, they are cleanly handled by the server ([#208](https://github.com/thelounge/lounge/pull/208) by [@xPaw](https://github.com/xPaw))
- Add a `/query` command that simply opens a query window ([#218](https://github.com/thelounge/lounge/pull/218) by [@xPaw](https://github.com/xPaw))
- Disallow `/query` on non-nicks ([#221](https://github.com/thelounge/lounge/pull/221) by [@astorije](https://github.com/astorije))
### Fixed
- Fix message and topic text wrapping ([#215](https://github.com/thelounge/lounge/pull/215) by [@xPaw](https://github.com/xPaw))
- Fix `/part` command ([#222](https://github.com/thelounge/lounge/pull/222) by [@maxpoulin64](https://github.com/maxpoulin64))
- Harden URL fetcher and don't crash on non-ASCII urls ([#219](https://github.com/thelounge/lounge/pull/219) by [@xPaw](https://github.com/xPaw))
## v1.4.0 - 2016-03-20
[See the full changelog](https://github.com/thelounge/lounge/compare/v1.3.1...v1.4.0)
Note that this release will reset users' notification settings to their defaults. This unfortunate side effect is the consequence of an improvement of how this setting is handled in the application.
### Added
- Add context menu when right-clicking on a sidebar item ([#9](https://github.com/thelounge/lounge/pull/9) by [@xPaw](https://github.com/xPaw))
- Add tests for the `Chan#sortUsers` method ([#197](https://github.com/thelounge/lounge/pull/197) by [@astorije](https://github.com/astorije))
- Add a very basic test for `Network#export` ([#198](https://github.com/thelounge/lounge/pull/198) by [@astorije](https://github.com/astorije))
- Link to the demo from the IRC channel badge on the README ([#203](https://github.com/thelounge/lounge/pull/203) by [@Henni](https://github.com/Henni))
- Add support for HTTP/2 ([#174](https://github.com/thelounge/lounge/pull/174) by [@xPaw](https://github.com/xPaw))
- Support port in `/connect` command ([#210](https://github.com/thelounge/lounge/pull/210) by [@xPaw](https://github.com/xPaw))
### Changed
- Update Handlebars to 4.0.5 ([#140](https://github.com/thelounge/lounge/pull/140) by [@xPaw](https://github.com/xPaw))
- Update Socket.IO to 1.4.5 and use client library provided by the dependency ([#142](https://github.com/thelounge/lounge/pull/142) by [@xPaw](https://github.com/xPaw))
- Update ESLint to 2.3.0 and add stricter rules ([#171](https://github.com/thelounge/lounge/pull/171) by [@xPaw](https://github.com/xPaw))
- Mute color of the topic actions ([#151](https://github.com/thelounge/lounge/pull/151) by [@astorije](https://github.com/astorije))
- Rename "badge" setting and rely on browser choice for desktop notifications ([#28](https://github.com/thelounge/lounge/pull/28) by [@lpoujol](https://github.com/lpoujol))
- Invoke `handlebars` outside of `grunt` and generate a sourcemap ([#144](https://github.com/thelounge/lounge/pull/144) by [@xPaw](https://github.com/xPaw))
- Make `whois` a client action template and improve its output ([#161](https://github.com/thelounge/lounge/pull/161) by [@xPaw](https://github.com/xPaw))
- Handle commands in a better way and send unknown commands to the IRC server ([#154](https://github.com/thelounge/lounge/pull/154) by [@xPaw](https://github.com/xPaw))
- Switch the Send button to a paper plane icon ([#182](https://github.com/thelounge/lounge/pull/182) by [@astorije](https://github.com/astorije))
- Keep track of highlights when user is offline ([#190](https://github.com/thelounge/lounge/pull/190) by [@xPaw](https://github.com/xPaw))
- Load input plugins at startup and call them directly when a command is received ([#191](https://github.com/thelounge/lounge/pull/191) by [@astorije](https://github.com/astorije))
- Make defaults for socket.io transports consistent to use polling before websocket ([#202](https://github.com/thelounge/lounge/pull/202) by [@xPaw](https://github.com/xPaw))
- Update all server dependencies to current stable versions ([#200](https://github.com/thelounge/lounge/pull/200) by [@xPaw](https://github.com/xPaw))
- Update configuration file to reflect HTTP/2 support addition ([#206](https://github.com/thelounge/lounge/pull/206) by [@astorije](https://github.com/astorije))
- Change close button behavior and add a dropdown context menu ([#184](https://github.com/thelounge/lounge/pull/184) by [@xPaw](https://github.com/xPaw))
- Minor enhancements of the context menu UI ([#212](https://github.com/thelounge/lounge/pull/212) by [@astorije](https://github.com/astorije))
### Removed
- Remove `string.contains` library ([#163](https://github.com/thelounge/lounge/pull/163) by [@xPaw](https://github.com/xPaw))
- Remove Moment.js library from the client ([#183](https://github.com/thelounge/lounge/pull/183) by [@xPaw](https://github.com/xPaw))
- Disabled emails from Travis CI on successful builds ([#172](https://github.com/thelounge/lounge/pull/172) by [@xPaw](https://github.com/xPaw))
- Remove unnecessary operation when sorting users ([#193](https://github.com/thelounge/lounge/pull/193) by [@astorije](https://github.com/astorije))
### Fixed
- Make sure self messages are never highlighted and improve highlight lookup ([#157](https://github.com/thelounge/lounge/pull/157) by [@astorije](https://github.com/astorije))
- Fix Send button style on Zenburn and Morning themes, introduced by this release ([#187](https://github.com/thelounge/lounge/pull/187) by [@astorije](https://github.com/astorije))
- Make sure all close buttons in the sidebar have same weight ([#192](https://github.com/thelounge/lounge/pull/192) by [@astorije](https://github.com/astorije))
- Disallow parting from lobbies ([#209](https://github.com/thelounge/lounge/pull/209) by [@xPaw](https://github.com/xPaw))
## v1.3.1 - 2016-03-05
[See the full changelog](https://github.com/thelounge/lounge/compare/v1.3.0...v1.3.1)
### Removed
- Remove attempts to set file modes ([#117](https://github.com/thelounge/lounge/pull/117) by [@maxpoulin64](https://github.com/maxpoulin64))
### Fixed
- Correctly handle inline channels in messages ([#128](https://github.com/thelounge/lounge/pull/128) by [@xPaw](https://github.com/xPaw))
- Fix crash, introduced by this release ([#143](https://github.com/thelounge/lounge/pull/143) by [@xPaw](https://github.com/xPaw))
- Fix highlighted actions and mute colors of some of the actions ([#47](https://github.com/thelounge/lounge/pull/47) by [@xPaw](https://github.com/xPaw))
- Fix stripping multiple colors from notifications ([#145](https://github.com/thelounge/lounge/pull/145) by [@xPaw](https://github.com/xPaw))
- Correctly display channel name in notifications ([#148](https://github.com/thelounge/lounge/pull/148) by [@xPaw](https://github.com/xPaw))
- Fix hover effect on channels in topics ([#149](https://github.com/thelounge/lounge/pull/149) by [@xPaw](https://github.com/xPaw))
- Add missing mode action to muted colors ([#150](https://github.com/thelounge/lounge/pull/150) by [@astorije](https://github.com/astorije))
## v1.3.0 - 2016-03-03
[See the full changelog](https://github.com/thelounge/lounge/compare/v1.2.1...v1.3.0)
### Added
- Add hostmask in `join`/`part`/`quit` messages and move actions to templates ([#94](https://github.com/thelounge/lounge/pull/94) by [@xPaw](https://github.com/xPaw))
- Add a section in the README explaining why a fork was created ([#95](https://github.com/thelounge/lounge/pull/95) by [@almet](https://github.com/almet))
- Add the ability to let users change their password from the settings page ([#57](https://github.com/thelounge/lounge/pull/57) by [@diddledan](https://github.com/diddledan))
- Add the ability to let users set custom CSS in their settings ([#83](https://github.com/thelounge/lounge/pull/83) by [@xPaw](https://github.com/xPaw))
- Add notifications for channel invites ([#127](https://github.com/thelounge/lounge/pull/127) by [@astorije](https://github.com/astorije))
- Allow locking network configuration ([#82](https://github.com/thelounge/lounge/pull/82) by [@xPaw](https://github.com/xPaw))
### Changed
- Add target channel name in notifications ([#118](https://github.com/thelounge/lounge/pull/118) by [@astorije](https://github.com/astorije))
- Bump `grunt-contrib-uglify` and pin versions of `grunt`-related dependencies ([#119](https://github.com/thelounge/lounge/pull/119) by [@astorije](https://github.com/astorije))
- Switch to a power-off icon for logging out ([#131](https://github.com/thelounge/lounge/pull/131) by [@astorije](https://github.com/astorije))
### Removed
- Remove auto-select on input fields ([#120](https://github.com/thelounge/lounge/pull/120) by [@astorije](https://github.com/astorije))
### Fixed
- Fix the "Show more" button being displayed over chat messages and message paddings when `join`/`part`/`quit` messages are hidden ([`b53e5c4`](https://github.com/thelounge/lounge/commit/b53e5c407c7ca90e9741791b4e0d927fb5f54ea1) by [@xPaw](https://github.com/xPaw))
- Fix how highlights are handled and highlighted ([#91](https://github.com/thelounge/lounge/pull/91) by [@xPaw](https://github.com/xPaw))
- Fix favicon highlight on Chrome and remove `Favico.js` library ([#100](https://github.com/thelounge/lounge/pull/100) by [@xPaw](https://github.com/xPaw))
- Fix complete crash when refreshing a public instance, introduced by this release ([#125](https://github.com/thelounge/lounge/pull/125) by [@astorije](https://github.com/astorije))
- Fix clickable "you" in the text of an `/invite`, introduced by this release ([#122](https://github.com/thelounge/lounge/pull/122) by [@xPaw](https://github.com/xPaw))
- Fix minor issues with the main HTML file ([#134](https://github.com/thelounge/lounge/pull/134) by [@astorije](https://github.com/astorije))
- Strip control codes from notifications ([#123](https://github.com/thelounge/lounge/pull/123) by [@xPaw](https://github.com/xPaw))
## v1.2.1 - 2016-02-26
[See the full changelog](https://github.com/thelounge/lounge/compare/v1.2.0...v1.2.1)
### Changed
- Bump and pin mocha version ([#104](https://github.com/thelounge/lounge/pull/104) by [@astorije](https://github.com/astorije))
### Fixed
- Fix CSS selector syntax in channel message handler ([#102](https://github.com/thelounge/lounge/pull/102) by [@maxpoulin64](https://github.com/maxpoulin64))
- Fix fading channel name in sidebar of Crypto and Zenburn themes ([#105](https://github.com/thelounge/lounge/pull/105) by [@maxpoulin64](https://github.com/maxpoulin64))
- Fix `/invite` command broken by lodash bump ([#106](https://github.com/thelounge/lounge/pull/106) by [@JocelynDelalande](https://github.com/JocelynDelalande))
## v1.2.0 - 2016-02-24
[See the full changelog](https://github.com/thelounge/lounge/compare/v1.1.1...v1.2.0)
Note that this release will reset client-side settings to their defaults. Current users will have to re-set them in the settings page. This is [a conscious trade-off](https://github.com/thelounge/lounge/pull/70#issuecomment-186717859) as the fork is rather new and there are not many settings overall.
### Added
- Add support for the `/invite <nickname> <channel>` command ([#7](https://github.com/thelounge/lounge/pull/7) by [@xPaw](https://github.com/xPaw))
- Add a command shorthand to invite in the current channel with `/invite <nickname>` ([#76](https://github.com/thelounge/lounge/pull/76) by [@astorije](https://github.com/astorije))
- Add style linting for all CSS files in the repository ([#43](https://github.com/thelounge/lounge/pull/43) by [@xPaw](https://github.com/xPaw))
### Changed
- Improve client performance by updating the users' list only when it's needed ([#58](https://github.com/thelounge/lounge/pull/58) by [@maxpoulin64](https://github.com/maxpoulin64))
- Let the badge counter hide with a fade-out ([#73](https://github.com/thelounge/lounge/pull/73) by [@xPaw](https://github.com/xPaw))
- Update `lodash` dependency to the latest major version ([#38](https://github.com/thelounge/lounge/pull/38) by [@xPaw](https://github.com/xPaw))
- Use `localStorage` instead of cookies for client-side settings storage ([#70](https://github.com/thelounge/lounge/pull/70) by [@xPaw](https://github.com/xPaw))
- Replace Bootstrap's tooltips with CSS tooltips from GitHub's Primer ([#79](https://github.com/thelounge/lounge/pull/79) by [@xPaw](https://github.com/xPaw))
### Fixed
- Fade long channel names in the sidebar instead of breaking to another line ([#75](https://github.com/thelounge/lounge/pull/75) by [@maxpoulin64](https://github.com/maxpoulin64))
## v1.1.1 - 2016-02-19
[See the full changelog](https://github.com/thelounge/lounge/compare/v1.1.0...v1.1.1)
### Changed
- Remove compiled assets and generate them at prepublish time ([#63](https://github.com/thelounge/lounge/pull/63) by [@astorije](https://github.com/astorije))
## v1.1.0 - 2016-02-19
[See the full changelog](https://github.com/thelounge/lounge/compare/v1.0.2...v1.1.0)
### Added
- Allow The Lounge to be proxied behind a `/path/` URL ([#27](https://github.com/thelounge/lounge/pull/27) by [@gdamjan](https://github.com/gdamjan))
### Changed
- Simplify a great deal the CONTRIBUTING file ([#40](https://github.com/thelounge/lounge/pull/40) by [@astorije](https://github.com/astorije))
- Use a Font Awesome icon for the channel closing button ([#48](https://github.com/thelounge/lounge/pull/48) by [@xPaw](https://github.com/xPaw))
### Removed
- Remove Node 0.10 from Travis CI ([#60](https://github.com/thelounge/lounge/pull/60) by [@astorije](https://github.com/astorije))
### Fixed
- Suppress deprecation warning for `moment().zone` ([#37](https://github.com/thelounge/lounge/pull/37) by [@deiu](https://github.com/deiu))
- Fix a bug preventing the closing of a channel when the user was kicked out ([#34](https://github.com/thelounge/lounge/pull/34) by [@xPaw](https://github.com/xPaw))
## v1.0.2 - 2016-02-15
[See the full changelog](https://github.com/thelounge/lounge/compare/v1.0.1...v1.0.2)
### Changed
- Remove `#foo` channel from the default configuration file ([#22](https://github.com/thelounge/lounge/pull/22) by [@astorije](https://github.com/astorije))
- Change the Freenode URL to `chat.freenode.net` in the default configuration file ([#13](https://github.com/thelounge/lounge/pull/13) by [@dubzi](https://github.com/dubzi))
- Ensure all `.js` files are linted ([#42](https://github.com/thelounge/lounge/pull/42) by [@williamboman](https://github.com/williamboman))
### Fixed
- Hide the user list button on a server or private message window ([#32](https://github.com/thelounge/lounge/pull/32) by [@MaxLeiter](https://github.com/MaxLeiter))
- Correctly sort the user list whenever a user joins ([#33](https://github.com/thelounge/lounge/pull/33) by [@xPaw](https://github.com/xPaw))
## v1.0.1 - 2016-02-14
[See the full changelog](https://github.com/thelounge/lounge/compare/v1.0.0...v1.0.1)
### Changed
- In the change log, use a permanent URL to link the previous history of The Lounge to Shout ([#12](https://github.com/thelounge/lounge/pull/12) by [@xPaw](https://github.com/xPaw))
- Update some dependencies and pin versions ([#8](https://github.com/thelounge/lounge/pull/8) by [@xPaw](https://github.com/xPaw))
### Fixed
- Add missing form methods that were causing LastPass to trigger a warning ([#19](https://github.com/thelounge/lounge/pull/19) by [@maxpoulin64](https://github.com/maxpoulin64))
- Fix comments in the configuration file ([#1](https://github.com/thelounge/lounge/pull/1) by [@FryDay](https://github.com/FryDay))
## v1.0.0 - 2016-02-12
[See the full changelog](https://github.com/thelounge/lounge/compare/baadc3df3534fb22515a8c2ea29218fbbc1228b4...v1.0.0)
This is the first release of **The Lounge**, picking up where Shout `v0.53.0` left off!
### Added
- Enable notifications on all messages, which can be controlled in the settings ([#540](https://github.com/erming/shout/pull/540) by [@nickel715](https://github.com/nickel715))
- Add Travis CI and David DM badges on the README ([#465](https://github.com/erming/shout/pull/465) by [@astorije](https://github.com/astorije))
- Emit sent notice back to the user ([#590](https://github.com/erming/shout/pull/590) by [@xPaw](https://github.com/xPaw))
- Send user agent with link expander requests ([#608](https://github.com/erming/shout/pull/608) by [@xPaw](https://github.com/xPaw))
- Add a `.gitattributes` file to normalize line endings ([#610](https://github.com/erming/shout/pull/610) by [@xPaw](https://github.com/xPaw))
- Style scrollbars (WebKit only) ([#593](https://github.com/erming/shout/pull/593) by [@xPaw](https://github.com/xPaw))
- Add a badge to display the IRC channel at the top ([#599](https://github.com/erming/shout/pull/599) by [@astorije](https://github.com/astorije))
- Rotate `part`/`quit` icon for new action display ([#617](https://github.com/erming/shout/pull/617) by [@MaxLeiter](https://github.com/MaxLeiter))
### Changed
- Update slate-irc to v0.8.1 ([#597](https://github.com/erming/shout/pull/597) by [@xPaw](https://github.com/xPaw))
- Limit maximum height of inline images ([#598](https://github.com/erming/shout/pull/598) by [@xPaw](https://github.com/xPaw))
- Use a single function to process and render messages ([#596](https://github.com/erming/shout/pull/596) by [@xPaw](https://github.com/xPaw))
- Render user actions separately ([#588](https://github.com/erming/shout/pull/588) by [@xPaw](https://github.com/xPaw))
- Simply parse all 0-99 IRC colors ([#609](https://github.com/erming/shout/pull/609) by [@xPaw](https://github.com/xPaw))
- Tag notifications to reduce notification spam ([#418](https://github.com/erming/shout/pull/418) by [@williamboman](https://github.com/williamboman))
- Change all mentions of Shout to the new name: The Lounge ([#2](https://github.com/thelounge/lounge/pull/2) by [@astorije](https://github.com/astorije))
### Fixed
- Fix initial copyright year in the LICENSE notice ([#591](https://github.com/erming/shout/pull/591) by [@pra85](https://github.com/pra85))
- Fix wrong color class on Zenburn style ([#595](https://github.com/erming/shout/pull/595) by [@astorije](https://github.com/astorije))
- Run new topic through parser when it is updated ([#587](https://github.com/erming/shout/pull/587) by [@xPaw](https://github.com/xPaw))
- Fix several things on Morning and Zenburn themes ([#605](https://github.com/erming/shout/pull/605) by [@xPaw](https://github.com/xPaw))
- Fix word wrap on firefox ([#570](https://github.com/erming/shout/pull/570) by [@YaManicKill](https://github.com/YaManicKill))
- Change user buttons to `a` links, allowing highlighting on Firefox ([#571](https://github.com/erming/shout/pull/571) and [#574](https://github.com/erming/shout/pull/574) by [@YaManicKill](https://github.com/YaManicKill))
---
All previous changes can be found on [Shout's CHANGELOG, starting at `v0.53.0`](https://github.com/erming/shout/blob/35587f3c35d0a8ac78a2495934ff9eaa8f1aa71c/CHANGELOG.md#0530--2016-01-07).

View file

@ -1,37 +0,0 @@
## Contributing
Welcome to The Lounge, it's great to have you here! We thank you in advance for
your contributions.
### I have a question
- Find us on the Freenode channel `#thelounge`. You might not get an answer
right away, but this channel is full of nice people who will be happy to
help you.
### I want to report a bug
- Look at the [open and closed
issues](https://github.com/thelounge/lounge/issues?q=is%3Aissue) to see if
this was not already discussed before. If you can't see any, feel free to
[open a new issue](https://github.com/thelounge/lounge/issues/new).
### I want to contribute to the code
- Make sure to discuss your ideas with the community in an
[issue](https://github.com/thelounge/lounge/issues) or on the IRC channel.
- Take a look at the open issues labeled as [`help wanted`](https://github.com/thelounge/lounge/issues?q=is%3Aopen+is%3Aissue+label%3Abug+label%3A%22help+wanted%22)
if you want to help without having a specific idea in mind.
- Make sure that your PRs do not contain unnecessary commits or merge commits.
[Squash commits](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History)
whenever possible.
- [Rebase](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) (instead of
merge) outdated PRs on the `master` branch.
- Give extra care to your commit messages. Use the [imperative present
tense](https://git-scm.com/book/ch5-2.html#Commit-Guidelines) and [follow Tim
Pope's guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
- Each PR will be reviewed by at least two different project maintainers. You
can read more about this in the [maintainers'
corner](https://github.com/thelounge/lounge/wiki/Maintainers'-corner).
- Please document any relevant changes in the documentation that can be found
[in its own repository](https://github.com/thelounge/thelounge.github.io).

View file

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2016 All contributors to The Lounge
Copyright (c) 2014 Mattias Erming and contributors, as part of Shout.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -1,83 +0,0 @@
[![#thelounge IRC channel on freenode](https://img.shields.io/badge/irc%20channel-%23thelounge%20on%20freenode-blue.svg)](https://avatar.playat.ch:1000/)
[![npm version](https://img.shields.io/npm/v/thelounge.svg)](https://www.npmjs.org/package/thelounge)
[![Travis CI Build Status](https://travis-ci.org/thelounge/lounge.svg?branch=master)](https://travis-ci.org/thelounge/lounge)
[![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/deymtp0lldq78s8t/branch/master?svg=true)](https://ci.appveyor.com/project/astorije/lounge/branch/master)
[![Dependency Status](https://david-dm.org/thelounge/lounge.svg)](https://david-dm.org/thelounge/lounge)
[![devDependency Status](https://david-dm.org/thelounge/lounge/dev-status.svg)](https://david-dm.org/thelounge/lounge?type=dev)
# The Lounge
__What is it?__
The Lounge is a web IRC client that you host on your own server.
*This is the official, community-managed fork of @erming's great initiative, the [Shout](https://github.com/erming/shout) project.*
__What features does it have?__
- Multiple user support
- Stays connected even when you close the browser
- Connect from multiple devices at once
- Responsive layout — works well on your smartphone
- _.. and more!_
__Why the fork?__
We felt that the original [Shout](https://github.com/erming/shout) project
"stagnated" a little because its original author wanted it to remain his pet
project (which is a perfectly fine thing!).
A bunch of people, excited about doing things a bit differently than the upstream
project forked it under a new name: “The Lounge”.
This fork aims to be community managed, meaning that the decisions are taken
in a collegial fashion, and that a bunch of maintainers should be able to make
the review process quicker and more streamlined.
## Installation and usage
The Lounge requires [Node.js](https://nodejs.org/) v4 or more recent.
### Running stable releases from npm (recommended)
Run this in a terminal to install (or upgrade) the latest stable release from
[npm](https://www.npmjs.com/):
```sh
[sudo] npm install -g thelounge
```
When installation is complete, run:
```sh
lounge start
```
For more information, read the [documentation](https://thelounge.github.io/docs/), [wiki](https://github.com/thelounge/lounge/wiki), or run:
```sh
lounge --help
```
### Running from source
The following commands install the development version of The Lounge. A word of
caution: while it is the most recent codebase, this is not production-ready!
```sh
git clone https://github.com/thelounge/lounge.git
cd lounge
npm install
npm start
```
## Development setup
Simply follow the instructions to run The Lounge from source above, on your own
fork.
Before submitting any change, make sure to:
- Read the [Contributing instructions](https://github.com/thelounge/lounge/blob/master/CONTRIBUTING.md#contributing)
- Run `npm test` to execute linters and test suite
- Run `npm run build` if you change or add anything in `client/js/libs` or `client/views`

View file

@ -1,29 +0,0 @@
---
# http://www.appveyor.com/docs/appveyor-yml
# Build version format
version: "{build}"
# Do not build on tags (GitHub only)
skip_tags: true
environment:
nodejs_version: '4'
install:
- ps: Install-Product node $env:nodejs_version
- npm install
- npm install mocha-appveyor-reporter
- echo --reporter mocha-appveyor-reporter >> test/mocha.opts
test_script:
- node --version
- npm --version
- npm test
# cache npm modules
cache:
- '%AppData%/npm-cache'
# Don't actually build
build: off

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -1,93 +0,0 @@
Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -1,93 +0,0 @@
Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -1,7 +0,0 @@
<?xml version="1.0"?>
<svg enable-background="new 0 0 512 512" height="512px" id="Layer_1" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="chat_x5F_support">
<path d="M170.542,357.786c-15.944-2.444-31.341-6.704-46.024-12.779l-5.493-2.272l-44.78,16.973l15.091-33.515 l-8.914-7.281C48.552,292.879,31,258.503,31,222.119C31,145.96,108.141,84,202.96,84c66.491,0,124.284,30.47,152.885,74.937 c6.45,0.715,12.819,1.716,19.08,3.013C346.379,107.298,280.133,69,202.96,69C99.705,69,16,137.554,16,222.119 c0,42.354,20.999,80.691,54.934,108.411l-25.235,56.04l73.085-27.701c18.762,7.762,39.34,13.007,61.07,15.203 C176.34,368.804,173.231,363.368,170.542,357.786z" fill="#818B9E" />
<path d="M492,303.273c0-72.144-71.411-130.629-159.5-130.629S173,231.128,173,303.273s71.411,130.629,159.5,130.629 c25.834,0,50.229-5.036,71.813-13.965l62.35,23.633l-21.528-47.809C474.085,372.112,492,339.406,492,303.273z M253.5,334.606 c-14.636,0-26.5-11.864-26.5-26.5s11.864-26.5,26.5-26.5c14.636,0,26.5,11.864,26.5,26.5S268.136,334.606,253.5,334.606z M332.5,334.606c-14.636,0-26.5-11.864-26.5-26.5s11.864-26.5,26.5-26.5s26.5,11.864,26.5,26.5S347.136,334.606,332.5,334.606z M411.5,334.606c-14.636,0-26.5-11.864-26.5-26.5s11.864-26.5,26.5-26.5s26.5,11.864,26.5,26.5S426.136,334.606,411.5,334.606z" fill="#455164" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,7 +0,0 @@
<?xml version="1.0"?>
<svg enable-background="new 0 0 512 512" height="512px" id="Layer_1" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="chat_x5F_support">
<path d="M170.542,357.786c-15.944-2.444-31.341-6.704-46.024-12.779l-5.493-2.272l-44.78,16.973l15.091-33.515 l-8.914-7.281C48.552,292.879,31,258.503,31,222.119C31,145.96,108.141,84,202.96,84c66.491,0,124.284,30.47,152.885,74.937 c6.45,0.715,12.819,1.716,19.08,3.013C346.379,107.298,280.133,69,202.96,69C99.705,69,16,137.554,16,222.119 c0,42.354,20.999,80.691,54.934,108.411l-25.235,56.04l73.085-27.701c18.762,7.762,39.34,13.007,61.07,15.203 C176.34,368.804,173.231,363.368,170.542,357.786z" fill="#818B9E" />
<path d="M492,303.273c0-72.144-71.411-130.629-159.5-130.629S173,231.128,173,303.273s71.411,130.629,159.5,130.629 c25.834,0,50.229-5.036,71.813-13.965l62.35,23.633l-21.528-47.809C474.085,372.112,492,339.406,492,303.273z M253.5,334.606 c-14.636,0-26.5-11.864-26.5-26.5s11.864-26.5,26.5-26.5c14.636,0,26.5,11.864,26.5,26.5S268.136,334.606,253.5,334.606z M332.5,334.606c-14.636,0-26.5-11.864-26.5-26.5s11.864-26.5,26.5-26.5s26.5,11.864,26.5,26.5S347.136,334.606,332.5,334.606z M411.5,334.606c-14.636,0-26.5-11.864-26.5-26.5s11.864-26.5,26.5-26.5s26.5,11.864,26.5,26.5S426.136,334.606,411.5,334.606z" fill="#f2f3f5" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -1,382 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="mobile-web-app-capable" content="yes">
<meta name="referrer" content="no-referrer">
<meta name="theme-color" content="#455164">
<title>The Lounge</title>
<link rel="stylesheet" href="css/bootstrap.css">
<link rel="stylesheet" href="css/style.css">
<link id="theme" rel="stylesheet" href="<%= theme %>">
<style id="user-specified-css"></style>
<link rel="shortcut icon" href="img/favicon.png" data-other="img/favicon-notification.png" data-toggled="false" id="favicon">
<link rel="apple-touch-icon" sizes="120x120" href="img/apple-touch-icon-120x120.png">
<link rel="manifest" href="manifest.json">
</head>
<body class="signed-out <%= public ? "public" : "" %>">
<div id="wrap">
<div id="viewport">
<aside id="sidebar">
<div class="networks"></div>
<div class="empty">
You're not connected to any networks yet.
</div>
</aside>
<footer id="footer">
<span class="tooltipped tooltipped-n" aria-label="Sign in"><button class="icon sign-in" data-target="#sign-in" aria-label="Sign in"></button></span>
<span class="tooltipped tooltipped-n" aria-label="Connect to network"><button class="icon connect" data-target="#connect" aria-label="Connect to network"></button></span>
<span class="tooltipped tooltipped-n" aria-label="Client settings"><button class="icon settings" data-target="#settings" aria-label="Client settings"></button></span>
<span class="tooltipped tooltipped-n" aria-label="Sign out"><button class="icon sign-out" id="sign-out" aria-label="Sign out"></button></span>
</footer>
<div id="main">
<div id="windows">
<div id="loading" class="window active">
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1 class="title">The Lounge is loading…</h1>
</div>
<div class="col-xs-12">
<p id="loading-page-message">Loading the app… <a href="http://enable-javascript.com/" target="_blank">Make sure to have JavaScript enabled.</a></p>
<p id="loading-slow">This is taking longer than it should, there might be connectivity issues.</p>
<script async src="js/loading-slow-alert.js"></script>
</div>
</div>
</div>
</div>
<div id="chat-container" class="window">
<div id="chat"></div>
<form id="form" method="post" action="">
<div class="input">
<span id="nick">
<span id="nick-value" spellcheck="false"></span><!-- Comments here remove spaces between elements
--><span id="set-nick-tooltip" class="tooltipped tooltipped-e" aria-label="Change nick"><button id="set-nick" type="button" aria-label="Change nick"></button></span><!--
--><span id="cancel-nick-tooltip" class="tooltipped tooltipped-e" aria-label="Cancel"><button id="cancel-nick" type="button" aria-label="Cancel"></button></span><!--
--><span id="save-nick-tooltip" class="tooltipped tooltipped-e" aria-label="Save"><button id="submit-nick" type="button" aria-label="Save"></button></span>
</span>
<textarea id="input" class="mousetrap"></textarea>
<span class="tooltipped tooltipped-w" aria-label="Send message">
<button id="submit" type="submit" aria-label="Send message"></button>
</span>
</div>
</form>
</div>
<div id="sign-in" class="window">
<form class="container" method="post" action="">
<div class="row">
<div class="col-xs-12">
<h1 class="title">Sign in to The Lounge</h1>
</div>
<div class="col-xs-12">
<label>
Username
<input class="input" name="user">
</label>
</div>
<div class="col-xs-12">
<label class="port">
Password
<input class="input" type="password" name="password">
</label>
</div>
<div class="col-xs-12">
<label class="remember">
<input type="checkbox" name="remember" id="sign-in-remember" checked>
Stay signed in
</label>
</div>
<div class="col-xs-12 error" style="display: none;">
Authentication failed.
</div>
<div class="col-xs-12">
<button type="submit" class="btn">
Sign in
</button>
</div>
</div>
</form>
</div>
<div id="connect" class="window">
<div class="header">
<button class="lt" aria-label="Toggle channel list"></button>
</div>
<form class="container" method="post" action="">
<div class="row">
<div class="col-sm-12">
<h1 class="title">
<%= public ? "The Lounge - " : "" %>
Connect
<%= !displayNetwork && lockNetwork ? "to " + defaults.name : "" %>
</h1>
</div>
<div <%= typeof(displayNetwork) !== "undefined" && !displayNetwork ? 'style="display: none;"' : ''%>>
<div class="col-sm-12">
<h2>Network settings</h2>
</div>
<div class="col-sm-3">
<label for="connect:name">Name</label>
</div>
<div class="col-sm-9">
<input class="input" id="connect:name" name="name" value="<%= defaults.name %>">
</div>
<div class="col-sm-3">
<label for="connect:host">Server</label>
</div>
<div class="col-sm-6 col-xs-8">
<input class="input" id="connect:host" name="host" value="<%= defaults.host %>" aria-label="Server address" <%= typeof(lockNetwork) !== "undefined" && lockNetwork ? "disabled" : "" %>>
</div>
<div class="col-sm-3 col-xs-4">
<div class="port">
<input class="input" type="number" min="1" max="65535" name="port" value="<%= defaults.port %>" aria-label="Server port" <%= typeof(lockNetwork) !== "undefined" && lockNetwork ? "disabled" : "" %>>
</div>
</div>
<div class="clearfix"></div>
<div class="col-sm-3">
<label for="connect:password">Password</label>
</div>
<div class="col-sm-9">
<input class="input" id="connect:password" type="password" name="password" value="<%= defaults.password %>">
</div>
<div class="col-sm-9 col-sm-offset-3">
<label class="tls">
<input type="checkbox" name="tls" <%= defaults.tls ? "checked" : "" %> <%= typeof(lockNetwork) !== "undefined" && lockNetwork ? "disabled" : "" %>>
Enable TLS/SSL
</label>
</div>
<div class="clearfix"></div>
</div>
<div class="col-sm-12">
<h2>User preferences</h2>
</div>
<div class="col-sm-3">
<label for="connect:nick">Nick</label>
</div>
<div class="col-sm-9">
<input class="input nick" id="connect:nick" name="nick" value="<%= defaults.nick %>">
</div>
<div class="col-sm-3">
<label for="connect:username">Username</label>
</div>
<div class="col-sm-9">
<input class="input username" id="connect:username" name="username" value="<%= defaults.username %>">
</div>
<div class="col-sm-3">
<label for="connect:realname">Real name</label>
</div>
<div class="col-sm-9">
<input class="input" id="connect:realname" name="realname" value="<%= defaults.realname %>">
</div>
<div class="col-sm-3">
<label for="connect:channels">Channels</label>
</div>
<div class="col-sm-9">
<input class="input" id="connect:channels" name="join" value="<%= defaults.join %>">
</div>
<div class="col-sm-9 col-sm-offset-3">
<button type="submit" class="btn">Connect</button>
</div>
</div>
</form>
</div>
<div id="settings" class="window">
<div class="header">
<button class="lt" aria-label="Toggle channel list"></button>
</div>
<div class="container">
<div class="row">
<div class="col-sm-12">
<h1 class="title">Settings</h1>
</div>
<div class="col-sm-12">
<h2>Messages</h2>
</div>
<div class="col-sm-6">
<label class="opt">
<input type="checkbox" name="join">
Show joins
</label>
</div>
<div class="col-sm-6">
<label class="opt">
<input type="checkbox" name="motd">
Show <abbr title="Message Of The Day">MOTD</abbr>
</label>
</div>
<div class="col-sm-6">
<label class="opt">
<input type="checkbox" name="part">
Show parts
</label>
</div>
<div class="col-sm-6">
<label class="opt">
<input type="checkbox" name="nick">
Show nick changes
</label>
</div>
<div class="col-sm-6">
<label class="opt">
<input type="checkbox" name="mode">
Show mode
</label>
</div>
<div class="col-sm-6">
<label class="opt">
<input type="checkbox" name="quit">
Show quits
</label>
</div>
<div class="col-sm-12">
<h2>Visual Aids</h2>
</div>
<div class="col-sm-12">
<label class="opt">
<input type="checkbox" name="coloredNicks">
Enable colored nicknames
</label>
</div>
<div class="col-sm-12">
<h2>Theme</h2>
</div>
<div class="col-sm-12">
<label for="theme-select" class="sr-only">Theme</label>
<select id="theme-select" name="theme" class="input">
<% themes.forEach(function(themeName) { %>
<option value="<%= themeName %>">
<%= themeName.charAt(0).toUpperCase() + themeName.slice(1) %>
</option>
<% }) %>
</select>
</div>
<% if (typeof prefetch === "undefined" || prefetch !== false) { %>
<div class="col-sm-12">
<h2>Links and URLs</h2>
</div>
<div class="col-sm-6">
<label class="opt">
<input type="checkbox" name="thumbnails">
Auto-expand thumbnails
</label>
</div>
<div class="col-sm-6">
<label class="opt">
<input type="checkbox" name="links">
Auto-expand links
</label>
</div>
<% } %>
<div class="col-sm-12">
<h2>Notifications</h2>
</div>
<div class="col-sm-12">
<label class="opt">
<input id="desktopNotifications" type="checkbox" name="desktopNotifications">
Enable desktop notifications<br>
<div class="error" id="warnDisabledDesktopNotifications"><strong>Warning</strong>: Desktop notifications are blocked by your web browser</div>
</label>
</div>
<div class="col-sm-12">
<label class="opt">
<input type="checkbox" name="notification">
Enable notification sound
</label>
</div>
<div class="col-sm-12">
<div class="opt">
<button id="play">Play sound</button>
</div>
</div>
<div class="col-sm-12">
<label class="opt">
<input type="checkbox" name="notifyAllMessages">
Enable notification for all messages
</label>
</div>
<div class="col-sm-12">
<label class="opt">
<label for="highlights" class="sr-only">Custom highlights (comma-separated keywords)</label>
<input type="text" id="highlights" name="highlights" class="input" placeholder="Custom highlights (comma-separated keywords)">
</label>
</div>
<% if (!public && !ldap.enable) { %>
<div id="change-password">
<form action="" method="post">
<div class="col-sm-12">
<h2>Change password</h2>
</div>
<div class="col-sm-12">
<label for="old_password_input" class="sr-only">Enter current password</label>
<input type="password" id="old_password_input" name="old_password" class="input" placeholder="Enter current password">
</div>
<div class="col-sm-12">
<label for="new_password_input" class="sr-only">Enter desired new password</label>
<input type="password" id="new_password_input" name="new_password" class="input" placeholder="Enter desired new password">
</div>
<div class="col-sm-12">
<label for="verify_password_input" class="sr-only">Repeat new password</label>
<input type="password" id="verify_password_input" name="verify_password" class="input" placeholder="Repeat new password">
</div>
<div class="col-sm-12 feedback"></div>
<div class="col-sm-12">
<button type="submit" class="btn">Change password</button>
</div>
</form>
</div>
<% } %>
<div class="col-sm-12">
<h2>Custom Stylesheet</h2>
</div>
<div class="col-sm-12">
<textarea class="input" name="userStyles" id="user-specified-css-input" placeholder="You can override any style with CSS here"></textarea>
</div>
<div class="col-sm-12">
<h2>About The Lounge</h2>
</div>
<div class="col-sm-12">
<p class="about">
<% if (gitCommit) { %>
The Lounge is running from source
(<a href="https://github.com/thelounge/lounge/tree/<%= gitCommit %>" target="_blank"><code><%= gitCommit %></code></a>).<br>
<% } else { %>
The Lounge is in version <strong><%= version %></strong>
(<a href="https://github.com/thelounge/lounge/releases/tag/v<%= version %>" target="_blank">See release notes</a>).<br>
<% } %>
<a href="https://thelounge.github.io/" target="_blank">Website</a><br>
<a href="https://thelounge.github.io/docs/" target="_blank">Documentation</a><br>
<a href="https://github.com/thelounge/lounge/issues/new" target="_blank">Report a bug</a>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="context-menu-container">
<ul id="context-menu"></ul>
</div>
<script src="socket.io/socket.io.js"></script>
<script src="js/libs.min.js"></script>
<script src="js/lounge.templates.js"></script>
<script src="js/lounge.js"></script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -1,13 +0,0 @@
"use strict";
Handlebars.registerHelper(
// Generates a string from "color-1" to "color-32" based on an input string
"colorClass", function(str) {
var hash = 0;
for (var i = 0; i < str.length; i++) {
hash += str.charCodeAt(i);
}
return "color-" + (1 + hash % 32);
}
);

View file

@ -1,14 +0,0 @@
"use strict";
var diff;
Handlebars.registerHelper(
"diff", function(a, opt) {
if (a !== diff) {
diff = a;
return opt.fn(this);
}
return opt.inverse(this);
}
);

View file

@ -1,13 +0,0 @@
"use strict";
Handlebars.registerHelper(
"equal", function(a, b, opt) {
a = a.toString();
b = b.toString();
if (a === b) {
return opt.fn(this);
}
return opt.inverse(this);
}
);

View file

@ -1,5 +0,0 @@
"use strict";
Handlebars.registerHelper("localetime", function(time) {
return new Date(time).toLocaleString();
});

View file

@ -1,16 +0,0 @@
"use strict";
Handlebars.registerHelper(
"modes", function(mode) {
var modes = {
"~": "owner",
"&": "admin",
"!": "admin",
"@": "op",
"%": "half-op",
"+": "voice",
"": "normal"
};
return modes[mode];
}
);

View file

@ -1,126 +0,0 @@
"use strict";
Handlebars.registerHelper(
"parse", function(text) {
text = Handlebars.Utils.escapeExpression(text);
text = colors(text);
text = channels(text);
text = uri(text);
return text;
}
);
function uri(text) {
return window.URI.withinString(text, function(url) {
if (url.indexOf("javascript:") === 0) {
return url;
}
var split = url.split("<");
url = "<a href='" + split[0].replace(/^www/, "http://www") + "' target='_blank' rel='noopener'>" + split[0] + "</a>";
if (split.length > 1) {
url += "<" + split.slice(1).join("<");
}
return url;
});
}
/**
* Channels names are strings of length up to fifty (50) characters.
* The only restriction on a channel name is that it SHALL NOT contain
* any spaces (' '), a control G (^G or ASCII 7), a comma (',').
* Channel prefix '&' is handled as '&amp;' because this parser is executed
* after entities in the message have been escaped. This prevents a couple of bugs.
*/
function channels(text) {
return text.replace(
/(^|\s|\x07|,)((?:#|&amp;)[^\x07\s,]{1,49})/g,
'$1<span class="inline-channel" role="button" tabindex="0" data-chan="$2">$2</span>'
);
}
/**
* MIRC compliant colour and style parser
* Unfortuanately this is a non trivial operation
* See this branch for source and tests
* https://github.com/megawac/irc-style-parser/tree/shout
*/
var styleCheck_Re = /[\x00-\x1F]/,
back_re = /^([0-9]{1,2})(,([0-9]{1,2}))?/,
colourKey = "\x03",
// breaks all open styles ^O (\x0F)
styleBreak = "\x0F";
function styleTemplate(settings) {
return "<span class='" + settings.style + "'>" + settings.text + "</span>";
}
var 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] ? style[2] : "irc-" + style[0],
key: style[1],
keyregex: new RegExp(escaped + "(.*?)(" + escaped + "|$)")
};
});
function colors(line) {
// http://www.mirc.com/colors.html
// http://www.aviran.org/stripremove-irc-client-control-characters/
// https://github.com/perl6/mu/blob/master/examples/rules/Grammar-IRC.pm
// regexs are cruel to parse this thing
// already done?
if (!styleCheck_Re.test(line)) {
return line;
}
// split up by the irc style break character ^O
if (line.indexOf(styleBreak) >= 0) {
return line.split(styleBreak).map(colors).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);
if (!match) {
// ^C (no colour) ending. Escape current colour and carry on
background = "";
continue;
}
colour = "irc-fg" + +match[1];
// set the background colour
if (match[3]) {
background = " irc-bg" + +match[3];
}
// update the parsed text result
result = result.replace(colourKey + text, styleTemplate({
style: colour + background,
text: text.slice(match[0].length)
}));
}
// Matching styles (italics/bold/underline)
// if only colours were this easy...
styles.forEach(function(style) {
if (result.indexOf(style.key) < 0) {
return;
}
result = result.replace(style.keyregex, function(matchedTrash, matchedText) {
return styleTemplate({
style: style.style,
text: matchedText
});
});
});
return result;
}

View file

@ -1,11 +0,0 @@
"use strict";
Handlebars.registerHelper(
"roundBadgeNumber", function(count) {
if (count < 1000) {
return count;
}
return (count / 1000).toFixed(2).slice(0, -1) + "k";
}
);

View file

@ -1,7 +0,0 @@
"use strict";
Handlebars.registerHelper(
"toJSON", function(context) {
return JSON.stringify(context);
}
);

View file

@ -1,19 +0,0 @@
"use strict";
Handlebars.registerHelper(
"tz", function(time) {
time = new Date(time);
var h = time.getHours();
var m = time.getMinutes();
if (h < 10) {
h = "0" + h;
}
if (m < 10) {
m = "0" + m;
}
return h + ":" + m;
}
);

View file

@ -1,7 +0,0 @@
"use strict";
Handlebars.registerHelper(
"users", function(count) {
return count + " " + (count === 1 ? "user" : "users");
}
);

File diff suppressed because it is too large Load diff

View file

@ -1,89 +0,0 @@
/*!
* inputhistory
* https://github.com/erming/inputhistory
* v0.3.1
*/
(function($) {
$.inputhistory = {};
$.inputhistory.defaultOptions = {
history: [],
preventSubmit: false
};
$.fn.history = // Alias
$.fn.inputhistory = function(options) {
options = $.extend(
$.inputhistory.defaultOptions,
options
);
var self = this;
if (self.size() > 1) {
return self.each(function() {
$(this).history(options);
});
}
var history = options.history;
history.push("");
var i = 0;
self.on("keydown", function(e) {
var key = e.which;
switch (key) {
case 13: // Enter
if (e.shiftKey) {
return; // multiline input
}
if (self.val() != "") {
i = history.length;
history[i - 1] = self.val();
history.push("");
if (history[i - 1] == history[i - 2]) {
history.splice(-2, 1);
i--;
}
}
if (!options.preventSubmit) {
self.parents("form").eq(0).submit();
}
self.val("");
break;
case 38: // Up
case 40: // Down
// NOTICE: This is specific to The Lounge.
if (e.ctrlKey || e.metaKey) {
break;
}
if (
this.value.indexOf("\n") >= 0
&&
(key === 38 && this.selectionStart > 0)
||
(key === 40 && this.selectionStart < this.value.length))
{
return; // don't prevent default
}
history[i] = self.val();
if (key == 38 && i != 0) {
i--;
} else if (key == 40 && i < history.length - 1) {
i++;
}
self.val(history[i]);
break;
default:
return;
}
e.preventDefault();
});
return this;
}
})(jQuery);

File diff suppressed because it is too large Load diff

View file

@ -1,953 +0,0 @@
/*global define:false */
/**
* Copyright 2013 Craig Campbell
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Mousetrap is a simple keyboard shortcut library for Javascript with
* no external dependencies
*
* @version 1.4.6
* @url craig.is/killing/mice
*/
(function(window, document, undefined) {
/**
* mapping of special keycodes to their corresponding keys
*
* everything in this dictionary cannot use keypress events
* so it has to be here to map to the correct keycodes for
* keyup/keydown events
*
* @type {Object}
*/
var _MAP = {
8: 'backspace',
9: 'tab',
13: 'enter',
16: 'shift',
17: 'ctrl',
18: 'alt',
20: 'capslock',
27: 'esc',
32: 'space',
33: 'pageup',
34: 'pagedown',
35: 'end',
36: 'home',
37: 'left',
38: 'up',
39: 'right',
40: 'down',
45: 'ins',
46: 'del',
91: 'meta',
93: 'meta',
224: 'meta'
},
/**
* mapping for special characters so they can support
*
* this dictionary is only used incase you want to bind a
* keyup or keydown event to one of these keys
*
* @type {Object}
*/
_KEYCODE_MAP = {
106: '*',
107: '+',
109: '-',
110: '.',
111 : '/',
186: ';',
187: '=',
188: ',',
189: '-',
190: '.',
191: '/',
192: '`',
219: '[',
220: '\\',
221: ']',
222: '\''
},
/**
* this is a mapping of keys that require shift on a US keypad
* back to the non shift equivelents
*
* this is so you can use keyup events with these keys
*
* note that this will only work reliably on US keyboards
*
* @type {Object}
*/
_SHIFT_MAP = {
'~': '`',
'!': '1',
'@': '2',
'#': '3',
'$': '4',
'%': '5',
'^': '6',
'&': '7',
'*': '8',
'(': '9',
')': '0',
'_': '-',
'+': '=',
':': ';',
'\"': '\'',
'<': ',',
'>': '.',
'?': '/',
'|': '\\'
},
/**
* this is a list of special strings you can use to map
* to modifier keys when you specify your keyboard shortcuts
*
* @type {Object}
*/
_SPECIAL_ALIASES = {
'option': 'alt',
'command': 'meta',
'return': 'enter',
'escape': 'esc',
'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl'
},
/**
* variable to store the flipped version of _MAP from above
* needed to check if we should use keypress or not when no action
* is specified
*
* @type {Object|undefined}
*/
_REVERSE_MAP,
/**
* a list of all the callbacks setup via Mousetrap.bind()
*
* @type {Object}
*/
_callbacks = {},
/**
* direct map of string combinations to callbacks used for trigger()
*
* @type {Object}
*/
_directMap = {},
/**
* keeps track of what level each sequence is at since multiple
* sequences can start out with the same sequence
*
* @type {Object}
*/
_sequenceLevels = {},
/**
* variable to store the setTimeout call
*
* @type {null|number}
*/
_resetTimer,
/**
* temporary state where we will ignore the next keyup
*
* @type {boolean|string}
*/
_ignoreNextKeyup = false,
/**
* temporary state where we will ignore the next keypress
*
* @type {boolean}
*/
_ignoreNextKeypress = false,
/**
* are we currently inside of a sequence?
* type of action ("keyup" or "keydown" or "keypress") or false
*
* @type {boolean|string}
*/
_nextExpectedAction = false;
/**
* loop through the f keys, f1 to f19 and add them to the map
* programatically
*/
for (var i = 1; i < 20; ++i) {
_MAP[111 + i] = 'f' + i;
}
/**
* loop through to map numbers on the numeric keypad
*/
for (i = 0; i <= 9; ++i) {
_MAP[i + 96] = i;
}
/**
* cross browser add event method
*
* @param {Element|HTMLDocument} object
* @param {string} type
* @param {Function} callback
* @returns void
*/
function _addEvent(object, type, callback) {
if (object.addEventListener) {
object.addEventListener(type, callback, false);
return;
}
object.attachEvent('on' + type, callback);
}
/**
* takes the event and returns the key character
*
* @param {Event} e
* @return {string}
*/
function _characterFromEvent(e) {
// for keypress events we should return the character as is
if (e.type == 'keypress') {
var character = String.fromCharCode(e.which);
// if the shift key is not pressed then it is safe to assume
// that we want the character to be lowercase. this means if
// you accidentally have caps lock on then your key bindings
// will continue to work
//
// the only side effect that might not be desired is if you
// bind something like 'A' cause you want to trigger an
// event when capital A is pressed caps lock will no longer
// trigger the event. shift+a will though.
if (!e.shiftKey) {
character = character.toLowerCase();
}
return character;
}
// for non keypress events the special maps are needed
if (_MAP[e.which]) {
return _MAP[e.which];
}
if (_KEYCODE_MAP[e.which]) {
return _KEYCODE_MAP[e.which];
}
// if it is not in the special map
// with keydown and keyup events the character seems to always
// come in as an uppercase character whether you are pressing shift
// or not. we should make sure it is always lowercase for comparisons
return String.fromCharCode(e.which).toLowerCase();
}
/**
* checks if two arrays are equal
*
* @param {Array} modifiers1
* @param {Array} modifiers2
* @returns {boolean}
*/
function _modifiersMatch(modifiers1, modifiers2) {
return modifiers1.sort().join(',') === modifiers2.sort().join(',');
}
/**
* resets all sequence counters except for the ones passed in
*
* @param {Object} doNotReset
* @returns void
*/
function _resetSequences(doNotReset) {
doNotReset = doNotReset || {};
var activeSequences = false,
key;
for (key in _sequenceLevels) {
if (doNotReset[key]) {
activeSequences = true;
continue;
}
_sequenceLevels[key] = 0;
}
if (!activeSequences) {
_nextExpectedAction = false;
}
}
/**
* finds all callbacks that match based on the keycode, modifiers,
* and action
*
* @param {string} character
* @param {Array} modifiers
* @param {Event|Object} e
* @param {string=} sequenceName - name of the sequence we are looking for
* @param {string=} combination
* @param {number=} level
* @returns {Array}
*/
function _getMatches(character, modifiers, e, sequenceName, combination, level) {
var i,
callback,
matches = [],
action = e.type;
// if there are no events related to this keycode
if (!_callbacks[character]) {
return [];
}
// if a modifier key is coming up on its own we should allow it
if (action == 'keyup' && _isModifier(character)) {
modifiers = [character];
}
// loop through all callbacks for the key that was pressed
// and see if any of them match
for (i = 0; i < _callbacks[character].length; ++i) {
callback = _callbacks[character][i];
// if a sequence name is not specified, but this is a sequence at
// the wrong level then move onto the next match
if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) {
continue;
}
// if the action we are looking for doesn't match the action we got
// then we should keep going
if (action != callback.action) {
continue;
}
// if this is a keypress event and the meta key and control key
// are not pressed that means that we need to only look at the
// character, otherwise check the modifiers as well
//
// chrome will not fire a keypress if meta or control is down
// safari will fire a keypress if meta or meta+shift is down
// firefox will fire a keypress if meta or control is down
if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) {
// when you bind a combination or sequence a second time it
// should overwrite the first one. if a sequenceName or
// combination is specified in this call it does just that
//
// @todo make deleting its own method?
var deleteCombo = !sequenceName && callback.combo == combination;
var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level;
if (deleteCombo || deleteSequence) {
_callbacks[character].splice(i, 1);
}
matches.push(callback);
}
}
return matches;
}
/**
* takes a key event and figures out what the modifiers are
*
* @param {Event} e
* @returns {Array}
*/
function _eventModifiers(e) {
var modifiers = [];
if (e.shiftKey) {
modifiers.push('shift');
}
if (e.altKey) {
modifiers.push('alt');
}
if (e.ctrlKey) {
modifiers.push('ctrl');
}
if (e.metaKey) {
modifiers.push('meta');
}
return modifiers;
}
/**
* prevents default for this event
*
* @param {Event} e
* @returns void
*/
function _preventDefault(e) {
if (e.preventDefault) {
e.preventDefault();
return;
}
e.returnValue = false;
}
/**
* stops propogation for this event
*
* @param {Event} e
* @returns void
*/
function _stopPropagation(e) {
if (e.stopPropagation) {
e.stopPropagation();
return;
}
e.cancelBubble = true;
}
/**
* actually calls the callback function
*
* if your callback function returns false this will use the jquery
* convention - prevent default and stop propogation on the event
*
* @param {Function} callback
* @param {Event} e
* @returns void
*/
function _fireCallback(callback, e, combo, sequence) {
// if this event should not happen stop here
if (Mousetrap.stopCallback(e, e.target || e.srcElement, combo, sequence)) {
return;
}
if (callback(e, combo) === false) {
_preventDefault(e);
_stopPropagation(e);
}
}
/**
* handles a character key event
*
* @param {string} character
* @param {Array} modifiers
* @param {Event} e
* @returns void
*/
function _handleKey(character, modifiers, e) {
var callbacks = _getMatches(character, modifiers, e),
i,
doNotReset = {},
maxLevel = 0,
processedSequenceCallback = false;
// Calculate the maxLevel for sequences so we can only execute the longest callback sequence
for (i = 0; i < callbacks.length; ++i) {
if (callbacks[i].seq) {
maxLevel = Math.max(maxLevel, callbacks[i].level);
}
}
// loop through matching callbacks for this key event
for (i = 0; i < callbacks.length; ++i) {
// fire for all sequence callbacks
// this is because if for example you have multiple sequences
// bound such as "g i" and "g t" they both need to fire the
// callback for matching g cause otherwise you can only ever
// match the first one
if (callbacks[i].seq) {
// only fire callbacks for the maxLevel to prevent
// subsequences from also firing
//
// for example 'a option b' should not cause 'option b' to fire
// even though 'option b' is part of the other sequence
//
// any sequences that do not match here will be discarded
// below by the _resetSequences call
if (callbacks[i].level != maxLevel) {
continue;
}
processedSequenceCallback = true;
// keep a list of which sequences were matches for later
doNotReset[callbacks[i].seq] = 1;
_fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq);
continue;
}
// if there were no sequence matches but we are still here
// that means this is a regular match so we should fire that
if (!processedSequenceCallback) {
_fireCallback(callbacks[i].callback, e, callbacks[i].combo);
}
}
// if the key you pressed matches the type of sequence without
// being a modifier (ie "keyup" or "keypress") then we should
// reset all sequences that were not matched by this event
//
// this is so, for example, if you have the sequence "h a t" and you
// type "h e a r t" it does not match. in this case the "e" will
// cause the sequence to reset
//
// modifier keys are ignored because you can have a sequence
// that contains modifiers such as "enter ctrl+space" and in most
// cases the modifier key will be pressed before the next key
//
// also if you have a sequence such as "ctrl+b a" then pressing the
// "b" key will trigger a "keypress" and a "keydown"
//
// the "keydown" is expected when there is a modifier, but the
// "keypress" ends up matching the _nextExpectedAction since it occurs
// after and that causes the sequence to reset
//
// we ignore keypresses in a sequence that directly follow a keydown
// for the same character
var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress;
if (e.type == _nextExpectedAction && !_isModifier(character) && !ignoreThisKeypress) {
_resetSequences(doNotReset);
}
_ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown';
}
/**
* handles a keydown event
*
* @param {Event} e
* @returns void
*/
function _handleKeyEvent(e) {
// normalize e.which for key events
// @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion
if (typeof e.which !== 'number') {
e.which = e.keyCode;
}
var character = _characterFromEvent(e);
// no character found then stop
if (!character) {
return;
}
// need to use === for the character check because the character can be 0
if (e.type == 'keyup' && _ignoreNextKeyup === character) {
_ignoreNextKeyup = false;
return;
}
Mousetrap.handleKey(character, _eventModifiers(e), e);
}
/**
* determines if the keycode specified is a modifier key or not
*
* @param {string} key
* @returns {boolean}
*/
function _isModifier(key) {
return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta';
}
/**
* called to set a 1 second timeout on the specified sequence
*
* this is so after each key press in the sequence you have 1 second
* to press the next key before you have to start over
*
* @returns void
*/
function _resetSequenceTimer() {
clearTimeout(_resetTimer);
_resetTimer = setTimeout(_resetSequences, 1000);
}
/**
* reverses the map lookup so that we can look for specific keys
* to see what can and can't use keypress
*
* @return {Object}
*/
function _getReverseMap() {
if (!_REVERSE_MAP) {
_REVERSE_MAP = {};
for (var key in _MAP) {
// pull out the numeric keypad from here cause keypress should
// be able to detect the keys from the character
if (key > 95 && key < 112) {
continue;
}
if (_MAP.hasOwnProperty(key)) {
_REVERSE_MAP[_MAP[key]] = key;
}
}
}
return _REVERSE_MAP;
}
/**
* picks the best action based on the key combination
*
* @param {string} key - character for key
* @param {Array} modifiers
* @param {string=} action passed in
*/
function _pickBestAction(key, modifiers, action) {
// if no action was picked in we should try to pick the one
// that we think would work best for this key
if (!action) {
action = _getReverseMap()[key] ? 'keydown' : 'keypress';
}
// modifier keys don't work as expected with keypress,
// switch to keydown
if (action == 'keypress' && modifiers.length) {
action = 'keydown';
}
return action;
}
/**
* binds a key sequence to an event
*
* @param {string} combo - combo specified in bind call
* @param {Array} keys
* @param {Function} callback
* @param {string=} action
* @returns void
*/
function _bindSequence(combo, keys, callback, action) {
// start off by adding a sequence level record for this combination
// and setting the level to 0
_sequenceLevels[combo] = 0;
/**
* callback to increase the sequence level for this sequence and reset
* all other sequences that were active
*
* @param {string} nextAction
* @returns {Function}
*/
function _increaseSequence(nextAction) {
return function() {
_nextExpectedAction = nextAction;
++_sequenceLevels[combo];
_resetSequenceTimer();
};
}
/**
* wraps the specified callback inside of another function in order
* to reset all sequence counters as soon as this sequence is done
*
* @param {Event} e
* @returns void
*/
function _callbackAndReset(e) {
_fireCallback(callback, e, combo);
// we should ignore the next key up if the action is key down
// or keypress. this is so if you finish a sequence and
// release the key the final key will not trigger a keyup
if (action !== 'keyup') {
_ignoreNextKeyup = _characterFromEvent(e);
}
// weird race condition if a sequence ends with the key
// another sequence begins with
setTimeout(_resetSequences, 10);
}
// loop through keys one at a time and bind the appropriate callback
// function. for any key leading up to the final one it should
// increase the sequence. after the final, it should reset all sequences
//
// if an action is specified in the original bind call then that will
// be used throughout. otherwise we will pass the action that the
// next key in the sequence should match. this allows a sequence
// to mix and match keypress and keydown events depending on which
// ones are better suited to the key provided
for (var i = 0; i < keys.length; ++i) {
var isFinal = i + 1 === keys.length;
var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action);
_bindSingle(keys[i], wrappedCallback, action, combo, i);
}
}
/**
* Converts from a string key combination to an array
*
* @param {string} combination like "command+shift+l"
* @return {Array}
*/
function _keysFromString(combination) {
if (combination === '+') {
return ['+'];
}
return combination.split('+');
}
/**
* Gets info for a specific key combination
*
* @param {string} combination key combination ("command+s" or "a" or "*")
* @param {string=} action
* @returns {Object}
*/
function _getKeyInfo(combination, action) {
var keys,
key,
i,
modifiers = [];
// take the keys from this pattern and figure out what the actual
// pattern is all about
keys = _keysFromString(combination);
for (i = 0; i < keys.length; ++i) {
key = keys[i];
// normalize key names
if (_SPECIAL_ALIASES[key]) {
key = _SPECIAL_ALIASES[key];
}
// if this is not a keypress event then we should
// be smart about using shift keys
// this will only work for US keyboards however
if (action && action != 'keypress' && _SHIFT_MAP[key]) {
key = _SHIFT_MAP[key];
modifiers.push('shift');
}
// if this key is a modifier then add it to the list of modifiers
if (_isModifier(key)) {
modifiers.push(key);
}
}
// depending on what the key combination is
// we will try to pick the best event for it
action = _pickBestAction(key, modifiers, action);
return {
key: key,
modifiers: modifiers,
action: action
};
}
/**
* binds a single keyboard combination
*
* @param {string} combination
* @param {Function} callback
* @param {string=} action
* @param {string=} sequenceName - name of sequence if part of sequence
* @param {number=} level - what part of the sequence the command is
* @returns void
*/
function _bindSingle(combination, callback, action, sequenceName, level) {
// store a direct mapped reference for use with Mousetrap.trigger
_directMap[combination + ':' + action] = callback;
// make sure multiple spaces in a row become a single space
combination = combination.replace(/\s+/g, ' ');
var sequence = combination.split(' '),
info;
// if this pattern is a sequence of keys then run through this method
// to reprocess each pattern one key at a time
if (sequence.length > 1) {
_bindSequence(combination, sequence, callback, action);
return;
}
info = _getKeyInfo(combination, action);
// make sure to initialize array if this is the first time
// a callback is added for this key
_callbacks[info.key] = _callbacks[info.key] || [];
// remove an existing match if there is one
_getMatches(info.key, info.modifiers, {type: info.action}, sequenceName, combination, level);
// add this call back to the array
// if it is a sequence put it at the beginning
// if not put it at the end
//
// this is important because the way these are processed expects
// the sequence ones to come first
_callbacks[info.key][sequenceName ? 'unshift' : 'push']({
callback: callback,
modifiers: info.modifiers,
action: info.action,
seq: sequenceName,
level: level,
combo: combination
});
}
/**
* binds multiple combinations to the same callback
*
* @param {Array} combinations
* @param {Function} callback
* @param {string|undefined} action
* @returns void
*/
function _bindMultiple(combinations, callback, action) {
for (var i = 0; i < combinations.length; ++i) {
_bindSingle(combinations[i], callback, action);
}
}
// start!
_addEvent(document, 'keypress', _handleKeyEvent);
_addEvent(document, 'keydown', _handleKeyEvent);
_addEvent(document, 'keyup', _handleKeyEvent);
var Mousetrap = {
/**
* binds an event to mousetrap
*
* can be a single key, a combination of keys separated with +,
* an array of keys, or a sequence of keys separated by spaces
*
* be sure to list the modifier keys first to make sure that the
* correct key ends up getting bound (the last key in the pattern)
*
* @param {string|Array} keys
* @param {Function} callback
* @param {string=} action - 'keypress', 'keydown', or 'keyup'
* @returns void
*/
bind: function(keys, callback, action) {
keys = keys instanceof Array ? keys : [keys];
_bindMultiple(keys, callback, action);
return this;
},
/**
* unbinds an event to mousetrap
*
* the unbinding sets the callback function of the specified key combo
* to an empty function and deletes the corresponding key in the
* _directMap dict.
*
* TODO: actually remove this from the _callbacks dictionary instead
* of binding an empty function
*
* the keycombo+action has to be exactly the same as
* it was defined in the bind method
*
* @param {string|Array} keys
* @param {string} action
* @returns void
*/
unbind: function(keys, action) {
return Mousetrap.bind(keys, function() {}, action);
},
/**
* triggers an event that has already been bound
*
* @param {string} keys
* @param {string=} action
* @returns void
*/
trigger: function(keys, action) {
if (_directMap[keys + ':' + action]) {
_directMap[keys + ':' + action]({}, keys);
}
return this;
},
/**
* resets the library back to its initial state. this is useful
* if you want to clear out the current keyboard shortcuts and bind
* new ones - for example if you switch to another page
*
* @returns void
*/
reset: function() {
_callbacks = {};
_directMap = {};
return this;
},
/**
* should we stop this event before firing off callbacks
*
* @param {Event} e
* @param {Element} element
* @return {boolean}
*/
stopCallback: function(e, element) {
// if the element has the class "mousetrap" then no need to stop
if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
return false;
}
// stop for input, select, and textarea
return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
},
/**
* exposes _handleKey publicly so it can be overwritten by extensions
*/
handleKey: _handleKey
};
// expose mousetrap to the global object
window.Mousetrap = Mousetrap;
// expose mousetrap as an AMD module
if (typeof define === 'function' && define.amd) {
define(Mousetrap);
}
}) (window, document);

View file

@ -1,53 +0,0 @@
(function($) {
$.fn.unsticky = function() {
return this.trigger("unstick.sticky").unbind(".sticky");
};
$.fn.sticky = function() {
var self = this;
var stuckToBottom = true;
var lastStick = 0;
var keepToBottom = function() {
if (stuckToBottom) {
self.scrollBottom();
}
};
$(window).on("resize.sticky", keepToBottom);
self
.on("unstick.sticky", function() {
$(window).off("resize.sticky", keepToBottom);
})
.on("scroll.sticky", function() {
// When resizing, sometimes the browser sends a bunch of extra scroll events due to content
// reflow, so if we resized within 250ms we can assume it's one of those. The order of said
// events is not predictable, and scroll can happen last, so not setting stuckToBottom is
// not enough, we have to force the scroll still.
if (stuckToBottom && Date.now() - lastStick < 250) {
self.scrollBottom();
} else {
stuckToBottom = self.isScrollBottom();
}
})
.on("scrollBottom.sticky", function() {
stuckToBottom = true;
lastStick = Date.now();
this.scrollTop = this.scrollHeight;
})
.on("msg.sticky", keepToBottom)
.scrollBottom();
return self;
};
$.fn.scrollBottom = function() {
this.trigger("scrollBottom.sticky");
return this;
};
$.fn.isScrollBottom = function() {
var el = this[0];
return el.scrollHeight - el.scrollTop - el.offsetHeight <= 30;
};
})(jQuery);

View file

@ -1,256 +0,0 @@
/*!
* tabcomplete
* http://github.com/erming/tabcomplete
* v1.3.1
*/
(function($) {
var keys = {
backspace: 8,
tab: 9,
up: 38,
down: 40
};
$.tabcomplete = {};
$.tabcomplete.defaultOptions = {
after: "",
arrowKeys: false,
caseSensitive: false,
hint: "placeholder",
minLength: 1
};
$.fn.tab = // Alias
$.fn.tabcomplete = function(args, options) {
if (this.length > 1) {
return this.each(function() {
$(this).tabcomplete(args, options);
});
}
// Only enable the plugin on <input> and <textarea> elements.
var tag = this.prop("tagName");
if (tag != "INPUT" && tag != "TEXTAREA") {
return;
}
// Set default options.
options = $.extend(
$.tabcomplete.defaultOptions,
options
);
// Remove any leftovers.
// This allows us to override the plugin if necessary.
this.unbind(".tabcomplete");
this.prev(".hint").remove();
var self = this;
var backspace = false;
var i = -1;
var words = [];
var last = "";
var hint = $.noop;
// Determine what type of hinting to use.
switch (options.hint) {
case "placeholder":
hint = placeholder;
break;
case "select":
hint = select;
break;
}
this.on("input.tabcomplete", function() {
var input = self.val();
var word = input.split(/ |\n/).pop();
// Reset iteration.
i = -1;
last = "";
words = [];
// Check for matches if the current word is the last word.
if (self[0].selectionStart == input.length
&& word.length) {
if (typeof args === "function") {
// If the user supplies a function, invoke it
// and keep the result.
words = args(word);
} else {
// Otherwise, call the .match() function.
words = match(word, args, options.caseSensitive);
}
// Append 'after' to each word.
if (options.after) {
words = $.map(words, function(w) { return w + options.after; });
}
}
// Emit the number of matching words with the 'match' event.
self.trigger("match", words.length);
if (options.hint) {
if (!(options.hint == "select" && backspace) && word.length >= options.minLength) {
// Show hint.
hint.call(self, words[0]);
} else {
// Clear hinting.
// This call is needed when using backspace.
hint.call(self, "");
}
}
if (backspace) {
backspace = false;
}
});
this.on("keydown.tabcomplete", function(e) {
var key = e.which;
if (key == keys.tab && !e.ctrlKey
|| (options.arrowKeys && (key == keys.up || key == keys.down))) {
// Don't lose focus on tab click.
e.preventDefault();
// Iterate the matches with tab and the up and down keys by incrementing
// or decrementing the 'i' variable.
if (key != keys.up) {
i++;
} else {
if (i == -1) return;
if (i == 0) {
// Jump to the last word.
i = words.length - 1;
} else {
i--;
}
}
// Get next match.
var word = words[i % words.length];
if (!word) {
return;
}
var value = self.val();
last = last || value.split(/ |\n/).pop();
// Return if the 'minLength' requirement isn't met.
if (last.length < options.minLength) {
return;
}
// Update element with the completed text.
var text = value.substr(0, self[0].selectionStart - last.length) + word;
self.val(text);
// Put the cursor at the end after completion.
// This isn't strictly necessary, but solves an issue with
// Internet Explorer.
if (options.hint == "select") {
self[0].selectionStart = text.length;
}
// Remember the word until next time.
last = word;
// Emit event.
self.trigger("tabcomplete", last);
if (options.hint) {
// Turn off any additional hinting.
hint.call(self, "");
}
} else if (e.which == keys.backspace) {
// Remember that backspace was pressed. This is used
// by the 'input' event.
backspace = true;
// Reset iteration.
i = -1;
last = "";
}
});
if (options.hint) {
// If enabled, turn on hinting.
hint.call(this, "");
}
return this;
}
// Simple matching.
// Filter the array and return the items that begins with 'word'.
function match(word, array, caseSensitive) {
return $.grep(
array,
function(w) {
if (caseSensitive) {
return !w.indexOf(word);
} else {
return !w.toLowerCase().indexOf(word.toLowerCase());
}
}
);
}
// Show placeholder text.
// This works by creating a copy of the input and placing it behind
// the real input.
function placeholder(word) {
var input = this;
var clone = input.prev(".hint");
input.css({
backgroundColor: "transparent",
position: "relative",
});
// Lets create a clone of the input if it does
// not already exist.
if (!clone.length) {
input.wrap(
$("<div>").css({position: "relative", height: input.css("height")})
);
clone = input
.clone()
.attr("tabindex", -1)
.removeAttr("id name placeholder")
.addClass("hint")
.insertBefore(input);
clone.css({
position: "absolute",
});
}
var hint = "";
if (typeof word !== "undefined") {
var value = input.val();
hint = value + word.substr(value.split(/ |\n/).pop().length);
}
clone.val(hint);
}
// Hint by selecting part of the suggested word.
function select(word) {
var input = this;
var value = input.val();
if (word) {
input.val(
value
+ word.substr(value.split(/ |\n/).pop().length)
);
// Select hint.
input[0].selectionStart = value.length;
}
}
})(jQuery);

View file

@ -1,218 +0,0 @@
/**
* Notification JS
* Shims up the Notification API
*
* @author Andrew Dodson
* @website http://adodson.com/notification.js/
*/
//
// Does the browser support the the Notification API?
// .. and does it have a permission property?
//
(function(window, document){
var PERMISSION_GRANTED = 'granted',
PERMISSION_DENIED = 'denied',
PERMISSION_UNKNOWN = 'unknown';
var a = [], iv, i=0;
//
// Swap the document.title with the notification
//
function swaptitle(title){
if(a.length===0){
a = [document.title];
}
a.push(title);
if(!iv){
iv = setInterval(function(){
// has document.title changed externally?
if(a.indexOf(document.title) === -1 ){
// update the default title
a[0] = document.title;
}
document.title = a[++i%a.length];
}, 1000);
}
}
function swapTitleCancel(){
// dont do any more if we haven't got anything open
if(a.length===0){
return;
}
// if an IE overlay is present, kill it
if("external" in window && "msSiteModeClearIconOverlay" in window.external ){
window.external.msSiteModeClearIconOverlay();
}
clearInterval(iv);
iv = false;
document.title = a[0];
a = [];
}
//
// Add aevent handlers
function addEvent(el,name,func){
if(name.match(" ")){
var a = name.split(' ');
for(var i=0;i<a.length;i++){
addEvent( el, a[i], func);
}
}
if(el.addEventListener){
el.removeEventListener(name, func, false);
el.addEventListener(name, func, false);
}
else {
el.detachEvent('on'+name, func);
el.attachEvent('on'+name, func);
}
}
function check_permission(){
// Check whether the current desktop supports notifications and if they are authorised,
// PERMISSION_GRANTED (yes they are supported and permission is granted),
// PERMISSION_DENIED (yes they are supported, permission has not been granted),
// -1 (Notifications are not supported)
// IE9
if(("external" in window) && ("msIsSiteMode" in window.external)){
return window.external.msIsSiteMode()? PERMISSION_GRANTED : PERMISSION_UNKNOWN;
}
else if("webkitNotifications" in window){
return window.webkitNotifications.checkPermission() === 0 ? PERMISSION_GRANTED : PERMISSION_DENIED;
}
else if("mozNotification" in window.navigator){
return PERMISSION_GRANTED;
}
else {
return PERMISSION_UNKNOWN;
}
}
function update_permission(){
// Define the current state
window.Notification.permission = check_permission();
return window.Notification.permission;
}
if(!Object(window.Notification).permission){
//
// Bind event handlers to the body
addEvent(window, "focus scroll click", swapTitleCancel);
// Assign it.
window.Notification = function(message, options){
// ensure this is an instance
if(!(this instanceof window.Notification)){
return new window.Notification(message,options);
}
var n, self = this;
//
options = options || {};
this.body = options.body || '';
this.icon = options.icon || '';
this.lang = options.lang || '';
this.tag = options.tag || '';
this.close = function(){
// remove swapTitle
swapTitleCancel();
// Close
if(Object(n).close){
n.close();
}
self.onclose();
};
this.onclick = function(){};
this.onclose = function(){};
//
// Swap document.title
//
swaptitle(message);
//
// Create Desktop Notifications
//
if(("external" in window) && ("msIsSiteMode" in window.external)){
if(window.external.msIsSiteMode()){
window.external.msSiteModeActivate();
if(this.icon){
window.external.msSiteModeSetIconOverlay(this.icon, message);
}
}
}
else if("webkitNotifications" in window){
if(window.webkitNotifications.checkPermission() === 0){
n = window.webkitNotifications.createNotification(this.icon, message, this.body );
n.show();
n.onclick = function(){
// Fire any user bound events to the onclick function
self.onclick();
// redirect the user back to the page
window.focus();
setTimeout( function(){ n.cancel(); }, 1000);
};
}
}
else if( "mozNotification" in window.navigator ){
var m = window.navigator.mozNotification.createNotification( message, this.body, this.icon );
m.show();
}
};
window.Notification.requestPermission = function(cb){
// Setup
// triggers the authentication to create a notification
cb = cb || function(){};
// IE9
if(("external" in window) && ("msIsSiteMode" in window.external)){
try{
if( !window.external.msIsSiteMode() ){
window.external.msAddSiteMode();
cb( PERMISSION_UNKNOWN );
}
}
catch(e){}
cb( update_permission() );
}
else if("webkitNotifications" in window){
window.webkitNotifications.requestPermission(function(){
cb( update_permission() );
});
}
else {
cb( update_permission() );
}
};
// Get the current permission
update_permission();
}
})(window, document);

View file

@ -1,99 +0,0 @@
/**
* Simple slideout menu implementation.
*/
function slideoutMenu(viewport, menu) {
var touchStartPos = null;
var touchCurPos = null;
var touchStartTime = 0;
var menuWidth = parseFloat(window.getComputedStyle(menu).width);
var menuIsOpen = false;
var menuIsMoving = false;
function toggleMenu(state) {
menuIsOpen = state;
viewport.classList.toggle("menu-open", state);
}
function disableSlideout() {
viewport.removeEventListener("ontouchstart", onTouchStart);
}
function onTouchStart(e) {
if (e.touches.length !== 1) {
onTouchEnd();
return false;
}
var touch = e.touches.item(0);
viewport.classList.toggle("menu-dragging", true);
if ((!menuIsOpen && touch.screenX < 50) || (menuIsOpen && touch.screenX > menuWidth)) {
touchStartPos = touch;
touchCurPos = touch;
touchStartTime = Date.now();
viewport.addEventListener("touchmove", onTouchMove);
viewport.addEventListener("touchend", onTouchEnd);
}
}
function onTouchMove(e) {
var touch = touchCurPos = e.touches.item(0);
var setX = touch.screenX - touchStartPos.screenX;
if (Math.abs(setX > 30)) {
menuIsMoving = true;
}
if (!menuIsMoving && Math.abs(touch.screenY - touchStartPos.screenY) > 30) {
onTouchEnd();
return;
}
if (menuIsOpen) {
setX += menuWidth;
}
if (setX > menuWidth) {
setX = menuWidth;
} else if (setX < 0) {
setX = 0;
}
viewport.style.transform = "translate3d(" + setX + "px, 0, 0)";
if (menuIsMoving) {
e.preventDefault();
e.stopPropagation();
}
}
function onTouchEnd() {
var diff = touchCurPos.screenX - touchStartPos.screenX;
var absDiff = Math.abs(diff);
if (absDiff > menuWidth / 2 || Date.now() - touchStartTime < 180 && absDiff > 50) {
toggleMenu(diff > 0);
}
viewport.removeEventListener("touchmove", onTouchMove);
viewport.removeEventListener("touchend", onTouchEnd);
viewport.classList.toggle("menu-dragging", false);
viewport.style.transform = null;
touchStartPos = null;
touchCurPos = null;
touchStartTime = 0;
menuIsMoving = false;
}
viewport.addEventListener("touchstart", onTouchStart);
return {
disable: disableSlideout,
toggle: toggleMenu,
isOpen: function() {
return menuIsOpen;
}
};
}

File diff suppressed because it is too large Load diff

View file

@ -1,16 +0,0 @@
"use strict";
/*
* This is a separate file for two reasons:
* 1. CSP policy does not allow inline javascript
* 2. It has to be a small javascript executed before all other scripts,
* so that the timeout can be triggered while slow JS is loading
*/
setTimeout(function() {
var element = document.getElementById("loading-slow");
if (element) {
element.style.display = "block";
}
}, 5000);

File diff suppressed because it is too large Load diff

View file

@ -1,26 +0,0 @@
{
"name": "The Lounge",
"short_name": "The Lounge",
"description": "Self-hosted web IRC client",
"display": "standalone",
"theme_color": "#455164",
"background_color": "#455164",
"icons":
[
{
"src": "img/touch-icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/apple-touch-icon-120x120.png",
"sizes": "120x120",
"type": "image/png"
},
{
"src": "img/favicon.png",
"sizes": "64x64",
"type": "image/png"
}
]
}

View file

@ -1,2 +0,0 @@
User-agent: *
Disallow: /

View file

@ -1,123 +0,0 @@
/*
Crypto theme for The Lounge.
Installation instructions can be found here
https://thelounge.github.io/docs/server/configuration.html#theme
Author: Aynik
GitHub: https://github.com/aynik
*/
@font-face {
font-family: Inconsolata-g;
src: url("../css/fonts/inconsolatag.woff") format("woff");
}
body {
background: #000;
font: 16px Inconsolata-g, monospace;
}
a,
#chat a {
color: #00ff0e;
}
a:hover,
#chat a:hover {
color: #3eff48;
}
#windows .window h2 {
color: #666;
font: regular 14px Lato, sans-serif;
border-bottom: none;
}
.container {
margin: 40px auto;
}
#sign-in label {
font: 14px Lato, sans-serif;
color: #666;
}
#sign-in label input {
margin-top: 10px !important;
font-size: 14px;
}
.btn {
border-color: #00ff0e;
color: #00ff0e;
}
.btn:disabled,
.btn:hover {
background: #00ff0e;
}
#settings .opt {
line-height: 20px;
font-size: 12px;
}
#sign-in .remember {
font-size: 12px;
line-height: 30px;
}
#sidebar .chan:first-child {
color: #00ff0e;
}
#sidebar .chan .name:after {
background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100%);
}
#sidebar button,
#sidebar .chan,
#sidebar .sign-out,
#chat .time,
#chat .count:before,
#sidebar .empty {
color: #666;
}
#chat .unhandled .from {
color: #ddd;
}
#sidebar .active {
color: #fff;
}
#windows .header .topic,
.messages .msg,
.sidebar {
font: 12px Inconsolata-g, monospace;
line-height: 1.8;
}
#chat .user {
color: black;
font-weight: bold;
}
#windows #form .input {
font-family: inherit;
font-size: 12px;
}
#windows select.input {
height: 38px;
}
#footer .icon {
color: #666;
}
.tooltipped:after {
font-family: Inconsolata-g, monospace;
}

View file

@ -1,62 +0,0 @@
/**
* This is just an empty theme.
*/
body {
margin: 0;
}
.signed-out #main {
left: 5px;
}
#sidebar {
bottom: 52px;
}
#footer {
border-radius: 2px;
bottom: 4px;
left: 5px;
width: 210px;
}
#main {
bottom: 4px;
right: 5px;
top: 4px;
}
#chat .unhandled .from {
color: #ddd;
}
#windows .window:before {
background: #f4f4f4;
background-image: linear-gradient(#f4f4f4, #ececec);
border-bottom: 1px solid #d7d7d7;
content: " ";
display: block;
height: 10px;
position: relative;
z-index: 10;
}
#windows .window {
border-radius: 2px;
}
@media (max-width: 768px) {
#sidebar {
left: -220px;
}
#footer {
left: -215px;
width: 215px;
}
#main {
left: 5px;
}
}

View file

@ -1,219 +0,0 @@
/*
Morning theme for The Lounge.
Has a bit more eye-friendly color scheme.
Installation instructions can be found here
https://thelounge.github.io/docs/server/configuration.html#theme
Author: Riku Rouvila
GitHub: https://github.com/rikukissa
*/
/*
BACKGROUND #333c4a
INPUT BACKGROUND #2e3642
PRIMARY #fefefe
SECONDARY #99a2b4
BORDERS #2a323d
QUIT #d0907d
*/
body {
color: #ccc;
}
#main,
#chat .sidebar,
#windows .chan,
#windows .window {
background: #333c4a;
}
#windows .header .topic,
#windows #form .input,
.messages .msg,
.sidebar {
font-family: inherit;
font-size: 13px;
}
#chat .count {
background-color: #2e3642;
}
#chat .search {
color: #ccc;
padding: 15px 16px;
}
#chat .search::-webkit-input-placeholder {
color: #99a2b4;
opacity: .5;
}
/* Borders */
#chat .from,
#windows .header,
#chat .user-mode:before,
#chat .sidebar {
border-color: #2a323d;
}
/* User list */
#chat .user-mode {
color: #fefefe;
}
/* Nicknames */
#chat .user {
color: #b0bacf;
}
#chat .user:hover {
color: #fefefe;
}
#chat.colored-nicks .user.color-1 { color: #f7adf7; }
#chat.colored-nicks .user.color-2 { color: #abf99f; }
#chat.colored-nicks .user.color-3 { color: #86efdc; }
#chat.colored-nicks .user.color-4 { color: #b76ee5; }
#chat.colored-nicks .user.color-5 { color: #f9a4b3; }
#chat.colored-nicks .user.color-6 { color: #f7999a; }
#chat.colored-nicks .user.color-7 { color: #f497b9; }
#chat.colored-nicks .user.color-8 { color: #f9a9d7; }
#chat.colored-nicks .user.color-9 { color: #7fa2e2; }
#chat.colored-nicks .user.color-10 { color: #a8b8ff; }
#chat.colored-nicks .user.color-11 { color: #ad88fc; }
#chat.colored-nicks .user.color-12 { color: #f4aead; }
#chat.colored-nicks .user.color-13 { color: #fc71ab; }
#chat.colored-nicks .user.color-14 { color: #ff72e0; }
#chat.colored-nicks .user.color-15 { color: #8cb6ea; }
#chat.colored-nicks .user.color-16 { color: #f9857c; }
#chat.colored-nicks .user.color-17 { color: #ed9b82; }
#chat.colored-nicks .user.color-18 { color: #8df484; }
#chat.colored-nicks .user.color-19 { color: #ffcce3; }
#chat.colored-nicks .user.color-20 { color: #efcc81; }
#chat.colored-nicks .user.color-21 { color: #90a1ed; }
#chat.colored-nicks .user.color-22 { color: #f4d484; }
#chat.colored-nicks .user.color-23 { color: #97ea70; }
#chat.colored-nicks .user.color-24 { color: #fcbbba; }
#chat.colored-nicks .user.color-25 { color: #eef975; }
#chat.colored-nicks .user.color-26 { color: #c7ff93; }
#chat.colored-nicks .user.color-27 { color: #ffade1; }
#chat.colored-nicks .user.color-28 { color: #98ecf2; }
#chat.colored-nicks .user.color-29 { color: #7187f2; }
#chat.colored-nicks .user.color-30 { color: #9676e2; }
#chat.colored-nicks .user.color-31 { color: #f2a4eb; }
#chat.colored-nicks .user.color-32 { color: #85f27d; }
#chat a {
color: #428bca;
}
#chat button:hover {
opacity: 1;
}
/* Message form */
#form {
background: #2a323d;
border-color: #242a33;
}
#windows #form .input {
background-color: #2e3642 !important;
border-color: #242a33 !important;
color: #ccc;
}
#form #nick {
background: #242a33;
color: #ccc;
}
#form #submit:hover {
opacity: 1;
}
/* Buttons */
#chat .show-more-button {
background: #2e3642;
border-color: #242a33;
color: #ccc;
}
#chat .show-more-button:hover {
color: #fff;
}
#chat .header {
color: #99a2b4;
}
/* Notification dot on the top right corner of the menu icon */
#viewport .lt:after {
border-color: #333c4a;
}
#chat .unread-marker {
opacity: 1;
}
#chat .unread-marker-text:before {
background-color: #333c4a;
}
/* Setup text colors */
#chat .msg {
color: #f3f3f3;
}
#chat .message {
color: #fefefe;
}
#chat .self .text {
color: #99a2b4;
}
#chat .error,
#chat .error .from,
#chat .channel .highlight,
#chat .channel .highlight .from,
#chat .channel .highlight .text {
color: #f92772;
}
#chat .unhandled .from {
color: #99a2b4;
}
#chat .msg.quit .time,
#chat .msg.quit .from button {
color: #d0907d !important;
}
#chat .msg.topic {
color: #fefefe;
}
#chat .msg.join .time,
#chat .msg.join .from button {
color: #84ce88 !important;
}
/* Embeds */
#chat .toggle-content,
#chat .toggle-button {
background: #242a33;
color: #f3f3f3;
}
#chat .toggle-content img {
float: left;
margin-right: .5em;
}
#chat .toggle-content .body {
color: #99a2b4;
}

View file

@ -1,245 +0,0 @@
/*
Zenburn theme for The Lounge.
Based on the Morning Theme by Riku Rouvila
Installation instructions can be found here
https://thelounge.github.io/docs/server/configuration.html#theme
Author: JP Smith
GitHub: https://github.com/japesinator
*/
/*
BACKGROUND #3f3f3f
INPUT BACKGROUND #434443
PRIMARY #dcdccc
SECONDARY #d2d39b
BORDERS #333333
QUIT #bc6c4c
*/
body {
background: #2b2b2b;
color: #dcdccc;
}
#main,
#chat .sidebar,
#windows .chan,
#windows .window {
background: #3f3f3f;
}
#windows .header .topic,
#windows #form .input,
.messages .msg,
.sidebar {
font-family: inherit;
font-size: 13px;
}
#settings,
#sign-in,
#connect .title {
color: #88b090;
}
#settings,
#sign-in,
#connect .opt {
color: #dcdccc;
}
#sidebar {
background: #2b2b2b;
}
#sidebar .chan .name:after {
background: linear-gradient(to right, rgba(43, 43, 43, 0) 0%, rgba(43, 43, 43, 1) 100%);
}
#footer {
background: #33332f;
border-top: 1px solid #000;
}
#chat .count {
background-color: #434443;
}
#chat .search {
color: #88b090;
padding: 15px 16px;
}
#chat .search::-webkit-input-placeholder {
color: #d2d39b;
opacity: .5;
}
/* Borders */
#chat .from,
#windows .header,
#chat .user-mode:before,
#chat .sidebar {
border-color: #333;
}
/* User list */
#chat .user-mode {
color: #dcdccc;
}
/* Nicknames */
#chat .user {
color: #bc8cbc;
}
#chat .user:hover {
color: #dcdccc;
}
#chat.colored-nicks .user.color-1 { color: #f7adf7; }
#chat.colored-nicks .user.color-2 { color: #abf99f; }
#chat.colored-nicks .user.color-3 { color: #86efdc; }
#chat.colored-nicks .user.color-4 { color: #b76ee5; }
#chat.colored-nicks .user.color-5 { color: #f9a4b3; }
#chat.colored-nicks .user.color-6 { color: #f7999a; }
#chat.colored-nicks .user.color-7 { color: #f497b9; }
#chat.colored-nicks .user.color-8 { color: #f9a9d7; }
#chat.colored-nicks .user.color-9 { color: #7fa2e2; }
#chat.colored-nicks .user.color-10 { color: #a8b8ff; }
#chat.colored-nicks .user.color-11 { color: #ad88fc; }
#chat.colored-nicks .user.color-12 { color: #f4aead; }
#chat.colored-nicks .user.color-13 { color: #fc71ab; }
#chat.colored-nicks .user.color-14 { color: #ff72e0; }
#chat.colored-nicks .user.color-15 { color: #8cb6ea; }
#chat.colored-nicks .user.color-16 { color: #f9857c; }
#chat.colored-nicks .user.color-17 { color: #ed9b82; }
#chat.colored-nicks .user.color-18 { color: #8df484; }
#chat.colored-nicks .user.color-19 { color: #ffcce3; }
#chat.colored-nicks .user.color-20 { color: #efcc81; }
#chat.colored-nicks .user.color-21 { color: #90a1ed; }
#chat.colored-nicks .user.color-22 { color: #f4d484; }
#chat.colored-nicks .user.color-23 { color: #97ea70; }
#chat.colored-nicks .user.color-24 { color: #fcbbba; }
#chat.colored-nicks .user.color-25 { color: #eef975; }
#chat.colored-nicks .user.color-26 { color: #c7ff93; }
#chat.colored-nicks .user.color-27 { color: #ffade1; }
#chat.colored-nicks .user.color-28 { color: #98ecf2; }
#chat.colored-nicks .user.color-29 { color: #7187f2; }
#chat.colored-nicks .user.color-30 { color: #9676e2; }
#chat.colored-nicks .user.color-31 { color: #f2a4eb; }
#chat.colored-nicks .user.color-32 { color: #85f27d; }
#chat a {
color: #8c8cbc;
}
#chat button:hover {
opacity: 1;
}
/* Message form */
#form {
background: #333;
border-color: #101010;
}
#windows #form .input {
background-color: #434443 !important;
border-color: #101010 !important;
color: #dcdccc !important;
}
#form #nick {
background: #101010;
color: #dcdccc;
}
#form #submit:hover {
opacity: 1;
}
/* Buttons */
#chat .show-more-button {
background: #434443;
border-color: #101010;
color: #dcdccc;
}
#chat .show-more-button:hover {
color: #fff;
}
#chat .header {
color: #d2d39b;
}
/* Notification dot on the top right corner of the menu icon */
#viewport .lt:after {
border-color: #3f3f3f;
}
#chat .unread-marker {
opacity: 1;
}
#chat .unread-marker-text:before {
background-color: #3f3f3f;
}
/* Setup text colors */
#chat .msg {
color: #ffcfaf;
}
#chat .message {
color: #dcdccc;
}
#chat .self .text {
color: #d2d39b;
}
#chat .error,
#chat .error .from,
#chat .channel .highlight,
#chat .channel .highlight .from,
#chat .channel .highlight .text {
color: #bc6c4c;
}
#chat .unhandled .from {
color: #aaa;
}
#chat .msg.quit .time,
#chat .msg.quit .from button {
color: #bc6c9c !important;
}
#chat .msg.topic {
color: #dcdccc;
}
#chat .msg.join .time,
#chat .msg.join .from button {
color: #8cd0d3 !important;
}
/* Embeds */
#chat .toggle-content,
#chat .toggle-button {
background: #93b3a3;
color: #dcdccc;
}
#chat .toggle-content img {
float: left;
margin-right: .5em;
}
#chat .toggle-content .body {
color: #d2d39b;
}

View file

@ -1,2 +0,0 @@
<span role="button" class="user {{colorClass from}}" data-name="{{from}}">{{mode}}{{from}}</span>
<span class="action-text">{{{parse text}}}</span>

View file

@ -1,18 +0,0 @@
<table class="channel-list">
<thead>
<tr>
<th class="channel">Channel</th>
<th class="users">Users</th>
<th class="topic">Topic</th>
</tr>
</thead>
<tbody>
{{#each channels}}
<tr>
<td class="channel">{{{parse channel}}}</td>
<td class="users">{{num_users}}</td>
<td class="topic">{{{parse topic}}}</td>
</tr>
{{/each}}
</tbody>
</table>

View file

@ -1,2 +0,0 @@
<span role="button" class="user {{colorClass from}}" data-name="{{from}}">{{from}}</span>
<b>{{ctcpType}}</b> {{{parse ctcpMessage}}}

View file

@ -1,9 +0,0 @@
<span role="button" class="user {{colorClass from}}" data-name="{{from}}">{{from}}</span>
invited
{{#if invitedYou}}
you
{{else}}
<span role="button" class="user {{colorClass invited}}" data-name="{{invited}}">{{invited}}</span>
{{/if}}
to
{{{parse channel}}}

View file

@ -1,3 +0,0 @@
<span role="button" class="user {{colorClass from}}" data-name="{{from}}">{{mode}}{{from}}</span>
<i class="hostmask">({{hostmask}})</i>
has joined the channel

View file

@ -1,6 +0,0 @@
<span role="button" class="user {{colorClass from}}" data-name="{{from}}">{{mode}}{{from}}</span>
has kicked
<span role="button" class="user {{colorClass target}}" data-name="{{target}}">{{target}}</span>
{{#if text}}
<i class="part-reason">({{{parse text}}})</i>
{{/if}}

View file

@ -1,3 +0,0 @@
<span role="button" class="user {{colorClass from}}" data-name="{{from}}">{{mode}}{{from}}</span>
sets mode
{{{parse text}}}

View file

@ -1,3 +0,0 @@
<span role="button" class="user {{colorClass from}}" data-name="{{from}}">{{mode}}{{from}}</span>
is now known as
<span role="button" class="user {{colorClass new_nick}}" data-name="{{new_nick}}">{{mode}}{{new_nick}}</span>

View file

@ -1,6 +0,0 @@
<span role="button" class="user {{colorClass from}}" data-name="{{from}}">{{mode}}{{from}}</span>
<i class="hostmask">({{hostmask}})</i>
has left the channel
{{#if text}}
<i class="part-reason">({{{parse text}}})</i>
{{/if}}

View file

@ -1,6 +0,0 @@
<span role="button" class="user {{colorClass from}}" data-name="{{from}}">{{mode}}{{from}}</span>
<i class="hostmask">({{hostmask}})</i>
has quit
{{#if text}}
<i class="quit-reason">({{{parse text}}})</i>
{{/if}}

View file

@ -1,8 +0,0 @@
{{#if from}}
<span role="button" class="user {{colorClass from}}" data-name="{{from}}">{{mode}}{{from}}</span>
has changed the topic to:
{{else}}
The topic is:
{{/if}}
<span class="new-topic">{{{parse text}}}</span>

View file

@ -1,3 +0,0 @@
Topic set by
<span role="button" class="user {{colorClass nick}}" data-name="{{nick}}">{{mode}}{{nick}}</span>
on {{localetime when}}

View file

@ -1,35 +0,0 @@
<div>
<span role="button" class="user {{colorClass whois.nick}}" data-name="{{whois.nick}}">{{whois.nick}}</span>
<i class="hostmask">({{whois.user}}@{{whois.host}})</i>:
<b>{{whois.real_name}}</b>
</div>
{{#if whois.account}}
<div>
<span role="button" class="user {{colorClass whois.nick}}" data-name="{{whois.nick}}">{{whois.nick}}</span>
is logged in as <b>{{whois.account}}</b>
</div>
{{/if}}
{{#if whois.channels}}
<div>
<span role="button" class="user {{colorClass whois.nick}}" data-name="{{whois.nick}}">{{whois.nick}}</span>
is on the following channels: {{{parse whois.channels}}}
</div>
{{/if}}
{{#if whois.server}}
<div>
<span role="button" class="user {{colorClass whois.nick}}" data-name="{{whois.nick}}">{{whois.nick}}</span>
is connected to {{whois.server}} <i>({{whois.server_info}})</i>
</div>
{{/if}}
{{#if whois.secure}}
<div>
<span role="button" class="user {{colorClass whois.nick}}" data-name="{{whois.nick}}">{{whois.nick}}</span>
is using a secure connection
</div>
{{/if}}
{{#if whois.away}}
<div>
<span role="button" class="user {{colorClass whois.nick}}" data-name="{{whois.nick}}">{{whois.nick}}</span>
is away <i>({{whois.away}})</i>
</div>
{{/if}}

View file

@ -1,7 +0,0 @@
{{#each channels}}
<div data-id="{{id}}" data-target="#chan-{{id}}" data-title="{{name}}" class="chan {{type}}">
<span class="badge{{#if highlight}} highlight{{/if}}" data-count="{{unread}}">{{#if unread}}{{roundBadgeNumber unread}}{{/if}}</span>
<button class="close" aria-label="Close"></button>
<span class="name" title="{{name}}">{{name}}</span>
</div>
{{/each}}

View file

@ -1,26 +0,0 @@
{{#each channels}}
<div id="chan-{{id}}" data-title="{{name}}" data-id="{{id}}" data-type="{{type}}" data-target="#chan-{{id}}" class="chan {{type}}">
<div class="header">
<button class="lt" aria-label="Toggle channel list"></button>
{{#equal type "channel"}}
<span class="rt-tooltip tooltipped tooltipped-w" aria-label="Toggle user list">
<button class="rt" aria-label="Toggle user list"></button>
</span>
{{/equal}}
<button class="menu" aria-label="Open the context menu"></button>
<span class="title">{{name}}</span>
<span title="{{topic}}" class="topic">{{{parse topic}}}</span>
</div>
<div class="chat">
<div class="show-more {{#equal messages.length 100}}show{{/equal}}">
<button class="show-more-button" data-id="{{id}}">
Show older messages
</button>
</div>
<div class="messages"></div>
</div>
<aside class="sidebar">
<div class="users"></div>
</aside>
</div>
{{/each}}

View file

@ -1 +0,0 @@
<li class="context-menu-divider" />

View file

@ -1,3 +0,0 @@
<li class="context-menu-item context-menu-{{class}}" data-action="{{class}}"{{#if data}} data-data="{{data}}"{{/if}}>
{{text}}
</li>

View file

@ -1,23 +0,0 @@
<div class="msg {{type}}{{#if self}} self{{/if}}{{#if highlight}} highlight{{/if}}" id="msg-{{id}}">
<span class="time" title="{{localetime time}}">
{{tz time}}
</span>
<span class="from">
{{#if from}}
<span role="button" class="user {{colorClass from}}" data-name="{{from}}">{{mode}}{{from}}</span>
{{/if}}
</span>
{{#equal type "toggle"}}
<span class="text">
<div class="force-newline">
<button id="toggle-{{id}}" class="toggle-button" aria-label="Toggle prefetched media">···</button>
</div>
{{#if toggle}}
{{partial "toggle"}}
{{/if}}
</span>
{{else}}
<span class="text">{{{parse text}}}</span>
{{/equal}}
</span>
</div>

View file

@ -1,9 +0,0 @@
<div class="msg {{type}}{{#if self}} self{{/if}}{{#if highlight}} highlight{{/if}}" id="msg-{{id}}">
<span class="time" title="{{localetime time}}">
{{tz time}}
</span>
<span class="from"></span>
<span class="text">
{{partial template}}
</span>
</div>

View file

@ -1,11 +0,0 @@
<div class="msg {{type}}{{#if self}} self{{/if}}{{#if highlight}} highlight{{/if}}">
<span class="time" title="{{localetime time}}">
{{tz time}}
</span>
<span class="from">[{{command}}]</span>
<span class="text">
{{#each params}}
<span>{{this}}</span>
{{/each}}
</span>
</div>

View file

@ -1,5 +0,0 @@
{{#each networks}}
<section id="network-{{id}}" class="network" data-id="{{id}}" data-nick="{{nick}}" data-options="{{toJSON serverOptions}}">
{{partial "chan"}}
</section>
{{/each}}

View file

@ -1,19 +0,0 @@
{{#toggle}}
<div class="toggle-content">
{{#equal type "image"}}
<a href="{{link}}" target="_blank">
<img src="{{link}}">
</a>
{{else}}
<a href="{{link}}" target="_blank">
{{#if thumb}}
<img src="{{thumb}}" class="thumb">
{{/if}}
<div class="head">{{head}}</div>
<div class="body">
{{body}}
</div>
</a>
{{/equal}}
</div>
{{/toggle}}

View file

@ -1,3 +0,0 @@
<div class="unread-marker">
<span class="unread-marker-text"></span>
</div>

View file

@ -1,18 +0,0 @@
{{#if users.length}}
<div class="count">
<input class="search" placeholder="{{users users.length}}" aria-label="Search among the user list">
</div>
{{/if}}
<div class="names">
{{#diff "reset"}}{{/diff}}
{{#each users}}
{{#diff mode}}
{{#unless @first}}
</div>
{{/unless}}
<div class="user-mode {{modes mode}}">
{{/diff}}
<span role="button" class="user {{colorClass name}}" data-name="{{name}}">{{mode}}{{name}}</span>
{{/each}}
</div>
</div>

View file

@ -1,371 +0,0 @@
"use strict";
module.exports = {
//
// Set the server mode.
// Public servers does not require authentication.
//
// Set to 'false' to enable users.
//
// @type boolean
// @default true
//
public: true,
//
// IP address or hostname for the web server to listen on.
// Setting this to undefined will listen on all interfaces.
//
// @type string
// @default undefined
//
host: undefined,
//
// Set the port to listen on.
//
// @type int
// @default 9000
//
port: 9000,
//
// Set the local IP to bind to for outgoing connections. Leave to undefined
// to let the operating system pick its preferred one.
//
// @type string
// @default undefined
//
bind: undefined,
//
// Sets whether the server is behind a reverse proxy and should honor the
// X-Forwarded-For header or not.
//
// @type boolean
// @default false
//
reverseProxy: false,
//
// Set the default theme.
//
// @type string
// @default "themes/example.css"
//
theme: "themes/example.css",
//
// Autoload users
//
// When this setting is enabled, your 'users/' folder will be monitored. This is useful
// if you want to add/remove users while the server is running.
//
// @type boolean
// @default true
//
autoload: true,
//
// Prefetch URLs
//
// If enabled, The Lounge will try to load thumbnails and site descriptions from
// URLs posted in channels.
//
// @type boolean
// @default false
//
prefetch: false,
//
// Prefetch URLs Image Preview size limit
//
// If prefetch is enabled, The Lounge will only display content under the maximum size.
// Default value is 512 (in kB)
//
// @type int
// @default 512
//
prefetchMaxImageSize: 512,
//
// Display network
//
// If set to false network settings will not be shown in the login form.
//
// @type boolean
// @default true
//
displayNetwork: true,
//
// Lock network
//
// If set to true, users will not be able to modify host, port and tls
// settings and will be limited to the configured network.
//
// @type boolean
// @default false
//
lockNetwork: false,
//
// WEBIRC support
//
// If enabled, The Lounge will pass the connecting user's host and IP to the
// IRC server. Note that this requires to obtain a password from the IRC network
// The Lounge will be connecting to and generally involves a lot of trust from the
// network you are connecting to.
//
// Format (standard): {"irc.example.net": "hunter1", "irc.example.org": "passw0rd"}
// Format (function):
// {"irc.example.net": function(client, args, trusted) {
// // here, we return a webirc object fed directly to `irc-framework`
// return {username: "thelounge", password: "hunter1", address: args.ip, hostname: "webirc/"+args.hostname};
// }}
//
// @type string | function(client, args):object(webirc)
// @default null
webirc: null,
//
// Log settings
//
// Logging has to be enabled per user. If enabled, logs will be stored in
// the 'logs/<user>/<network>/' folder.
//
// @type object
// @default {}
//
logs: {
//
// Timestamp format
//
// @type string
// @default "YYYY-MM-DD HH:mm:ss"
//
format: "YYYY-MM-DD HH:mm:ss",
//
// Timezone
//
// @type string
// @default "UTC+00:00"
//
timezone: "UTC+00:00"
},
//
// Maximum number of history lines per channel
//
// Defines the maximum number of history lines that will be kept in
// memory per channel/query, in order to reduce the memory usage of
// the server. Negative means unlimited.
//
// @type integer
// @default -1
maxHistory: -1,
//
// Default values for the 'Connect' form.
//
// @type object
// @default {}
//
defaults: {
//
// Name
//
// @type string
// @default "Freenode"
//
name: "Freenode",
//
// Host
//
// @type string
// @default "chat.freenode.net"
//
host: "chat.freenode.net",
//
// Port
//
// @type int
// @default 6697
//
port: 6697,
//
// Password
//
// @type string
// @default ""
//
password: "",
//
// Enable TLS/SSL
//
// @type boolean
// @default true
//
tls: true,
//
// Nick
//
// @type string
// @default "lounge-user"
//
nick: "lounge-user",
//
// Username
//
// @type string
// @default "lounge-user"
//
username: "lounge-user",
//
// Real Name
//
// @type string
// @default "The Lounge User"
//
realname: "The Lounge User",
//
// Channels
// This is a comma-separated list.
//
// @type string
// @default "#thelounge"
//
join: "#thelounge"
},
//
// Set socket.io transports
//
// @type array
// @default ["polling", "websocket"]
//
transports: ["polling", "websocket"],
//
// Run The Lounge using encrypted HTTP/2.
// This will fallback to regular HTTPS if HTTP/2 is not supported.
//
// @type object
// @default {}
//
https: {
//
// Enable HTTP/2 / HTTPS support.
//
// @type boolean
// @default false
//
enable: false,
//
// Path to the key.
//
// @type string
// @example "sslcert/key.pem"
// @default ""
//
key: "",
//
// Path to the certificate.
//
// @type string
// @example "sslcert/key-cert.pem"
// @default ""
//
certificate: ""
},
//
// Run The Lounge with identd support.
//
// @type object
// @default {}
//
identd: {
//
// Run the identd daemon on server start.
//
// @type boolean
// @default false
//
enable: false,
//
// Port to listen for ident requests.
//
// @type int
// @default 113
//
port: 113
},
//
// Enable oidentd support using the specified file
//
// Example: oidentd: "~/.oidentd.conf",
//
// @type string
// @default null
//
oidentd: null,
//
// LDAP authentication settings (only available if public=false)
// @type object
// @default {}
//
ldap: {
//
// Enable LDAP user authentication
//
// @type boolean
// @default false
//
enable: false,
//
// LDAP server URL
//
// @type string
//
url: "ldaps://example.com",
//
// LDAP base dn
//
// @type string
//
baseDN: "ou=accounts,dc=example,dc=com",
//
// LDAP primary key
//
// @type string
// @default "uid"
//
primaryKey: "uid"
},
// Enables extra debugging output. Turn this on if you experience
// IRC connection issues and want to file a bug report.
//
// @type boolean
// @default false
//
debug: false,
};

View file

@ -1,16 +0,0 @@
#!/usr/bin/env node
"use strict";
process.chdir(__dirname);
// Perform node version check before loading any other files or modules
// Doing this check as soon as possible allows us to avoid ES6 parser errors or other issues
var pkg = require("./package.json");
if (!require("semver").satisfies(process.version, pkg.engines.node)) {
console.error("=== WARNING!");
console.error("=== The oldest supported Node.js version is", pkg.engines.node);
console.error("=== We strongly encourage you to upgrade, see https://nodejs.org/en/download/package-manager/ for more details\n");
}
require("./src/command-line");

View file

@ -1,71 +0,0 @@
{
"name": "thelounge",
"description": "The self-hosted Web IRC client",
"version": "2.1.0",
"preferGlobal": true,
"bin": {
"lounge": "index.js"
},
"repository": {
"type": "git",
"url": "https://github.com/thelounge/lounge.git"
},
"homepage": "https://thelounge.github.io/",
"scripts": {
"coverage": "istanbul cover node_modules/mocha/bin/_mocha -r test/fixtures/env.js test/**/*.js",
"start": "node index",
"build": "npm-run-all build:*",
"build:font-awesome": "node scripts/build-fontawesome.js",
"build:libs": "uglifyjs client/js/libs/*.js client/js/libs/jquery/*.js client/js/libs/handlebars/*.js -o client/js/libs.min.js --source-map client/js/libs.min.js.map --source-map-url libs.min.js.map -p relative",
"build:handlebars": "handlebars client/views/ -e tpl -f client/js/lounge.templates.js",
"test": "npm-run-all -c test:mocha lint",
"test:mocha": "mocha -r test/fixtures/env.js test/**/*.js",
"lint": "npm-run-all -c lint:js lint:css",
"lint:js": "npm-run-all -c lint:js:es5 lint:js:es6",
"lint:js:es5": "eslint --parser-options=\"ecmaVersion:5\" client/",
"lint:js:es6": "eslint --ignore-pattern client/ .",
"lint:css": "stylelint \"**/*.css\"",
"prepublish": "npm run build"
},
"keywords": [
"browser",
"web",
"chat",
"client",
"irc",
"server"
],
"license": "MIT",
"engines": {
"node": ">=4.2.0"
},
"dependencies": {
"bcrypt-nodejs": "0.0.3",
"cheerio": "0.20.0",
"colors": "1.1.2",
"commander": "2.9.0",
"event-stream": "3.3.2",
"express": "4.13.4",
"fs-extra": "0.30.0",
"irc-framework": "2.5.0",
"lodash": "4.11.2",
"moment": "2.13.0",
"read": "1.0.7",
"request": "2.74.0",
"semver": "5.1.0",
"socket.io": "1.4.5",
"spdy": "3.3.2",
"ldapjs": "1.0.0"
},
"devDependencies": {
"chai": "3.5.0",
"eslint": "3.6.0",
"font-awesome": "4.6.3",
"handlebars": "4.0.5",
"istanbul": "0.4.5",
"mocha": "3.0.2",
"npm-run-all": "3.1.0",
"stylelint": "7.3.1",
"uglify-js": "2.7.3"
}
}

View file

@ -1,26 +0,0 @@
"use strict";
var fs = require("fs-extra");
var srcDir = "./node_modules/font-awesome/fonts/";
var destDir = "./client/fonts/";
var fonts = [
"fontawesome-webfont.woff",
"fontawesome-webfont.woff2"
];
fs.ensureDir(destDir, function(dirErr) {
if (dirErr) {
console.error(dirErr);
}
fonts.forEach(function(font) {
fs.copy(srcDir + font, destDir + font, function(err) {
if (err) {
console.error(err);
} else {
console.log(font + " successfully installed.");
}
});
});
});

View file

@ -1,14 +0,0 @@
#!/bin/sh
set -e
if [ -z "$1" ]; then
echo "No pull request ID was specified."
exit 1
fi
git fetch https://github.com/thelounge/lounge.git refs/pull/${1}/head
git checkout FETCH_HEAD
npm install
npm test || true
npm start

View file

@ -1,464 +0,0 @@
"use strict";
var _ = require("lodash");
var pkg = require("../package.json");
var Chan = require("./models/chan");
var crypto = require("crypto");
var userLog = require("./userLog");
var Msg = require("./models/msg");
var Network = require("./models/network");
var ircFramework = require("irc-framework");
var Helper = require("./helper");
module.exports = Client;
var id = 0;
var events = [
"connection",
"unhandled",
"ctcp",
"error",
"invite",
"join",
"kick",
"mode",
"motd",
"message",
"link",
"names",
"nick",
"part",
"quit",
"topic",
"welcome",
"list",
"whois"
];
var inputs = [
"ctcp",
"msg",
"part",
"action",
"connect",
"disconnect",
"invite",
"kick",
"mode",
"nick",
"notice",
"query",
"quit",
"raw",
"topic",
"list",
].reduce(function(plugins, name) {
var path = "./plugins/inputs/" + name;
var plugin = require(path);
plugin.commands.forEach(command => plugins[command] = plugin);
return plugins;
}, {});
function Client(manager, name, config) {
if (typeof config !== "object") {
config = {};
}
_.merge(this, {
activeChannel: -1,
config: config,
id: id++,
name: name,
networks: [],
sockets: manager.sockets,
manager: manager
});
var client = this;
if (client.name && !client.config.token) {
client.updateToken(function(token) {
client.manager.updateUser(client.name, {token: token});
});
}
var delay = 0;
(client.config.networks || []).forEach(n => {
setTimeout(function() {
client.connect(n);
}, delay);
delay += 1000;
});
if (client.name) {
log.info("User '" + client.name + "' loaded");
}
}
Client.prototype.emit = function(event, data) {
if (this.sockets !== null) {
this.sockets.in(this.id).emit(event, data);
}
if (this.config.log === true) {
if (event === "msg") {
var target = this.find(data.chan);
if (target) {
var chan = target.chan.name;
if (target.chan.type === Chan.Type.LOBBY) {
chan = target.network.host;
}
userLog.write(
this.name,
target.network.host,
chan,
data.msg
);
}
}
}
};
Client.prototype.find = function(channelId) {
var network = null;
var chan = null;
for (var i in this.networks) {
var n = this.networks[i];
chan = _.find(n.channels, {id: channelId});
if (chan) {
network = n;
break;
}
}
if (network && chan) {
return {
network: network,
chan: chan
};
}
return false;
};
Client.prototype.connect = function(args) {
var config = Helper.config;
var client = this;
var nick = args.nick || "lounge-user";
var webirc = null;
var channels = [];
if (args.channels) {
var badName = false;
args.channels.forEach(chan => {
if (!chan.name) {
badName = true;
return;
}
channels.push(new Chan({
name: chan.name
}));
});
if (badName && client.name) {
log.warn("User '" + client.name + "' on network '" + args.name + "' has an invalid channel which has been ignored");
}
// `join` is kept for backwards compatibility when updating from versions <2.0
// also used by the "connect" window
} else if (args.join) {
channels = args.join
.replace(/,/g, " ")
.split(/\s+/g)
.map(function(chan) {
return new Chan({
name: chan
});
});
}
var network = new Network({
name: args.name || "",
host: args.host || "",
port: parseInt(args.port, 10) || (args.tls ? 6697 : 6667),
tls: !!args.tls,
password: args.password,
username: args.username || nick.replace(/[^a-zA-Z0-9]/g, ""),
realname: args.realname || "The Lounge User",
commands: args.commands,
ip: args.ip,
hostname: args.hostname,
channels: channels,
});
network.setNick(nick);
client.networks.push(network);
client.emit("network", {
networks: [network]
});
if (config.lockNetwork) {
// This check is needed to prevent invalid user configurations
if (args.host && args.host.length > 0 && args.host !== config.defaults.host) {
network.channels[0].pushMessage(client, new Msg({
type: Msg.Type.ERROR,
text: "Hostname you specified is not allowed."
}));
return;
}
network.host = config.defaults.host;
network.port = config.defaults.port;
network.tls = config.defaults.tls;
}
if (network.host.length === 0) {
network.channels[0].pushMessage(client, new Msg({
type: Msg.Type.ERROR,
text: "You must specify a hostname to connect."
}));
return;
}
if (config.webirc && network.host in config.webirc) {
args.ip = args.ip || (client.config && client.config.ip) || client.ip;
args.hostname = args.hostname || (client.config && client.config.hostname) || client.hostname || args.ip;
if (args.ip) {
if (config.webirc[network.host] instanceof Function) {
webirc = config.webirc[network.host](client, args);
} else {
webirc = {
password: config.webirc[network.host],
username: pkg.name,
address: args.ip,
hostname: args.hostname
};
}
} else {
log.warn("Cannot find a valid WEBIRC configuration for " + nick
+ "!" + network.username + "@" + network.host);
}
}
network.irc = new ircFramework.Client();
network.irc.requestCap([
"echo-message",
"znc.in/self-message",
]);
events.forEach(plugin => {
var path = "./plugins/irc-events/" + plugin;
require(path).apply(client, [
network.irc,
network
]);
});
network.irc.connect({
version: pkg.name + " " + Helper.getVersion() + " -- " + pkg.homepage,
host: network.host,
port: network.port,
nick: nick,
username: network.username,
gecos: network.realname,
password: network.password,
tls: network.tls,
localAddress: config.bind,
rejectUnauthorized: false,
auto_reconnect: true,
auto_reconnect_wait: 10000 + Math.floor(Math.random() * 1000), // If multiple users are connected to the same network, randomize their reconnections a little
auto_reconnect_max_retries: 360, // At least one hour (plus timeouts) worth of reconnections
webirc: webirc,
});
};
Client.prototype.updateToken = function(callback) {
var client = this;
crypto.randomBytes(48, function(err, buf) {
if (err) {
throw err;
}
callback(client.config.token = buf.toString("hex"));
});
};
Client.prototype.setPassword = function(hash, callback) {
var client = this;
client.updateToken(function(token) {
client.manager.updateUser(client.name, {
token: token,
password: hash
});
// re-read the hash off disk to ensure we use whatever is saved. this will
// prevent situations where the password failed to save properly and so
// a restart of the server would forget the change and use the old
// password again.
var user = client.manager.readUserConfig(client.name);
if (user.password === hash) {
client.config.password = hash;
callback(true);
} else {
callback(false);
}
});
};
Client.prototype.input = function(data) {
var client = this;
data.text.split("\n").forEach(line => {
data.text = line;
client.inputLine(data);
});
};
Client.prototype.inputLine = function(data) {
var client = this;
var text = data.text;
var target = client.find(data.target);
// This is either a normal message or a command escaped with a leading '/'
if (text.charAt(0) !== "/" || text.charAt(1) === "/") {
text = "say " + text.replace(/^\//, "");
} else {
text = text.substr(1);
}
var args = text.split(" ");
var cmd = args.shift().toLowerCase();
var irc = target.network.irc;
var connected = irc && irc.connection && irc.connection.connected;
if (cmd in inputs) {
var plugin = inputs[cmd];
if (connected || plugin.allowDisconnected) {
connected = true;
plugin.input.apply(client, [target.network, target.chan, cmd, args]);
}
} else if (connected) {
irc.raw(text);
}
if (!connected) {
target.chan.pushMessage(this, new Msg({
type: Msg.Type.ERROR,
text: "You are not connected to the IRC network, unable to send your command."
}));
}
};
Client.prototype.more = function(data) {
var client = this;
var target = client.find(data.target);
if (!target) {
return;
}
var chan = target.chan;
var count = chan.messages.length - (data.count || 0);
var messages = chan.messages.slice(Math.max(0, count - 100), count);
client.emit("more", {
chan: chan.id,
messages: messages
});
};
Client.prototype.open = function(data) {
var target = this.find(data);
if (target) {
target.chan.firstUnread = 0;
target.chan.unread = 0;
target.chan.highlight = false;
this.activeChannel = target.chan.id;
}
};
Client.prototype.sort = function(data) {
var self = this;
var type = data.type;
var order = data.order || [];
var sorted = [];
switch (type) {
case "networks":
order.forEach(i => {
var find = _.find(self.networks, {id: i});
if (find) {
sorted.push(find);
}
});
self.networks = sorted;
break;
case "channels":
var target = data.target;
var network = _.find(self.networks, {id: target});
if (!network) {
return;
}
order.forEach(i => {
var find = _.find(network.channels, {id: i});
if (find) {
sorted.push(find);
}
});
network.channels = sorted;
break;
}
self.save();
};
Client.prototype.names = function(data) {
var client = this;
var target = client.find(data.target);
if (!target) {
return;
}
client.emit("names", {
id: target.chan.id,
users: target.chan.users
});
};
Client.prototype.quit = function() {
var sockets = this.sockets.sockets;
var room = sockets.adapter.rooms[this.id] || [];
for (var user in room) {
var socket = sockets.adapter.nsp.connected[user];
if (socket) {
socket.disconnect();
}
}
this.networks.forEach(network => {
if (network.irc) {
network.irc.quit("Page closed");
}
});
};
var timer;
Client.prototype.save = function(force) {
var client = this;
if (Helper.config.public) {
return;
}
if (!force) {
clearTimeout(timer);
timer = setTimeout(function() {
client.save(true);
}, 1000);
return;
}
var json = {};
json.networks = this.networks.map(n => n.export());
client.manager.updateUser(client.name, json);
};

View file

@ -1,164 +0,0 @@
"use strict";
var _ = require("lodash");
var fs = require("fs");
var Client = require("./client");
var Helper = require("./helper");
var Oidentd = require("./oidentd");
module.exports = ClientManager;
function ClientManager() {
this.clients = [];
if (typeof Helper.config.oidentd === "string") {
this.identHandler = new Oidentd(Helper.config.oidentd);
}
}
ClientManager.prototype.findClient = function(name, token) {
for (var i in this.clients) {
var client = this.clients[i];
if (client.name === name || (token && token === client.config.token)) {
return client;
}
}
return false;
};
ClientManager.prototype.loadUsers = function() {
var users = this.getUsers();
for (var i in users) {
this.loadUser(users[i]);
}
};
ClientManager.prototype.loadUser = function(name) {
let json;
try {
json = this.readUserConfig(name);
} catch (e) {
log.error("Failed to read user config", e);
return;
}
if (!this.findClient(name)) {
this.clients.push(new Client(
this,
name,
json
));
}
};
ClientManager.prototype.getUsers = function() {
var users = [];
try {
var files = fs.readdirSync(Helper.USERS_PATH);
files.forEach(file => {
if (file.indexOf(".json") !== -1) {
users.push(file.replace(".json", ""));
}
});
} catch (e) {
log.error("Failed to get users", e);
return;
}
return users;
};
ClientManager.prototype.addUser = function(name, password) {
var users = this.getUsers();
if (users.indexOf(name) !== -1) {
return false;
}
try {
if (require("path").basename(name) !== name) {
throw new Error(name + " is an invalid username.");
}
var user = {
user: name,
password: password || "",
log: false,
networks: []
};
fs.writeFileSync(
Helper.getUserConfigPath(name),
JSON.stringify(user, null, "\t")
);
} catch (e) {
log.error("Failed to add user " + name, e);
throw e;
}
return true;
};
ClientManager.prototype.updateUser = function(name, opts) {
var users = this.getUsers();
if (users.indexOf(name) === -1) {
return false;
}
if (typeof opts === "undefined") {
return false;
}
var user = {};
try {
user = this.readUserConfig(name);
_.assign(user, opts);
fs.writeFileSync(
Helper.getUserConfigPath(name),
JSON.stringify(user, null, "\t")
);
} catch (e) {
log.error("Failed to update user", e);
return;
}
return true;
};
ClientManager.prototype.readUserConfig = function(name) {
var users = this.getUsers();
if (users.indexOf(name) === -1) {
return false;
}
var data = fs.readFileSync(Helper.getUserConfigPath(name), "utf-8");
return JSON.parse(data);
};
ClientManager.prototype.removeUser = function(name) {
var users = this.getUsers();
if (users.indexOf(name) === -1) {
return false;
}
try {
fs.unlinkSync(Helper.getUserConfigPath(name));
} catch (e) {
throw e;
}
return true;
};
ClientManager.prototype.autoload = function(/* sockets */) {
var self = this;
setInterval(function() {
var loaded = self.clients.map(c => c.name);
var added = _.difference(self.getUsers(), loaded);
added.forEach(name => self.loadUser(name));
var removed = _.difference(loaded, self.getUsers());
removed.forEach(name => {
var client = _.find(
self.clients, {
name: name
}
);
if (client) {
client.quit();
self.clients = _.without(self.clients, client);
log.info("User '" + name + "' disconnected");
}
});
}, 1000);
};

View file

@ -1,37 +0,0 @@
"use strict";
var ClientManager = new require("../clientManager");
var bcrypt = require("bcrypt-nodejs");
var program = require("commander");
var Helper = require("../helper");
program
.command("add <name>")
.description("Add a new user")
.action(function(name/* , password */) {
var manager = new ClientManager();
var users = manager.getUsers();
if (users.indexOf(name) !== -1) {
log.error("User '" + name + "' already exists.");
return;
}
require("read")({
prompt: "[thelounge] Enter password: ",
silent: true
}, function(err, password) {
if (!err) {
add(manager, name, password);
}
});
});
function add(manager, name, password) {
var salt = bcrypt.genSaltSync(8);
var hash = bcrypt.hashSync(password, salt);
manager.addUser(
name,
hash
);
log.info("User '" + name + "' created:");
log.info(Helper.getUserConfigPath(name));
}

View file

@ -1,19 +0,0 @@
"use strict";
var program = require("commander");
var child = require("child_process");
var Helper = require("../helper");
program
.command("config")
.description("Edit config: " + Helper.CONFIG_PATH)
.action(function() {
var child_spawn = child.spawn(
process.env.EDITOR || "vi",
[Helper.CONFIG_PATH],
{stdio: "inherit"}
);
child_spawn.on("error", function() {
log.error("Unable to open " + Helper.CONFIG_PATH + ". $EDITOR is not set, and vi was not found.");
});
});

View file

@ -1,25 +0,0 @@
"use strict";
var ClientManager = new require("../clientManager");
var program = require("commander");
var child = require("child_process");
var Helper = require("../helper");
program
.command("edit <name>")
.description("Edit user: " + Helper.getUserConfigPath("<name>"))
.action(function(name) {
var users = new ClientManager().getUsers();
if (users.indexOf(name) === -1) {
log.error("User '" + name + "' doesn't exist.");
return;
}
var child_spawn = child.spawn(
process.env.EDITOR || "vi",
[Helper.getUserConfigPath(name)],
{stdio: "inherit"}
);
child_spawn.on("error", function() {
log.error("Unable to open " + Helper.getUserConfigPath(name) + ". $EDITOR is not set, and vi was not found.");
});
});

View file

@ -1,46 +0,0 @@
"use strict";
global.log = require("../log.js");
var program = require("commander");
var fs = require("fs");
var fsextra = require("fs-extra");
var path = require("path");
var Helper = require("../helper");
program.version(Helper.getVersion(), "-v, --version");
program.option("");
program.option(" --home <path>" , "home path");
var argv = program.parseOptions(process.argv);
Helper.setHome(program.home || process.env.LOUNGE_HOME);
if (!fs.existsSync(Helper.CONFIG_PATH)) {
fsextra.ensureDirSync(Helper.HOME);
fs.chmodSync(Helper.HOME, "0700");
fsextra.copySync(path.resolve(path.join(
__dirname,
"..",
"..",
"defaults",
"config.js"
)), Helper.CONFIG_PATH);
log.info("Config created:", Helper.CONFIG_PATH);
}
fsextra.ensureDirSync(Helper.USERS_PATH);
require("./start");
require("./config");
require("./list");
require("./add");
require("./remove");
require("./reset");
require("./edit");
program.parse(argv.args);
if (!program.args.length) {
program.parse(process.argv.concat("start"));
}

View file

@ -1,19 +0,0 @@
"use strict";
var ClientManager = new require("../clientManager");
var program = require("commander");
program
.command("list")
.description("List all users")
.action(function() {
var users = new ClientManager().getUsers();
if (!users.length) {
log.warn("No users found!");
} else {
console.log("Users:");
for (var i = 0; i < users.length; i++) {
console.log(" " + (i + 1) + ". " + users[i]);
}
}
});

Some files were not shown because too many files have changed in this diff Show more