message/call views: ui overhaul

This patch:
- implements QWebEngineView as the view for interactions
- reorganizes the main widget's layout in order to share views
  between in-call and out-of-call messaging views
- fixes behavioral bugs and crashes
- cannot be built with the mingw compiler due to lack of support for
  QWebEngine, and must be built natively with msvc and includes some
  build script modifications
- should be thought of as a new client

Change-Id: I59d8c68dc8384e85fb006f30d8313482c00d6c85
diff --git a/RingWinClient.pro b/RingWinClient.pro
index 3174caf..735b90d 100644
--- a/RingWinClient.pro
+++ b/RingWinClient.pro
@@ -53,11 +53,9 @@
     windowscontactbackend.cpp \
     selectareadialog.cpp \
     accountserializationadapter.cpp \
-    instantmessagingwidget.cpp \
     accountstatedelegate.cpp \
     videoview.cpp \
     videooverlay.cpp \
-    imdelegate.cpp \
     contactpicker.cpp \
     globalsystemtray.cpp \
     conversationitemdelegate.cpp \
@@ -79,11 +77,14 @@
     smartlistview.cpp \
     accountitemdelegate.cpp \
     accountlistmodel.cpp \
-    messagemodel.cpp \
     invitebuttonswidget.cpp \
     wizardwidget.cpp \
     currentaccountcombobox.cpp \
-    conversationfilterbutton.cpp
+    conversationfilterbutton.cpp \
+    messagewebpage.cpp \
+    messagewebview.cpp \
+    webchathelpers.cpp \
+    animationhelpers.cpp
 
 HEADERS  += mainwindow.h \
     callwidget.h \
@@ -97,11 +98,9 @@
     windowscontactbackend.h \
     selectareadialog.h \
     accountserializationadapter.h \
-    instantmessagingwidget.h \
     accountstatedelegate.h \
     videoview.h \
     videooverlay.h \
-    imdelegate.h \
     contactpicker.h \
     settingskey.h \
     globalsystemtray.h \
@@ -126,11 +125,14 @@
     smartlistview.h \
     accountitemdelegate.h \
     accountlistmodel.h \
-    messagemodel.h \
     invitebuttonswidget.h \
     wizardwidget.h \
     currentaccountcombobox.h \
-    conversationfilterbutton.h
+    conversationfilterbutton.h \
+    messagewebpage.h \
+    messagewebview.h \
+    webchathelpers.h \
+    animationhelpers.h
 
 
 contains(DEFINES, URI_PROTOCOL) {
@@ -144,7 +146,6 @@
     accountdetails.ui \
     aboutdialog.ui \
     wizarddialog.ui \
-    instantmessagingwidget.ui \
     videoview.ui \
     videooverlay.ui \
     contactpicker.ui \
@@ -158,7 +159,8 @@
     bannedcontactswidget.ui \
     photoboothwidget.ui \
     invitebuttonswidget.ui \
-    wizardwidget.ui
+    wizardwidget.ui \
+    animatedoverlay.ui
 
 win32: LIBS += -lole32 -luuid -lshlwapi -lgdi32
 LIBS += -lqrencode
@@ -273,7 +275,9 @@
     QTRUNTIME.files = $$RUNTIMEDIR/Qt5Core.dll $$RUNTIMEDIR/Qt5Widgets.dll \
                             $$RUNTIMEDIR/Qt5Gui.dll $$RUNTIMEDIR/Qt5Svg.dll \
                             $$RUNTIMEDIR/Qt5Xml.dll $$RUNTIMEDIR/Qt5WinExtras.dll \
-                            $$RUNTIMEDIR/Qt5Network.dll $$RUNTIMEDIR/Qt5Sql.dll
+                            $$RUNTIMEDIR/Qt5Network.dll $$RUNTIMEDIR/Qt5Sql.dll \
+                            $$RUNTIMEDIR/Qt5WebEngineWidgets.dll $$RUNTIMEDIR/Qt5WebChannel.dll
+
     QTRUNTIME.path = $$OUT_PWD/release
 
     QTDEPSRUNTIME.files = $$RUNTIMEDIR/zlib1.dll \
diff --git a/aboutdialog.cpp b/aboutdialog.cpp
index 9172c1d..ec5d0a7 100644
--- a/aboutdialog.cpp
+++ b/aboutdialog.cpp
@@ -19,6 +19,8 @@
 #include "aboutdialog.h"
 #include "ui_aboutdialog.h"
 
+#include "version.h"
+
 AboutDialog::AboutDialog(QWidget *parent) :
     QDialog(parent),
     ui(new Ui::AboutDialog)
@@ -27,7 +29,7 @@
 
     this->setFixedSize(this->width(),this->height());
     ui->creditsWidget->hide();
-    ui->gitVersionLabel->setText(QString("%1: %2").arg(tr("version"), NIGHTLY_VERSION));
+    ui->gitVersionLabel->setText(QString("%1: %2").arg(tr("version"), QString(VERSION_STRING)));
 
     ui->creditsBrowser->setHtml("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">"
                                 "<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">"
diff --git a/animatedoverlay.ui b/animatedoverlay.ui
new file mode 100644
index 0000000..4aff548
--- /dev/null
+++ b/animatedoverlay.ui
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<ui version="4.0">

+ <class>AnimatedOverlay</class>

+ <widget class="QWidget" name="AnimatedOverlay">

+  <property name="geometry">

+   <rect>

+    <x>0</x>

+    <y>0</y>

+    <width>400</width>

+    <height>300</height>

+   </rect>

+  </property>

+  <property name="windowTitle">

+   <string>Form</string>

+  </property>

+  <layout class="QGridLayout" name="gridLayout">

+   <property name="leftMargin">

+    <number>0</number>

+   </property>

+   <property name="topMargin">

+    <number>0</number>

+   </property>

+   <property name="rightMargin">

+    <number>0</number>

+   </property>

+   <property name="bottomMargin">

+    <number>0</number>

+   </property>

+   <property name="spacing">

+    <number>0</number>

+   </property>

+   <item row="0" column="0">

+    <widget class="QLabel" name="backgroundLabel">

+     <property name="styleSheet">

+      <string notr="true"/>

+     </property>

+     <property name="text">

+      <string/>

+     </property>

+    </widget>

+   </item>

+  </layout>

+ </widget>

+ <resources/>

+ <connections/>

+</ui>

diff --git a/animationhelpers.cpp b/animationhelpers.cpp
new file mode 100644
index 0000000..c2abd8d
--- /dev/null
+++ b/animationhelpers.cpp
@@ -0,0 +1,130 @@
+/***************************************************************************
+ * Copyright (C) 2018 by Savoir-faire Linux                                *
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>          *
+ *                                                                         *
+ * This program is free software; you can redistribute it and/or modify    *
+ * it under the terms of the GNU General Public License as published by    *
+ * the Free Software Foundation; either version 3 of the License, or       *
+ * (at your option) any later version.                                     *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful,         *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License       *
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ **************************************************************************/
+
+#include "animationhelpers.h"
+
+#include "ui_animatedoverlay.h"
+
+#include <QTimer>
+#include <QtMath>
+
+OpacityAnimation::OpacityAnimation(QWidget* target, QObject* parent)
+    : QObject(parent),
+    target_(target),
+    timer_(nullptr),
+    frameTime_((1.0 / 24.0) * 1000),
+    startValue_(0.0),
+    endValue_(1.0),
+    t_(0),
+    value_(0),
+    duration_(1000)
+{
+    timer_ = new QTimer(this);
+    connect(timer_, SIGNAL(timeout()), this, SLOT(updateAnimation()));
+
+    effect_ = new QGraphicsOpacityEffect(this);
+    effect_->setOpacity(startValue_);
+
+    target_->setGraphicsEffect(effect_);
+    target_->setAutoFillBackground(true);
+}
+
+OpacityAnimation::~OpacityAnimation()
+{
+}
+
+void
+OpacityAnimation::setFPS(const int& fps)
+{
+    frameTime_ = (1.0 / static_cast<double>(fps)) * 1000;
+}
+
+void
+OpacityAnimation::setFrameTime(const int& milliseconds)
+{
+    frameTime_ = milliseconds;
+}
+
+void
+OpacityAnimation::setDuration(const int& milliseconds)
+{
+    duration_ = milliseconds;
+}
+
+void
+OpacityAnimation::setStartValue(const double& value)
+{
+    startValue_ = value;
+    effect_->setOpacity(startValue_);
+}
+
+void
+OpacityAnimation::setEndValue(const double& value)
+{
+    endValue_ = value;
+}
+
+void
+OpacityAnimation::start()
+{
+    timer_->start(frameTime_);
+}
+
+void
+OpacityAnimation::stop()
+{
+    timer_->stop();
+}
+
+void
+OpacityAnimation::updateAnimation()
+{
+    double d = (startValue_ + endValue_) * 0.5;
+    double a = abs(startValue_ - endValue_) * 0.5;
+
+    t_ += frameTime_;
+    value_ = a * sin(2 * M_PI * t_ * duration_ * .000001) + d;
+    effect_->setOpacity(value_);
+    target_->update();
+}
+
+AnimatedOverlay::AnimatedOverlay(QColor color, QWidget* parent) :
+    QWidget(parent),
+    ui(new Ui::AnimatedOverlay)
+{
+    ui->setupUi(this);
+    ui->backgroundLabel->setAutoFillBackground(true);
+    auto values = QString("%1,%2,%3,255")
+        .arg(color.red())
+        .arg(color.green())
+        .arg(color.blue());
+    ui->backgroundLabel->setStyleSheet("background-color: rgba(" + values + ");");
+
+    oa_ = new OpacityAnimation(this, this);
+    oa_->setFPS(16);
+    oa_->setDuration(1000);
+    oa_->setStartValue(0.0);
+    oa_->setEndValue(0.25);
+    oa_->start();
+}
+
+AnimatedOverlay::~AnimatedOverlay()
+{
+    disconnect(this);
+    delete ui;
+}
diff --git a/animationhelpers.h b/animationhelpers.h
new file mode 100644
index 0000000..e9cee03
--- /dev/null
+++ b/animationhelpers.h
@@ -0,0 +1,74 @@
+/***************************************************************************
+ * Copyright (C) 2018 by Savoir-faire Linux                                *
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>          *
+ *                                                                         *
+ * This program is free software; you can redistribute it and/or modify    *
+ * it under the terms of the GNU General Public License as published by    *
+ * the Free Software Foundation; either version 3 of the License, or       *
+ * (at your option) any later version.                                     *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful,         *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License       *
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ **************************************************************************/
+
+#pragma once
+
+#include <QWidget>
+#include <QPropertyAnimation>
+#include <QGraphicsOpacityEffect>
+#include <QMovie>
+
+class OpacityAnimation : public QObject
+{
+    Q_OBJECT
+public:
+    explicit OpacityAnimation(QWidget* target, QObject* parent = nullptr);
+    ~OpacityAnimation();
+
+    void setFPS(const int& fps);
+    void setFrameTime(const int& milliseconds);
+    void setDuration(const int& milliseconds);
+    void setStartValue(const double& value);
+    void setEndValue(const double& value);
+
+    void start();
+    void stop();
+
+private slots:
+    void updateAnimation();
+
+private:
+    QGraphicsOpacityEffect* effect_;
+    double value_;
+
+    QWidget* target_;
+    QTimer* timer_;
+    int frameTime_;
+    double t_;
+    int duration_;
+
+    double startValue_;
+    double endValue_;
+};
+
+namespace Ui {
+class AnimatedOverlay;
+}
+
+class AnimatedOverlay : public QWidget
+{
+    Q_OBJECT
+public:
+    explicit AnimatedOverlay(QColor color, QWidget* parent = 0);
+    ~AnimatedOverlay();
+
+private:
+    Ui::AnimatedOverlay* ui;
+
+    OpacityAnimation* oa_;
+};
\ No newline at end of file
diff --git a/build-client.bat b/build-client.bat
new file mode 100644
index 0000000..874ba2b
--- /dev/null
+++ b/build-client.bat
@@ -0,0 +1,117 @@
+:: Ring - native Windows client project build script

+

+@echo off

+setlocal

+

+if "%1" == "/?" goto Usage

+if "%~1" == "" goto Usage

+

+set doCompile=N

+set doBuild=N

+

+set SCRIPTNAME=%~nx0

+

+if "%1"=="compile" (

+    set doCompile=Y

+) else if "%1"=="build" (

+    set doBuild=Y

+) else (

+    goto Usage

+)

+

+set arch=N

+

+shift

+:ParseArgs

+if "%1" == "" goto FinishedArgs

+if /I "%1"=="x86" (

+    set arch=x86

+) else if /I "%1"=="x64" (

+    set arch=x64

+) else (

+    goto Usage

+)

+shift

+goto ParseArgs

+

+:FinishedArgs

+if "%arch%"=="x86" (

+    set MSBUILD_ARGS=/nologo /p:useenv=true /p:Platform=Win32 /maxcpucount:%NUMBER_OF_PROCESSORS%

+) else if "%arch%"=="x64" (

+    set MSBUILD_ARGS=/nologo /p:useenv=true /p:Platform=x64 /maxcpucount:%NUMBER_OF_PROCESSORS%

+)

+

+@setlocal

+

+set VSInstallerFolder="%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"

+if %PROCESSOR_ARCHITECTURE%==x86 set VSInstallerFolder="%ProgramFiles%\Microsoft Visual Studio\Installer"

+

+pushd %VSInstallerFolder%

+for /f "usebackq tokens=*" %%i in (`vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do (

+  set VSLATESTDIR=%%i

+)

+popd

+

+echo VS Installation folder: %VSLATESTDIR%

+

+if not exist "%VSLATESTDIR%\VC\Auxiliary\Build\vcvarsall.bat" (

+    echo:

+    echo VSInstallDir not found or not installed correctly.

+    goto cleanup

+)

+

+if %PROCESSOR_ARCHITECTURE%==x86 (

+    set Comp_x86=x86 10.0.15063.0

+    set Comp_x64=x86_amd64 10.0.15063.0

+) else (

+    set Comp_x86=amd64_x86 10.0.15063.0

+    set Comp_x64=amd64 10.0.15063.0

+)

+

+set path=%path:"=%

+if "%arch%"=="x86" (

+    call "%VSLATESTDIR%"\\VC\\Auxiliary\\Build\\vcvarsall.bat %Comp_x86%

+) else if "%arch%"=="x64" (

+    call "%VSLATESTDIR%"\\VC\\Auxiliary\\Build\\vcvarsall.bat %Comp_x64%

+)

+

+if "%arch%" neq "N" (

+    if "%doCompile%" neq "N" (

+        goto compileClient

+    ) else if "%doBuild%" neq "N" (

+        goto buildClient

+    )

+    goto :eof

+)

+goto Usage

+

+:compileClient

+msbuild ring-client-windows.vcxproj /verbosity:normal /p:Configuration=ReleaseCompile %MSBUILD_ARGS%

+goto cleanup

+

+:buildClient

+msbuild ring-client-windows.vcxproj /verbosity:normal /p:Configuration=Release %MSBUILD_ARGS%

+goto cleanup

+

+@endlocal

+

+:Usage

+echo:

+echo The correct usage is:

+echo:

+echo     %0 [action] [architecture]

+echo:

+echo where

+echo:

+echo [action]           is: compile  ^| build

+echo [architecture]     is: x86      ^| x64

+echo:

+echo For example:

+echo     %0 compile x86    - compile only x86 (for CI)

+echo     %0 build x64      - build x64 client

+echo:

+goto :eof

+

+:cleanup

+endlocal

+exit /B %ERRORLEVEL%
\ No newline at end of file
diff --git a/callwidget.cpp b/callwidget.cpp
index 71b29a7..d5679f2 100644
--- a/callwidget.cpp
+++ b/callwidget.cpp
@@ -27,6 +27,7 @@
 #include <QClipboard>
 #include <QDesktopServices>
 #include <QComboBox>
+#include <QWebEngineScript>
 
 #include <memory>
 
@@ -48,17 +49,16 @@
 #include "contactpicker.h"
 #include "globalsystemtray.h"
 #include "conversationitemdelegate.h"
-#include "imdelegate.h"
 #include "pixbufmanipulator.h"
 #include "settingskey.h"
 #include "lrcinstance.h"
-#include "messagemodel.h"
+#include "animationhelpers.h"
+#include "ringthemeutils.h"
 
 CallWidget::CallWidget(QWidget* parent) :
     NavWidget(parent),
     ui(new Ui::CallWidget),
-    menu_(new QMenu()),
-    imDelegate_(new ImDelegate())
+    menu_(new QMenu())
 {
     ui->setupUi(this);
 
@@ -77,11 +77,12 @@
     // this line is not welcome here, and must be removed
     ProfileModel::instance().addCollection<LocalProfileCollection>(LoadOptions::FORCE_ENABLED);
 
+    QSettings settings;
+
     // select last used account if stored in registry
     auto accountList = LRCInstance::accountModel().getAccountList();
     if (!accountList.empty()) {
         std::string accountIdToStartWith;
-        QSettings settings;
         if (settings.contains(SettingsKey::selectedAccount)) {
             accountIdToStartWith = settings
                 .value(SettingsKey::selectedAccount, true)
@@ -102,11 +103,19 @@
         }
     }
 
+    if (settings.contains(SettingsKey::mainSplitterState)) {
+        auto splitterStates = settings.value(SettingsKey::mainSplitterState).toByteArray();
+        ui->mainActivitySplitter->restoreState(splitterStates);
+    }
+
+    ui->mainActivitySplitter->setCollapsible(0, false);
+    ui->splitter->setCollapsible(0, false);
+    ui->splitter->setCollapsible(1, false);
+
     //disable dropdown shadow on combobox
     ui->currentAccountComboBox->view()->window()->setWindowFlags(Qt::Popup | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint);
 
     // conversation list
-    conversationItemDelegate_ = new ConversationItemDelegate();
     ui->smartList->setContextMenuPolicy(Qt::CustomContextMenu);
 
     // setup searchingfor mini spinner
@@ -114,29 +123,31 @@
     ui->spinnerLabel->setMovie(miniSpinner_);
     ui->spinnerLabel->hide();
 
-    ui->listMessageView->verticalScrollBar()->setEnabled(true);
-    ui->listMessageView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
-    ui->listMessageView->verticalScrollBar()->setStyleSheet("QScrollBar:vertical { width: 0px; }");
-
-    setupOutOfCallIM();
-
     // connections
     connect(ui->currentAccountComboBox, &CurrentAccountComboBox::settingsButtonClicked,
             this, &CallWidget::settingsButtonClicked);
 
-    connect(ui->videoWidget, SIGNAL(setChatVisibility(bool)),
-            ui->instantMessagingWidget, SLOT(setVisible(bool)));
+    connect(ui->videoWidget, &VideoView::setChatVisibility,
+        [this](bool visible) {
+            ui->callStackWidget->hide();
+        });
+
+    connect(ui->mainActivitySplitter, &QSplitter::splitterMoved,
+        [this](int pos, int index) {
+            QSettings settings;
+            settings.setValue(SettingsKey::mainSplitterState, ui->mainActivitySplitter->saveState());
+        });
 
     connect(ui->videoWidget, &VideoView::videoSettingsClicked,
             this, &CallWidget::settingsButtonClicked);
 
-    connect(ui->buttonConversations, &QPushButton::clicked,
+    connect(ui->btnConversations, &QPushButton::clicked,
             this, &CallWidget::conversationsButtonClicked);
 
-    connect(ui->buttonInvites, &QPushButton::clicked,
+    connect(ui->btnInvites, &QPushButton::clicked,
             this, &CallWidget::invitationsButtonClicked);
 
-    connect(ui->smartList, &QListView::customContextMenuRequested,
+    connect(ui->smartList, &QTreeView::customContextMenuRequested,
             this, &CallWidget::slotCustomContextMenuRequested);
 
     connect(ui->smartList, &SmartListView::btnAcceptInviteClicked,
@@ -169,48 +180,25 @@
     connect(ui->btnVideoCall, &QPushButton::clicked,
             this, &CallWidget::on_sendContactRequestButton_clicked);
 
-    // connect conversation filter buttons to act as radio buttons
-    connect(ui->buttonInvites, &ConversationFilterButton::clicked,
-            ui->buttonConversations, &ConversationFilterButton::setUnselected);
-
-    connect(ui->buttonConversations, &ConversationFilterButton::clicked,
-            ui->buttonInvites, &ConversationFilterButton::setUnselected);
-
     connect(ui->currentAccountComboBox, QOverload<int>::of(&CurrentAccountComboBox::currentIndexChanged),
         [this] {
-            ui->buttonConversations->setSelected();
-            ui->buttonInvites->setUnselected();
-    });
+            ui->btnConversations->setChecked(true);
+            ui->btnInvites->setChecked(false);
+        });
+
 
     // set first view to welcome view
     ui->stackedWidget->setCurrentWidget(ui->welcomePage);
-    ui->buttonConversations->setSelected();
+    ui->btnConversations->setChecked(true);
+
+    // chat view
+    ui->messageView->buildView();
 }
 
 CallWidget::~CallWidget()
 {
     delete ui;
     delete menu_;
-    delete imDelegate_;
-    delete conversationItemDelegate_;
-}
-
-void
-CallWidget::setupOutOfCallIM()
-{
-    ui->listMessageView->setItemDelegate(imDelegate_);
-    ui->listMessageView->setContextMenuPolicy(Qt::ActionsContextMenu);
-
-    auto copyAction = new QAction(tr("Copy"), this);
-    ui->listMessageView->addAction(copyAction);
-
-    connect(copyAction, &QAction::triggered, [=]() {
-        auto idx = ui->listMessageView->currentIndex();
-        if (idx.isValid()) {
-            auto text = ui->listMessageView->model()->data(idx);
-            QApplication::clipboard()->setText(text.value<QString>());
-        }
-    });
 }
 
 void
@@ -220,11 +208,24 @@
 {
     Q_UNUSED(interactionId);
     if (!QApplication::focusWidget()) {
+        auto convModel = LRCInstance::getCurrentConversationModel();
+        auto conversation = Utils::getConversationFromUid(convUid, *convModel);
+        auto bestName = Utils::bestNameForConversation(*conversation, *convModel);
         Utils::showSystemNotification(this,
             QString(tr("Message incoming from %1"))
-            .arg(QString::fromStdString(interaction.body)));
+            .arg(QString::fromStdString(bestName)));
     }
-    updateConversationView(convUid);
+    if (convUid != selectedConvUid()) {
+        return;
+    }
+
+    auto convModel = LRCInstance::getCurrentConversationModel();
+    convModel->clearUnreadInteractions(convUid);
+    auto currentConversation = Utils::getConversationFromUid(convUid, *convModel);
+    if (currentConversation == convModel->allFilteredConversations().end()) {
+        return;
+    }
+    ui->messageView->printNewInteraction(*convModel, interactionId, interaction);
     ui->conversationsFilterWidget->update();
 }
 
@@ -306,9 +307,9 @@
                 );
             });
     }
-    smartListModel_->isContextMenuOpen_ = true;
+    smartListModel_->isContextMenuOpen = true;
     menu.exec(globalPos);
-    smartListModel_->isContextMenuOpen_ = false;
+    smartListModel_->isContextMenuOpen = false;
 }
 
 void
@@ -386,18 +387,9 @@
 void
 CallWidget::showConversationView()
 {
-    ui->stackedWidget->setCurrentWidget(ui->messagingPage);
-    ui->imMessageEdit->clear();
-    ui->imMessageEdit->setFocus();
-    disconnect(imClickedConnection_);
-    imClickedConnection_ = connect(ui->listMessageView, &QListView::clicked, [this](const QModelIndex& index) {
-        auto urlList = index.data(static_cast<int>(media::TextRecording::Role::LinkList)).value<QList<QUrl>>();
-        if (urlList.size() == 1) {
-            QDesktopServices::openUrl(urlList.at(0));
-        } else if (urlList.size()) {
-            //TODO Handle multiple url in one message
-        }
-    });
+    ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
+    ui->messageView->setFocus();
+
 }
 
 void
@@ -476,21 +468,6 @@
 }
 
 void
-CallWidget::contactReqListCurrentChanged(const QModelIndex &currentIdx, const QModelIndex &previousIdx)
-{
-    Q_UNUSED(previousIdx)
-
-    if (currentIdx.isValid()) {
-        ui->contactRequestWidget->setCurrentContactRequest(currentIdx);
-        ui->stackedWidget->setCurrentWidget(ui->contactRequestPage);
-    } else {
-        ui->contactRequestWidget->setCurrentContactRequest(QModelIndex());
-        if (ui->stackedWidget->currentWidget() == ui->contactRequestPage)
-            Utils::setStackWidget(ui->stackedWidget, ui->welcomePage);
-    }
-}
-
-void
 CallWidget::placeCall()
 {
     if (ui->ringContactLineEdit->text().isEmpty())
@@ -498,21 +475,27 @@
     Call* c = CallModel::instance().dialingCall(PhoneDirectoryModel::instance().getNumber(ui->ringContactLineEdit->text()));
     c->performAction(Call::Action::ACCEPT);
     ui->ringContactLineEdit->clear();
+    auto photoRect = ui->callingPhoto->frameGeometry();
     ui->callingPhoto->setPixmap(
                 QPixmap::fromImage(
                     GlobalInstances::pixmapManipulator()
-                    .callPhoto(c, QSize(130,130)).value<QImage>()));
+                    .callPhoto(c, QSize(photoRect.width(), photoRect.height()))
+                    .value<QImage>()));
 }
 
 void
 CallWidget::conversationsButtonClicked()
 {
+    ui->btnConversations->setChecked(true);
+    ui->btnInvites->setChecked(false);
     setConversationFilter(lrc::api::profile::Type::RING);
 }
 
 void
 CallWidget::invitationsButtonClicked()
 {
+    ui->btnConversations->setChecked(false);
+    ui->btnInvites->setChecked(true);
     setConversationFilter(lrc::api::profile::Type::PENDING);
 }
 
@@ -535,8 +518,8 @@
     // select current temporary item and show conversation
     auto convModel = LRCInstance::getCurrentConversationModel();
     auto conversations = convModel->allFilteredConversations();
-    auto contactIsValid = Utils::isContactValid(conversations.at(0).participants.at(0), *convModel);
-    if (!conversations.empty() && contactIsValid) {
+    if (!conversations.empty() &&
+        Utils::isContactValid(conversations.at(0).participants.at(0), *convModel)) {
         selectConversation(smartListModel_->index(0));
     }
 }
@@ -575,65 +558,112 @@
 {
     Q_UNUSED(accountId);
     Q_UNUSED(convInfo);
-    qDebug() << "BehaviorController::showCallView";
-    ui->stackedWidget->setCurrentWidget(ui->videoPage);
+    qDebug() << "slotShowCallView";
+    ui->callStackWidget->show();
+    ui->callStackWidget->setCurrentWidget(ui->videoPage);
     hideMiniSpinner();
 }
 
 void CallWidget::slotShowIncomingCallView(const std::string& accountId,
-                                          const lrc::api::conversation::Info& convInfo) {
+                                          const lrc::api::conversation::Info& convInfo)
+{
     Q_UNUSED(accountId);
-    qDebug() << "BehaviorController::showIncomingCallView";
+    qDebug() << "slotShowIncomingCallView";
+
     auto callModel = LRCInstance::getCurrentCallModel();
-    auto call = callModel->getCall(convInfo.callId);
-    if (call.isOutgoing) {
-        miniSpinner_->start();
-        ui->spinnerLabel->show();
-        ui->stackedWidget->setCurrentWidget(ui->outboundCallPage);
-    }
-    else {
-        selectSmartlistItem(convInfo.uid);
-        auto selectedAccountId = LRCInstance::getCurrentAccountInfo().id;
-        auto accountProperties = LRCInstance::accountModel().getAccountConfig(selectedAccountId);
-        if (!accountProperties.autoAnswer) {
-            ui->stackedWidget->setCurrentWidget(ui->callInvitePage);
-        }
-        else {
-            ui->stackedWidget->setCurrentWidget(ui->videoPage);
-        }
-        if (!QApplication::focusWidget()) {
-            auto formattedName = Utils::bestNameForConversation(convInfo, *LRCInstance::getCurrentConversationModel());
-            Utils::showSystemNotification(this,
-                QString(tr("Call incoming from %1")).arg(QString::fromStdString(formattedName)));
-        }
-    }
 
     if (!callModel->hasCall(convInfo.callId)) {
         return;
     }
 
+    auto convModel = LRCInstance::getCurrentConversationModel();
+    ui->callerPhoto->setPixmap(QPixmap::fromImage(imageForConv(convInfo.uid)));
+    auto bestName = QString::fromStdString(Utils::bestNameForConversation(convInfo, *convModel));
+    auto bestId = QString::fromStdString(Utils::bestIdForConversation(convInfo, *convModel));
+    auto finalBestId = (bestName != bestId) ? bestId : "";
+
+    auto call = callModel->getCall(convInfo.callId);
+    auto isCallSelected = selectedConvUid() == convInfo.uid;
+
+    if (call.isOutgoing) {
+        if (isCallSelected) {
+            miniSpinner_->start();
+            ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
+            ui->spinnerLabel->show();
+            ui->callStackWidget->setCurrentWidget(ui->outgoingCallPage);
+            ui->callStackWidget->show();
+        }
+    } else {
+        if (!QApplication::focusWidget()) {
+            auto formattedName = Utils::bestNameForConversation(convInfo, *convModel);
+            Utils::showSystemNotification(this,
+                QString(tr("Call incoming from %1")).arg(QString::fromStdString(formattedName)));
+        }
+        selectSmartlistItem(convInfo.uid);
+        auto selectedAccountId = LRCInstance::getCurrentAccountInfo().id;
+        auto accountProperties = LRCInstance::accountModel().getAccountConfig(selectedAccountId);
+        if (accountProperties.autoAnswer) {
+            ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
+            ui->callStackWidget->setCurrentWidget(ui->videoPage);
+        } else if (isCallSelected) {
+            ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
+            ui->callStackWidget->setCurrentWidget(ui->incomingCallPage);
+            ui->callStackWidget->show();
+        }
+    }
+
+    // flashing index widget during call
+    for (int row = 0; row < smartListModel_->rowCount(); row++) {
+        QModelIndex index = smartListModel_->index(row);
+        auto indexUid = index.data(SmartListModel::Role::UID).value<QString>().toStdString();
+        if (indexUid == convInfo.uid) {
+            auto widget = ui->smartList->indexWidget(index);
+            if (!widget) {
+                auto blinkingWidget = new AnimatedOverlay(RingTheme::urgentOrange_);
+                ui->smartList->setIndexWidget(index, blinkingWidget);
+                blinkingWidget->show();
+            } else {
+                widget->show();
+            }
+        }
+    }
+
     ui->videoWidget->pushRenderer(convInfo.callId);
 
-    ui->instantMessagingWidget->setupCallMessaging(convInfo.callId, messageModel_.get());
+    QFontMetrics primaryCallLabelFontMetrics(ui->callingBestNameLabel->font());
+    QFontMetrics sencondaryCallLabelFontMetrics(ui->callingBestIdLabel->font());
 
-    disconnect(selectedCallChanged_);
-    selectedCallChanged_ = connect(
-        callModel,
-        &lrc::api::NewCallModel::callStatusChanged,
-        [this, callModel](const std::string& callUid) {
-            auto call = callModel->getCall(callUid);
-            qDebug() << "NewCallModel::callStatusChanged: " << static_cast<int>(call.status);
-        }
-    );
+    QString elidedLabel = primaryCallLabelFontMetrics.elidedText(bestName, Qt::ElideRight, ui->callerBestNameLabel->width());
+    ui->callerBestNameLabel->setText(elidedLabel);
 
-    ui->callerPhoto->setPixmap(QPixmap::fromImage(imageForConv(selectedConvUid())));
+    elidedLabel = primaryCallLabelFontMetrics.elidedText(bestName, Qt::ElideRight, ui->callingBestNameLabel->width());
+    ui->callingBestNameLabel->setText(elidedLabel);
+
+    elidedLabel = sencondaryCallLabelFontMetrics.elidedText(finalBestId, Qt::ElideRight, ui->callingBestIdLabel->width());
+    ui->callingBestIdLabel->setText(elidedLabel);
+
+    ui->smartList->update();
 }
 
 void CallWidget::slotShowChatView(const std::string& accountId,
-                      const lrc::api::conversation::Info& convInfo) {
+                      const lrc::api::conversation::Info& convInfo)
+{
     Q_UNUSED(accountId);
     Q_UNUSED(convInfo);
-    qDebug() << "BehaviorController::showChatView";
+
+    for (int row = 0; row < smartListModel_->rowCount(); row++) {
+        QModelIndex index = smartListModel_->index(row);
+        auto indexUid = index.data(SmartListModel::Role::UID).value<QString>().toStdString();
+        if (indexUid == convInfo.uid) {
+            if (auto widget = ui->smartList->indexWidget(index)) {
+                widget->hide();
+                widget->deleteLater();
+                ui->smartList->setIndexWidget(index, nullptr);
+            }
+        }
+    }
+
+    ui->callStackWidget->hide();
     showConversationView();
 }
 
@@ -681,6 +711,7 @@
         LRCInstance::getCurrentConversationModel()->setFilter(currentTypeFilter_);
     }
     ui->conversationsFilterWidget->setVisible(invites);
+    ui->conversationsFilterWidget->update();
 }
 
 void CallWidget::setConversationFilter(const QString & filter)
@@ -705,16 +736,6 @@
     }
     catch (...) {}
 
-    if (!isRINGAccount){
-        ui->imMessageEdit->setPlaceholderText("No messaging possible out of call (SIP) ");
-        ui->imMessageEdit->setEnabled(false);
-        ui->sendIMButton->hide();
-    } else {
-        ui->imMessageEdit->setPlaceholderText("Type your message here");
-        ui->imMessageEdit->setEnabled(true);
-        ui->sendIMButton->show();
-    }
-
     ui->imNameLabel->setText(QString(tr("%1", "%1 is the contact username"))
                                     .arg(displayName));
 
@@ -729,44 +750,34 @@
     bool shouldShowSendContactRequestBtn = !isContact && isRINGAccount;
     ui->sendContactRequestButton->setVisible(shouldShowSendContactRequestBtn);
 
+    auto convModel = LRCInstance::getCurrentConversationModel();
+    auto currentConversation = Utils::getConversationFromUid(selectedConvUid(),
+                                                             *convModel);
+    ui->messageView->clear();
+    ui->messageView->printHistory(*convModel, currentConversation->interactions);
+
     showConversationView();
 
-    auto currentConversation = Utils::getConversationFromUid(selectedConvUid(),
-                                                             *LRCInstance::getCurrentConversationModel());
-    messageModel_.reset(new MessageModel(*currentConversation, accountInfo, this->parent()));
-    ui->listMessageView->setModel(messageModel_.get());
-    ui->listMessageView->scrollToBottom();
-}
-
-void
-CallWidget::on_sendIMButton_clicked()
-{
-    auto msg = ui->imMessageEdit->text();
-    if (msg.trimmed().isEmpty()) return;
-    ui->imMessageEdit->clear();
+    // Contact Avatars
+    auto accInfo = &LRCInstance::getCurrentAccountInfo();
+    auto contactUri = currentConversation->participants.front();
     try {
-        LRCInstance::getCurrentConversationModel()->sendMessage(selectedConvUid(), msg.toStdString());
-    } catch (...) {
-        qDebug() << "exception when sending message";
-    }
-}
-
-void
-CallWidget::on_imMessageEdit_returnPressed()
-{
-    on_sendIMButton_clicked();
-}
-
-void
-CallWidget::slotAccountMessageReceived(const QMap<QString,QString> message,
-                                       ContactMethod* cm,
-                                       media::Media::Direction dir)
-{
-    Q_UNUSED(message)
-    Q_UNUSED(dir)
-
-    ui->listMessageView->scrollToBottom();
-    cm->textRecording()->setAllRead();
+        auto& contact = accInfo->contactModel->getContact(contactUri);
+        if (!contact.profileInfo.avatar.empty()) {
+            ui->messageView->setSenderImage(
+                accInfo->contactModel->getContactProfileId(contactUri),
+                contact.profileInfo.avatar);
+        } else {
+            auto avatar = Utils::conversationPhoto(selectedConvUid(), *accInfo);
+            QByteArray ba;
+            QBuffer bu(&ba);
+            avatar.save(&bu, "PNG");
+            std::string avatarString = ba.toBase64().toStdString();
+            ui->messageView->setSenderImage(
+                accInfo->contactModel->getContactProfileId(contactUri),
+                avatarString);
+        }
+    } catch (...) { }
 }
 
 void
@@ -780,6 +791,7 @@
 CallWidget::backToWelcomePage()
 {
     deselectConversation();
+    ui->messageView->hideMessages();
     ui->stackedWidget->setCurrentWidget(ui->welcomePage);
 }
 
@@ -855,6 +867,7 @@
     QObject::disconnect(conversationClearedConnection);
     QObject::disconnect(interactionStatusUpdatedConnection_);
     QObject::disconnect(newInteractionConnection_);
+    QObject::disconnect(interactionRemovedConnection_);
 
     modelSortedConnection_ = QObject::connect(
         currentConversationModel, &lrc::api::ConversationModel::modelSorted,
@@ -896,7 +909,7 @@
     conversationClearedConnection = QObject::connect(
         currentConversationModel, &lrc::api::ConversationModel::conversationCleared,
         [this](const std::string& convUid) {
-            updateConversationView(convUid);
+            ui->messageView->clear();
             // if currently selected,
             // switch to welcome screen (deselecting current smartlist item )
             if (convUid != selectedConvUid()) {
@@ -907,11 +920,15 @@
     );
     interactionStatusUpdatedConnection_ = QObject::connect(
         currentConversationModel, &lrc::api::ConversationModel::interactionStatusUpdated,
-        [this](const std::string& convUid) {
+        [this](const std::string& convUid, uint64_t interactionId, const lrc::api::interaction::Info& interaction) {
             if (convUid != selectedConvUid()) {
                 return;
             }
-            updateConversationView(convUid);
+            auto& currentAccountInfo = LRCInstance::getCurrentAccountInfo();
+            auto currentConversationModel = currentAccountInfo.conversationModel.get();
+            currentConversationModel->clearUnreadInteractions(convUid);
+            ui->conversationsFilterWidget->update();
+            ui->messageView->updateInteraction(*currentConversationModel, interactionId, interaction);
         }
     );
     newInteractionConnection_ = QObject::connect(
@@ -920,6 +937,13 @@
             onIncomingMessage(convUid, interactionId, interaction);
         }
     );
+    interactionRemovedConnection_ = QObject::connect(
+        currentConversationModel, &lrc::api::ConversationModel::interactionRemoved,
+        [this](const std::string& convUid, uint64_t interactionId) {
+            Q_UNUSED(convUid);
+            ui->messageView->removeInteraction(interactionId);
+        }
+    );
     currentConversationModel->setFilter("");
     // clear search field
     ui->ringContactLineEdit->setText("");
@@ -933,18 +957,8 @@
         return;
     }
 
-    auto& currentAccountInfo = LRCInstance::getCurrentAccountInfo();
-    auto currentConversationModel = currentAccountInfo.conversationModel.get();
-    currentConversationModel->clearUnreadInteractions(convUid);
-    ui->conversationsFilterWidget->update();
-    auto currentConversation = Utils::getConversationFromUid(convUid,
-                                                             *currentConversationModel);
-    if (currentConversation == currentConversationModel->allFilteredConversations().end()) {
-        return;
-    }
-    messageModel_.reset(new MessageModel(*currentConversation, currentAccountInfo, this->parent()));
-    ui->listMessageView->setModel(messageModel_.get());
-    ui->listMessageView->scrollToBottom();
+
+
 }
 
 void
@@ -959,6 +973,7 @@
     const auto item = currentConversationModel->filteredConversation(index.row());
 
     if (selectConversation(item, *currentConversationModel)) {
+        showIMOutOfCall(index);
         auto convUid = selectedConvUid();
         if (!lastConvUid_.compare(convUid)) {
             return;
@@ -968,12 +983,12 @@
         auto callModel = LRCInstance::getCurrentCallModel();
         auto conversation = Utils::getConversationFromUid(convUid, *currentConversationModel);
         const auto item = currentConversationModel->filteredConversation(index.row());
-        if (callModel->hasCall(conversation->callId)) {
-            ui->stackedWidget->setCurrentWidget(ui->videoPage);
+        if (callModel->hasCall(conversation->callId) && item.callId == conversation->callId) {
+            ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
+            ui->callStackWidget->show();
+            return;
         }
-        else {
-            showIMOutOfCall(index);
-        }
+        ui->callStackWidget->hide();
     }
 }
 
diff --git a/callwidget.h b/callwidget.h
index 7c37bcf..e6b1f13 100644
--- a/callwidget.h
+++ b/callwidget.h
@@ -30,7 +30,6 @@
 #include <QMovie>
 
 #include "navwidget.h"
-#include "instantmessagingwidget.h"
 #include "smartlistmodel.h"
 
 // old LRC
@@ -51,7 +50,6 @@
 #include "api/newcallmodel.h"
 
 class ConversationItemDelegate;
-class ImDelegate;
 class QPropertyAnimation;
 
 namespace Ui {
@@ -87,8 +85,6 @@
     void on_refuseButton_clicked();
     void on_cancelButton_clicked();
     void on_smartList_doubleClicked(const QModelIndex& index);
-    void on_sendIMButton_clicked();
-    void on_imMessageEdit_returnPressed();
     void on_ringContactLineEdit_textChanged(const QString& text);
     void on_imBackButton_clicked();
     void on_sendContactRequestButton_clicked();
@@ -101,15 +97,12 @@
 
 private slots:
     void smartListSelectionChanged(const QItemSelection  &selected, const QItemSelection  &deselected);
-    void contactReqListCurrentChanged(const QModelIndex &currentIdx, const QModelIndex &previousIdx);
-    void slotAccountMessageReceived(const QMap<QString,QString> message,ContactMethod* cm, media::Media::Direction dir);
     void onIncomingMessage(const std::string & convUid, uint64_t interactionId, const lrc::api::interaction::Info & interaction);
 
 private:
     void placeCall();
     void conversationsButtonClicked();
     void invitationsButtonClicked();
-    void setupOutOfCallIM();
     void setupSmartListContextMenu(const QPoint &pos);
     void setupQRCode(QString ringID);
     void backToWelcomePage();
@@ -136,14 +129,6 @@
     const std::string& selectedConvUid();
 
     QMenu* menu_;
-    ConversationItemDelegate* conversationItemDelegate_;
-    ImDelegate* imDelegate_;
-
-    QMetaObject::Connection imConnection_;
-    QMetaObject::Connection imVisibleConnection_;
-    QMetaObject::Connection callChangedConnection_;
-    QMetaObject::Connection imClickedConnection_;
-    QMetaObject::Connection crListSelectionConnection_;
 
     Ui::CallWidget* ui;
     QMovie* miniSpinner_;
@@ -154,9 +139,13 @@
     Video::Renderer* videoRenderer_;
     std::string lastConvUid_ {};
     lrc::api::profile::Type currentTypeFilter_{};
-
     std::unique_ptr<SmartListModel> smartListModel_;
-    std::unique_ptr<MessageModel> messageModel_;
+
+    QMetaObject::Connection imConnection_;
+    QMetaObject::Connection imVisibleConnection_;
+    QMetaObject::Connection callChangedConnection_;
+    QMetaObject::Connection imClickedConnection_;
+    QMetaObject::Connection crListSelectionConnection_;
     QMetaObject::Connection modelSortedConnection_;
     QMetaObject::Connection modelUpdatedConnection_;
     QMetaObject::Connection filterChangedConnection_;
@@ -167,4 +156,5 @@
     QMetaObject::Connection conversationClearedConnection;
     QMetaObject::Connection selectedCallChanged_;
     QMetaObject::Connection smartlistSelectionConnection_;
+    QMetaObject::Connection interactionRemovedConnection_;
 };
diff --git a/callwidget.ui b/callwidget.ui
index 6b8f98b..dcc32b1 100644
--- a/callwidget.ui
+++ b/callwidget.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>746</width>
-    <height>639</height>
+    <width>1250</width>
+    <height>729</height>
    </rect>
   </property>
   <property name="sizePolicy">
@@ -72,7 +72,7 @@
         </property>
         <property name="minimumSize">
          <size>
-          <width>324</width>
+          <width>320</width>
           <height>0</height>
          </size>
         </property>
@@ -93,256 +93,273 @@
           <number>0</number>
          </property>
          <item row="0" column="0">
-          <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,0">
-           <property name="spacing">
-            <number>0</number>
-           </property>
-           <property name="sizeConstraint">
-            <enum>QLayout::SetDefaultConstraint</enum>
-           </property>
-           <property name="topMargin">
-            <number>4</number>
-           </property>
-           <item>
-            <widget class="CurrentAccountComboBox" name="currentAccountComboBox" native="true">
-             <property name="sizePolicy">
-              <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
-               <horstretch>0</horstretch>
-               <verstretch>0</verstretch>
-              </sizepolicy>
-             </property>
-             <property name="minimumSize">
-              <size>
-               <width>0</width>
-               <height>60</height>
-              </size>
-             </property>
-             <property name="autoFillBackground">
-              <bool>false</bool>
-             </property>
-             <property name="styleSheet">
-              <string notr="true"/>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QWidget" name="selectBar" native="true">
-             <property name="sizePolicy">
-              <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
-               <horstretch>0</horstretch>
-               <verstretch>0</verstretch>
-              </sizepolicy>
-             </property>
-             <property name="maximumSize">
-              <size>
-               <width>16777214</width>
-               <height>30</height>
-              </size>
-             </property>
-             <layout class="QHBoxLayout" name="selectBar_layout" stretch="0,0,0">
-              <property name="spacing">
-               <number>0</number>
+          <widget class="QWidget" name="sidePanelLayoutWidget" native="true">
+           <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,0">
+            <property name="spacing">
+             <number>0</number>
+            </property>
+            <property name="sizeConstraint">
+             <enum>QLayout::SetDefaultConstraint</enum>
+            </property>
+            <property name="leftMargin">
+             <number>0</number>
+            </property>
+            <property name="topMargin">
+             <number>4</number>
+            </property>
+            <property name="rightMargin">
+             <number>0</number>
+            </property>
+            <property name="bottomMargin">
+             <number>0</number>
+            </property>
+            <item>
+             <widget class="CurrentAccountComboBox" name="currentAccountComboBox" native="true">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
               </property>
-              <property name="sizeConstraint">
-               <enum>QLayout::SetDefaultConstraint</enum>
+              <property name="minimumSize">
+               <size>
+                <width>0</width>
+                <height>60</height>
+               </size>
               </property>
-              <property name="leftMargin">
-               <number>0</number>
+              <property name="autoFillBackground">
+               <bool>false</bool>
               </property>
-              <property name="topMargin">
-               <number>0</number>
+              <property name="styleSheet">
+               <string notr="true"/>
               </property>
-              <property name="rightMargin">
-               <number>0</number>
+             </widget>
+            </item>
+            <item>
+             <widget class="QWidget" name="selectBar" native="true">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
               </property>
-              <property name="bottomMargin">
-               <number>0</number>
+              <property name="maximumSize">
+               <size>
+                <width>16777214</width>
+                <height>30</height>
+               </size>
               </property>
-              <item>
-               <spacer name="horizontalSpacer">
-                <property name="orientation">
-                 <enum>Qt::Horizontal</enum>
-                </property>
-                <property name="sizeType">
-                 <enum>QSizePolicy::Fixed</enum>
-                </property>
-                <property name="sizeHint" stdset="0">
-                 <size>
-                  <width>10</width>
-                  <height>38</height>
-                 </size>
-                </property>
-               </spacer>
-              </item>
-              <item>
-               <widget class="RingContactLineEdit" name="ringContactLineEdit">
-                <property name="enabled">
-                 <bool>true</bool>
-                </property>
-                <property name="sizePolicy">
-                 <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-                  <horstretch>0</horstretch>
-                  <verstretch>0</verstretch>
-                 </sizepolicy>
-                </property>
-                <property name="minimumSize">
-                 <size>
-                  <width>266</width>
-                  <height>30</height>
-                 </size>
-                </property>
-                <property name="baseSize">
-                 <size>
-                  <width>244</width>
-                  <height>30</height>
-                 </size>
-                </property>
-                <property name="font">
-                 <font>
-                  <pointsize>9</pointsize>
-                 </font>
-                </property>
-                <property name="cursor">
-                 <cursorShape>IBeamCursor</cursorShape>
-                </property>
-                <property name="toolTip">
-                 <string>Search contact text input</string>
-                </property>
-                <property name="maxLength">
-                 <number>100</number>
-                </property>
-                <property name="alignment">
-                 <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
-                </property>
-                <property name="placeholderText">
-                 <string>Search contacts or enter ring ID</string>
-                </property>
-                <property name="clearButtonEnabled">
-                 <bool>true</bool>
-                </property>
-               </widget>
-              </item>
-              <item>
-               <spacer name="horizontalSpacer_2">
-                <property name="orientation">
-                 <enum>Qt::Horizontal</enum>
-                </property>
-                <property name="sizeType">
-                 <enum>QSizePolicy::Fixed</enum>
-                </property>
-                <property name="sizeHint" stdset="0">
-                 <size>
-                  <width>10</width>
-                  <height>38</height>
-                 </size>
-                </property>
-               </spacer>
-              </item>
-             </layout>
-            </widget>
-           </item>
-           <item>
-            <widget class="QWidget" name="smartListWidget" native="true">
-             <layout class="QVBoxLayout" name="verticalLayout_5" stretch="0,0">
-              <property name="spacing">
-               <number>0</number>
-              </property>
-              <property name="leftMargin">
-               <number>1</number>
-              </property>
-              <property name="topMargin">
-               <number>1</number>
-              </property>
-              <property name="rightMargin">
-               <number>0</number>
-              </property>
-              <property name="bottomMargin">
-               <number>1</number>
-              </property>
-              <item>
-               <widget class="ConversationsFilterWidget" name="conversationsFilterWidget" native="true">
-                <layout class="QHBoxLayout" name="conversationFilterLayout">
-                 <property name="spacing">
-                  <number>10</number>
+              <layout class="QHBoxLayout" name="selectBar_layout" stretch="0,0,0">
+               <property name="spacing">
+                <number>0</number>
+               </property>
+               <property name="sizeConstraint">
+                <enum>QLayout::SetDefaultConstraint</enum>
+               </property>
+               <property name="leftMargin">
+                <number>0</number>
+               </property>
+               <property name="topMargin">
+                <number>0</number>
+               </property>
+               <property name="rightMargin">
+                <number>0</number>
+               </property>
+               <property name="bottomMargin">
+                <number>0</number>
+               </property>
+               <item>
+                <spacer name="horizontalSpacer">
+                 <property name="orientation">
+                  <enum>Qt::Horizontal</enum>
                  </property>
-                 <property name="leftMargin">
-                  <number>10</number>
+                 <property name="sizeType">
+                  <enum>QSizePolicy::Fixed</enum>
+                 </property>
+                 <property name="sizeHint" stdset="0">
+                  <size>
+                   <width>10</width>
+                   <height>38</height>
+                  </size>
+                 </property>
+                </spacer>
+               </item>
+               <item>
+                <widget class="RingContactLineEdit" name="ringContactLineEdit">
+                 <property name="enabled">
+                  <bool>true</bool>
+                 </property>
+                 <property name="sizePolicy">
+                  <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+                   <horstretch>0</horstretch>
+                   <verstretch>0</verstretch>
+                  </sizepolicy>
+                 </property>
+                 <property name="minimumSize">
+                  <size>
+                   <width>266</width>
+                   <height>30</height>
+                  </size>
+                 </property>
+                 <property name="baseSize">
+                  <size>
+                   <width>244</width>
+                   <height>30</height>
+                  </size>
+                 </property>
+                 <property name="font">
+                  <font>
+                   <pointsize>9</pointsize>
+                  </font>
+                 </property>
+                 <property name="cursor">
+                  <cursorShape>IBeamCursor</cursorShape>
+                 </property>
+                 <property name="toolTip">
+                  <string>Search contact text input</string>
+                 </property>
+                 <property name="maxLength">
+                  <number>100</number>
+                 </property>
+                 <property name="alignment">
+                  <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+                 </property>
+                 <property name="placeholderText">
+                  <string>Search contacts or enter ring ID</string>
+                 </property>
+                 <property name="clearButtonEnabled">
+                  <bool>true</bool>
+                 </property>
+                </widget>
+               </item>
+               <item>
+                <spacer name="horizontalSpacer_2">
+                 <property name="orientation">
+                  <enum>Qt::Horizontal</enum>
+                 </property>
+                 <property name="sizeType">
+                  <enum>QSizePolicy::Fixed</enum>
+                 </property>
+                 <property name="sizeHint" stdset="0">
+                  <size>
+                   <width>10</width>
+                   <height>38</height>
+                  </size>
+                 </property>
+                </spacer>
+               </item>
+              </layout>
+             </widget>
+            </item>
+            <item>
+             <widget class="QWidget" name="smartListWidget" native="true">
+              <layout class="QVBoxLayout" name="verticalLayout_5" stretch="0,0">
+               <property name="spacing">
+                <number>0</number>
+               </property>
+               <property name="leftMargin">
+                <number>1</number>
+               </property>
+               <property name="topMargin">
+                <number>1</number>
+               </property>
+               <property name="rightMargin">
+                <number>0</number>
+               </property>
+               <property name="bottomMargin">
+                <number>1</number>
+               </property>
+               <item>
+                <widget class="ConversationsFilterWidget" name="conversationsFilterWidget" native="true">
+                 <layout class="QHBoxLayout" name="conversationFilterLayout">
+                  <property name="spacing">
+                   <number>10</number>
+                  </property>
+                  <property name="leftMargin">
+                   <number>10</number>
+                  </property>
+                  <property name="topMargin">
+                   <number>10</number>
+                  </property>
+                  <property name="rightMargin">
+                   <number>10</number>
+                  </property>
+                  <property name="bottomMargin">
+                   <number>0</number>
+                  </property>
+                  <item>
+                   <widget class="QPushButton" name="btnConversations">
+                    <property name="toolTip">
+                     <string>Show conversations</string>
+                    </property>
+                    <property name="text">
+                     <string>Conversations</string>
+                    </property>
+                    <property name="checkable">
+                     <bool>true</bool>
+                    </property>
+                   </widget>
+                  </item>
+                  <item>
+                   <widget class="QPushButton" name="btnInvites">
+                    <property name="toolTip">
+                     <string>Show invites</string>
+                    </property>
+                    <property name="text">
+                     <string>Invites</string>
+                    </property>
+                    <property name="checkable">
+                     <bool>true</bool>
+                    </property>
+                   </widget>
+                  </item>
+                 </layout>
+                </widget>
+               </item>
+               <item>
+                <layout class="QVBoxLayout" name="verticalLayout_13">
+                 <property name="spacing">
+                  <number>0</number>
                  </property>
                  <property name="topMargin">
                   <number>10</number>
                  </property>
-                 <property name="rightMargin">
-                  <number>10</number>
-                 </property>
-                 <property name="bottomMargin">
-                  <number>0</number>
-                 </property>
                  <item>
-                  <widget class="ConversationFilterButton" name="buttonConversations" native="true">
-                   <property name="toolTip">
-                    <string>Show conversations</string>
+                  <widget class="SmartListView" name="smartList">
+                   <property name="autoScrollMargin">
+                    <number>16</number>
                    </property>
-                   <property name="text" stdset="0">
-                    <string>Conversations</string>
+                   <property name="indentation">
+                    <number>0</number>
                    </property>
-                  </widget>
-                 </item>
-                 <item>
-                  <widget class="ConversationFilterButton" name="buttonInvites" native="true">
-                   <property name="toolTip">
-                    <string>Show invites</string>
+                   <property name="rootIsDecorated">
+                    <bool>false</bool>
                    </property>
-                   <property name="text" stdset="0">
-                    <string>Invites</string>
+                   <property name="itemsExpandable">
+                    <bool>false</bool>
                    </property>
+                   <property name="expandsOnDoubleClick">
+                    <bool>false</bool>
+                   </property>
+                   <attribute name="headerVisible">
+                    <bool>false</bool>
+                   </attribute>
                   </widget>
                  </item>
                 </layout>
-               </widget>
-              </item>
-              <item>
-               <layout class="QVBoxLayout" name="verticalLayout_13">
-                <property name="spacing">
-                 <number>0</number>
-                </property>
-                <property name="topMargin">
-                 <number>10</number>
-                </property>
-                <item>
-                 <widget class="SmartListView" name="smartList">
-                  <property name="autoScrollMargin">
-                   <number>16</number>
-                  </property>
-                  <property name="indentation">
-                   <number>0</number>
-                  </property>
-                  <property name="rootIsDecorated">
-                   <bool>false</bool>
-                  </property>
-                  <property name="itemsExpandable">
-                   <bool>false</bool>
-                  </property>
-                  <property name="expandsOnDoubleClick">
-                   <bool>false</bool>
-                  </property>
-                  <attribute name="headerVisible">
-                   <bool>false</bool>
-                  </attribute>
-                 </widget>
-                </item>
-               </layout>
-              </item>
-             </layout>
-            </widget>
-           </item>
-          </layout>
+               </item>
+              </layout>
+             </widget>
+            </item>
+           </layout>
+          </widget>
          </item>
         </layout>
        </widget>
        <widget class="QWidget" name="widgetSplitterRight" native="true">
         <property name="sizePolicy">
          <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
-          <horstretch>1</horstretch>
+          <horstretch>0</horstretch>
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
@@ -366,7 +383,7 @@
           <number>0</number>
          </property>
          <item row="0" column="0">
-          <widget class="QWidget" name="CallSubGroup" native="true">
+          <widget class="QWidget" name="mainLayoutWidget" native="true">
            <property name="sizePolicy">
             <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
              <horstretch>0</horstretch>
@@ -404,7 +421,7 @@
                <string/>
               </property>
               <property name="currentIndex">
-               <number>2</number>
+               <number>0</number>
               </property>
               <widget class="QWidget" name="welcomePage">
                <layout class="QVBoxLayout" name="verticalLayout_15">
@@ -424,70 +441,6 @@
                  <number>0</number>
                 </property>
                 <item>
-                 <layout class="QHBoxLayout" name="horizontalLayout_9">
-                  <property name="spacing">
-                   <number>0</number>
-                  </property>
-                  <property name="topMargin">
-                   <number>7</number>
-                  </property>
-                  <property name="rightMargin">
-                   <number>7</number>
-                  </property>
-                  <item>
-                   <spacer name="horizontalSpacer_11">
-                    <property name="orientation">
-                     <enum>Qt::Horizontal</enum>
-                    </property>
-                    <property name="sizeHint" stdset="0">
-                     <size>
-                      <width>40</width>
-                      <height>20</height>
-                     </size>
-                    </property>
-                   </spacer>
-                  </item>
-                  <item>
-                   <widget class="QPushButton" name="settingsButton">
-                    <property name="sizePolicy">
-                     <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-                      <horstretch>0</horstretch>
-                      <verstretch>0</verstretch>
-                     </sizepolicy>
-                    </property>
-                    <property name="minimumSize">
-                     <size>
-                      <width>30</width>
-                      <height>30</height>
-                     </size>
-                    </property>
-                    <property name="maximumSize">
-                     <size>
-                      <width>30</width>
-                      <height>30</height>
-                     </size>
-                    </property>
-                    <property name="toolTip">
-                     <string>Configuration menu</string>
-                    </property>
-                    <property name="text">
-                     <string/>
-                    </property>
-                    <property name="icon">
-                     <iconset resource="ressources.qrc">
-                      <normaloff>:/images/icons/ic_settings_white_48dp_2x.png</normaloff>:/images/icons/ic_settings_white_48dp_2x.png</iconset>
-                    </property>
-                    <property name="iconSize">
-                     <size>
-                      <width>18</width>
-                      <height>18</height>
-                     </size>
-                    </property>
-                   </widget>
-                  </item>
-                 </layout>
-                </item>
-                <item>
                  <spacer name="verticalSpacer">
                   <property name="orientation">
                    <enum>Qt::Vertical</enum>
@@ -592,8 +545,8 @@
                   </property>
                   <property name="text">
                    <string>
-                                        This is your RingID.
-                                        Copy and share it with your friends!
+ This is your RingID.
+Copy and share it with your friends!
                                       </string>
                   </property>
                   <property name="textFormat">
@@ -870,89 +823,8 @@
                 </item>
                </layout>
               </widget>
-              <widget class="QWidget" name="sendContactRequestPage">
-               <layout class="QVBoxLayout" name="verticalLayout_7">
-                <item>
-                 <layout class="QHBoxLayout" name="horizontalLayout_10">
-                  <property name="bottomMargin">
-                   <number>0</number>
-                  </property>
-                  <item>
-                   <widget class="QPushButton" name="sendCRBackButton">
-                    <property name="minimumSize">
-                     <size>
-                      <width>30</width>
-                      <height>30</height>
-                     </size>
-                    </property>
-                    <property name="maximumSize">
-                     <size>
-                      <width>30</width>
-                      <height>30</height>
-                     </size>
-                    </property>
-                    <property name="toolTip">
-                     <string>Back to homepage button</string>
-                    </property>
-                    <property name="text">
-                     <string/>
-                    </property>
-                    <property name="icon">
-                     <iconset resource="ressources.qrc">
-                      <normaloff>:/images/icons/ic_arrow_back_white_24dp.png</normaloff>:/images/icons/ic_arrow_back_white_24dp.png</iconset>
-                    </property>
-                    <property name="iconSize">
-                     <size>
-                      <width>18</width>
-                      <height>18</height>
-                     </size>
-                    </property>
-                    <property name="flat">
-                     <bool>false</bool>
-                    </property>
-                   </widget>
-                  </item>
-                  <item>
-                   <spacer name="horizontalSpacer_14">
-                    <property name="orientation">
-                     <enum>Qt::Horizontal</enum>
-                    </property>
-                    <property name="sizeHint" stdset="0">
-                     <size>
-                      <width>40</width>
-                      <height>20</height>
-                     </size>
-                    </property>
-                   </spacer>
-                  </item>
-                 </layout>
-                </item>
-                <item>
-                 <widget class="SendContactRequestWidget" name="sendContactRequestWidget" native="true">
-                  <property name="sizePolicy">
-                   <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
-                    <horstretch>0</horstretch>
-                    <verstretch>0</verstretch>
-                   </sizepolicy>
-                  </property>
-                  <property name="locale">
-                   <locale language="English" country="UnitedStates"/>
-                  </property>
-                 </widget>
-                </item>
-               </layout>
-              </widget>
-              <widget class="QWidget" name="messagingPage">
-               <property name="sizePolicy">
-                <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
-                 <horstretch>0</horstretch>
-                 <verstretch>0</verstretch>
-                </sizepolicy>
-               </property>
-               <layout class="QVBoxLayout" name="verticalLayout_12" stretch="0,0">
-                <property name="spacing">
-                 <number>0</number>
-                </property>
+              <widget class="QWidget" name="mainActivityWidget">
+               <layout class="QGridLayout" name="gridLayout_5">
                 <property name="leftMargin">
                  <number>0</number>
                 </property>
@@ -965,331 +837,44 @@
                 <property name="bottomMargin">
                  <number>0</number>
                 </property>
-                <item>
-                 <widget class="QWidget" name="messagingHeaderWidget" native="true">
-                  <layout class="QHBoxLayout" name="horizontalLayout_6">
-                   <property name="spacing">
-                    <number>6</number>
-                   </property>
-                   <property name="leftMargin">
-                    <number>10</number>
-                   </property>
-                   <property name="topMargin">
-                    <number>6</number>
-                   </property>
-                   <property name="rightMargin">
-                    <number>10</number>
-                   </property>
-                   <property name="bottomMargin">
-                    <number>6</number>
-                   </property>
-                   <item>
-                    <widget class="QPushButton" name="imBackButton">
-                     <property name="sizePolicy">
-                      <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
-                       <horstretch>0</horstretch>
-                       <verstretch>0</verstretch>
-                      </sizepolicy>
-                     </property>
-                     <property name="minimumSize">
-                      <size>
-                       <width>30</width>
-                       <height>30</height>
-                      </size>
-                     </property>
-                     <property name="maximumSize">
-                      <size>
-                       <width>30</width>
-                       <height>30</height>
-                      </size>
-                     </property>
-                     <property name="toolTip">
-                      <string>Back to homepage button</string>
-                     </property>
-                     <property name="text">
-                      <string/>
-                     </property>
-                     <property name="icon">
-                      <iconset resource="ressources.qrc">
-                       <normaloff>:/images/icons/ic_arrow_back_24px.svg</normaloff>:/images/icons/ic_arrow_back_24px.svg</iconset>
-                     </property>
-                     <property name="iconSize">
-                      <size>
-                       <width>18</width>
-                       <height>18</height>
-                      </size>
-                     </property>
-                     <property name="flat">
-                      <bool>false</bool>
-                     </property>
-                    </widget>
-                   </item>
-                   <item>
-                    <layout class="QVBoxLayout" name="verticalLayout_14">
-                     <property name="spacing">
-                      <number>0</number>
-                     </property>
-                     <property name="leftMargin">
-                      <number>0</number>
-                     </property>
-                     <property name="bottomMargin">
-                      <number>0</number>
-                     </property>
-                     <item>
-                      <widget class="QLabel" name="imNameLabel">
-                       <property name="sizePolicy">
-                        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
-                         <horstretch>0</horstretch>
-                         <verstretch>0</verstretch>
-                        </sizepolicy>
-                       </property>
-                       <property name="minimumSize">
-                        <size>
-                         <width>200</width>
-                         <height>0</height>
-                        </size>
-                       </property>
-                       <property name="font">
-                        <font>
-                         <pointsize>10</pointsize>
-                         <weight>75</weight>
-                         <bold>true</bold>
-                        </font>
-                       </property>
-                       <property name="styleSheet">
-                        <string notr="true">color: rgb(63,63,63);</string>
-                       </property>
-                       <property name="text">
-                        <string/>
-                       </property>
-                       <property name="alignment">
-                        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
-                       </property>
-                       <property name="wordWrap">
-                        <bool>false</bool>
-                       </property>
-                      </widget>
-                     </item>
-                     <item>
-                      <widget class="QLabel" name="imIdLabel">
-                       <property name="sizePolicy">
-                        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
-                         <horstretch>0</horstretch>
-                         <verstretch>0</verstretch>
-                        </sizepolicy>
-                       </property>
-                       <property name="minimumSize">
-                        <size>
-                         <width>200</width>
-                         <height>0</height>
-                        </size>
-                       </property>
-                       <property name="font">
-                        <font>
-                         <pointsize>9</pointsize>
-                        </font>
-                       </property>
-                       <property name="styleSheet">
-                        <string notr="true">color: rgb(192,192,192);</string>
-                       </property>
-                       <property name="text">
-                        <string/>
-                       </property>
-                       <property name="alignment">
-                        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
-                       </property>
-                      </widget>
-                     </item>
-                    </layout>
-                   </item>
-                   <item>
-                    <spacer name="verticalSpacer_3">
-                     <property name="orientation">
-                      <enum>Qt::Vertical</enum>
-                     </property>
-                     <property name="sizeType">
-                      <enum>QSizePolicy::Fixed</enum>
-                     </property>
-                     <property name="sizeHint" stdset="0">
-                      <size>
-                       <width>0</width>
-                       <height>40</height>
-                      </size>
-                     </property>
-                    </spacer>
-                   </item>
-                   <item>
-                    <spacer name="horizontalSpacer_6">
-                     <property name="orientation">
-                      <enum>Qt::Horizontal</enum>
-                     </property>
-                     <property name="sizeHint" stdset="0">
-                      <size>
-                       <width>40</width>
-                       <height>20</height>
-                      </size>
-                     </property>
-                    </spacer>
-                   </item>
-                   <item>
-                    <widget class="QPushButton" name="btnAudioCall">
-                     <property name="minimumSize">
-                      <size>
-                       <width>30</width>
-                       <height>30</height>
-                      </size>
-                     </property>
-                     <property name="maximumSize">
-                      <size>
-                       <width>30</width>
-                       <height>30</height>
-                      </size>
-                     </property>
-                     <property name="text">
-                      <string/>
-                     </property>
-                     <property name="icon">
-                      <iconset resource="ressources.qrc">
-                       <normaloff>:/images/icons/ic_phone_24px.svg</normaloff>:/images/icons/ic_phone_24px.svg</iconset>
-                     </property>
-                     <property name="iconSize">
-                      <size>
-                       <width>18</width>
-                       <height>18</height>
-                      </size>
-                     </property>
-                    </widget>
-                   </item>
-                   <item>
-                    <widget class="QPushButton" name="btnVideoCall">
-                     <property name="minimumSize">
-                      <size>
-                       <width>30</width>
-                       <height>30</height>
-                      </size>
-                     </property>
-                     <property name="maximumSize">
-                      <size>
-                       <width>30</width>
-                       <height>30</height>
-                      </size>
-                     </property>
-                     <property name="text">
-                      <string/>
-                     </property>
-                     <property name="icon">
-                      <iconset resource="ressources.qrc">
-                       <normaloff>:/images/icons/ic_video_call_24px.svg</normaloff>:/images/icons/ic_video_call_24px.svg</iconset>
-                     </property>
-                     <property name="iconSize">
-                      <size>
-                       <width>18</width>
-                       <height>18</height>
-                      </size>
-                     </property>
-                    </widget>
-                   </item>
-                   <item>
-                    <widget class="QPushButton" name="sendContactRequestButton">
-                     <property name="sizePolicy">
-                      <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
-                       <horstretch>0</horstretch>
-                       <verstretch>0</verstretch>
-                      </sizepolicy>
-                     </property>
-                     <property name="minimumSize">
-                      <size>
-                       <width>30</width>
-                       <height>30</height>
-                      </size>
-                     </property>
-                     <property name="maximumSize">
-                      <size>
-                       <width>30</width>
-                       <height>30</height>
-                      </size>
-                     </property>
-                     <property name="toolTip">
-                      <string>Add to contacts</string>
-                     </property>
-                     <property name="styleSheet">
-                      <string notr="true"/>
-                     </property>
-                     <property name="text">
-                      <string/>
-                     </property>
-                     <property name="icon">
-                      <iconset resource="ressources.qrc">
-                       <normaloff>:/images/icons/ic_person_add_black_24dp_2x.png</normaloff>:/images/icons/ic_person_add_black_24dp_2x.png</iconset>
-                     </property>
-                     <property name="iconSize">
-                      <size>
-                       <width>18</width>
-                       <height>18</height>
-                      </size>
-                     </property>
-                    </widget>
-                   </item>
-                  </layout>
-                 </widget>
-                </item>
-                <item>
-                 <layout class="QHBoxLayout" name="messagesHBoxLayout" stretch="0,0,0">
-                  <property name="spacing">
-                   <number>0</number>
+                <property name="spacing">
+                 <number>0</number>
+                </property>
+                <item row="0" column="0">
+                 <widget class="QSplitter" name="mainActivitySplitter">
+                  <property name="sizePolicy">
+                   <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+                    <horstretch>0</horstretch>
+                    <verstretch>0</verstretch>
+                   </sizepolicy>
                   </property>
-                  <property name="leftMargin">
-                   <number>10</number>
+                  <property name="orientation">
+                   <enum>Qt::Horizontal</enum>
                   </property>
-                  <property name="topMargin">
-                   <number>0</number>
+                  <property name="handleWidth">
+                   <number>2</number>
                   </property>
-                  <property name="rightMargin">
-                   <number>10</number>
+                  <property name="childrenCollapsible">
+                   <bool>true</bool>
                   </property>
-                  <property name="bottomMargin">
-                   <number>0</number>
-                  </property>
-                  <item>
-                   <spacer name="horizontalSpacer">
-                    <property name="orientation">
-                     <enum>Qt::Horizontal</enum>
-                    </property>
-                    <property name="sizeType">
-                     <enum>QSizePolicy::Minimum</enum>
-                    </property>
-                    <property name="sizeHint" stdset="0">
-                     <size>
-                      <width>0</width>
-                      <height>20</height>
-                     </size>
-                    </property>
-                   </spacer>
-                  </item>
-                  <item>
-                   <widget class="QWidget" name="messagesListWidget" native="true">
+                  <widget class="QStackedWidget" name="callStackWidget">
+                   <property name="sizePolicy">
+                    <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+                     <horstretch>0</horstretch>
+                     <verstretch>0</verstretch>
+                    </sizepolicy>
+                   </property>
+                   <property name="currentIndex">
+                    <number>1</number>
+                   </property>
+                   <widget class="QWidget" name="videoPage">
                     <property name="sizePolicy">
-                     <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+                     <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
                       <horstretch>0</horstretch>
                       <verstretch>0</verstretch>
                      </sizepolicy>
                     </property>
-                    <property name="minimumSize">
-                     <size>
-                      <width>200</width>
-                      <height>0</height>
-                     </size>
-                    </property>
-                    <property name="maximumSize">
-                     <size>
-                      <width>640</width>
-                      <height>16777215</height>
-                     </size>
-                    </property>
-                    <layout class="QVBoxLayout" name="verticalLayout_6">
-                     <property name="spacing">
-                      <number>0</number>
-                     </property>
+                    <layout class="QGridLayout" name="gridLayout_6">
                      <property name="leftMargin">
                       <number>0</number>
                      </property>
@@ -1302,10 +887,301 @@
                      <property name="bottomMargin">
                       <number>0</number>
                      </property>
-                     <item>
-                      <widget class="QListView" name="listMessageView">
+                     <property name="spacing">
+                      <number>0</number>
+                     </property>
+                     <item row="0" column="0">
+                      <widget class="VideoView" name="videoWidget" native="true">
                        <property name="sizePolicy">
-                        <sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
+                        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+                         <horstretch>0</horstretch>
+                         <verstretch>0</verstretch>
+                        </sizepolicy>
+                       </property>
+                      </widget>
+                     </item>
+                    </layout>
+                   </widget>
+                   <widget class="QWidget" name="outgoingCallPage">
+                    <property name="sizePolicy">
+                     <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+                      <horstretch>0</horstretch>
+                      <verstretch>0</verstretch>
+                     </sizepolicy>
+                    </property>
+                    <layout class="QGridLayout" name="gridLayout_7">
+                     <property name="leftMargin">
+                      <number>0</number>
+                     </property>
+                     <property name="topMargin">
+                      <number>0</number>
+                     </property>
+                     <property name="rightMargin">
+                      <number>0</number>
+                     </property>
+                     <property name="bottomMargin">
+                      <number>0</number>
+                     </property>
+                     <property name="spacing">
+                      <number>0</number>
+                     </property>
+                     <item row="0" column="0">
+                      <widget class="QWidget" name="outgoingCall" native="true">
+                       <property name="sizePolicy">
+                        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+                         <horstretch>0</horstretch>
+                         <verstretch>0</verstretch>
+                        </sizepolicy>
+                       </property>
+                       <property name="minimumSize">
+                        <size>
+                         <width>244</width>
+                         <height>420</height>
+                        </size>
+                       </property>
+                       <property name="maximumSize">
+                        <size>
+                         <width>244</width>
+                         <height>16777215</height>
+                        </size>
+                       </property>
+                       <property name="baseSize">
+                        <size>
+                         <width>0</width>
+                         <height>0</height>
+                        </size>
+                       </property>
+                       <layout class="QVBoxLayout" name="spinnerLayout_3">
+                        <property name="spacing">
+                         <number>10</number>
+                        </property>
+                        <property name="leftMargin">
+                         <number>0</number>
+                        </property>
+                        <property name="topMargin">
+                         <number>0</number>
+                        </property>
+                        <property name="rightMargin">
+                         <number>0</number>
+                        </property>
+                        <property name="bottomMargin">
+                         <number>0</number>
+                        </property>
+                        <item>
+                         <layout class="QHBoxLayout" name="horizontalLayout_3">
+                          <property name="bottomMargin">
+                           <number>0</number>
+                          </property>
+                          <item>
+                           <widget class="QLabel" name="callingPhoto">
+                            <property name="sizePolicy">
+                             <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+                              <horstretch>0</horstretch>
+                              <verstretch>0</verstretch>
+                             </sizepolicy>
+                            </property>
+                            <property name="minimumSize">
+                             <size>
+                              <width>166</width>
+                              <height>166</height>
+                             </size>
+                            </property>
+                            <property name="maximumSize">
+                             <size>
+                              <width>166</width>
+                              <height>166</height>
+                             </size>
+                            </property>
+                            <property name="baseSize">
+                             <size>
+                              <width>166</width>
+                              <height>166</height>
+                             </size>
+                            </property>
+                            <property name="text">
+                             <string/>
+                            </property>
+                            <property name="alignment">
+                             <set>Qt::AlignCenter</set>
+                            </property>
+                           </widget>
+                          </item>
+                         </layout>
+                        </item>
+                        <item>
+                         <widget class="QLabel" name="callingBestNameLabel">
+                          <property name="font">
+                           <font>
+                            <family>Segoe UI Emoji</family>
+                            <pointsize>12</pointsize>
+                           </font>
+                          </property>
+                          <property name="styleSheet">
+                           <string notr="true">color: rgb(128, 128, 128);</string>
+                          </property>
+                          <property name="text">
+                           <string>best name</string>
+                          </property>
+                          <property name="alignment">
+                           <set>Qt::AlignCenter</set>
+                          </property>
+                         </widget>
+                        </item>
+                        <item>
+                         <widget class="QLabel" name="callingBestIdLabel">
+                          <property name="sizePolicy">
+                           <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+                            <horstretch>0</horstretch>
+                            <verstretch>0</verstretch>
+                           </sizepolicy>
+                          </property>
+                          <property name="font">
+                           <font>
+                            <pointsize>8</pointsize>
+                           </font>
+                          </property>
+                          <property name="styleSheet">
+                           <string notr="true">color: rgb(174, 174, 174);</string>
+                          </property>
+                          <property name="text">
+                           <string>best Id</string>
+                          </property>
+                          <property name="alignment">
+                           <set>Qt::AlignCenter</set>
+                          </property>
+                         </widget>
+                        </item>
+                        <item>
+                         <widget class="QLabel" name="spinnerLabel">
+                          <property name="sizePolicy">
+                           <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+                            <horstretch>0</horstretch>
+                            <verstretch>0</verstretch>
+                           </sizepolicy>
+                          </property>
+                          <property name="minimumSize">
+                           <size>
+                            <width>20</width>
+                            <height>0</height>
+                           </size>
+                          </property>
+                          <property name="text">
+                           <string/>
+                          </property>
+                          <property name="alignment">
+                           <set>Qt::AlignCenter</set>
+                          </property>
+                         </widget>
+                        </item>
+                        <item>
+                         <layout class="QVBoxLayout" name="verticalLayout_6">
+                          <property name="spacing">
+                           <number>20</number>
+                          </property>
+                          <property name="bottomMargin">
+                           <number>0</number>
+                          </property>
+                          <item>
+                           <layout class="QHBoxLayout" name="horizontalLayout">
+                            <property name="spacing">
+                             <number>0</number>
+                            </property>
+                            <property name="bottomMargin">
+                             <number>0</number>
+                            </property>
+                            <item>
+                             <widget class="QPushButton" name="cancelButton">
+                              <property name="sizePolicy">
+                               <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+                                <horstretch>0</horstretch>
+                                <verstretch>0</verstretch>
+                               </sizepolicy>
+                              </property>
+                              <property name="minimumSize">
+                               <size>
+                                <width>56</width>
+                                <height>56</height>
+                               </size>
+                              </property>
+                              <property name="maximumSize">
+                               <size>
+                                <width>56</width>
+                                <height>56</height>
+                               </size>
+                              </property>
+                              <property name="baseSize">
+                               <size>
+                                <width>56</width>
+                                <height>56</height>
+                               </size>
+                              </property>
+                              <property name="toolTip">
+                               <string>Cancel outgoing call</string>
+                              </property>
+                              <property name="layoutDirection">
+                               <enum>Qt::LeftToRight</enum>
+                              </property>
+                              <property name="icon">
+                               <iconset resource="ressources.qrc">
+                                <normaloff>:/images/icons/ic_close_white_24dp.png</normaloff>:/images/icons/ic_close_white_24dp.png</iconset>
+                              </property>
+                              <property name="iconSize">
+                               <size>
+                                <width>24</width>
+                                <height>24</height>
+                               </size>
+                              </property>
+                             </widget>
+                            </item>
+                           </layout>
+                          </item>
+                          <item>
+                           <widget class="QLabel" name="cancelCallLabel">
+                            <property name="styleSheet">
+                             <string notr="true">color: rgb(174, 174, 174);</string>
+                            </property>
+                            <property name="text">
+                             <string>Cancel</string>
+                            </property>
+                            <property name="alignment">
+                             <set>Qt::AlignCenter</set>
+                            </property>
+                           </widget>
+                          </item>
+                         </layout>
+                        </item>
+                       </layout>
+                      </widget>
+                     </item>
+                    </layout>
+                   </widget>
+                   <widget class="QWidget" name="incomingCallPage">
+                    <property name="sizePolicy">
+                     <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+                      <horstretch>0</horstretch>
+                      <verstretch>0</verstretch>
+                     </sizepolicy>
+                    </property>
+                    <layout class="QGridLayout" name="gridLayout_8">
+                     <property name="leftMargin">
+                      <number>0</number>
+                     </property>
+                     <property name="topMargin">
+                      <number>0</number>
+                     </property>
+                     <property name="rightMargin">
+                      <number>0</number>
+                     </property>
+                     <property name="bottomMargin">
+                      <number>0</number>
+                     </property>
+                     <property name="spacing">
+                      <number>0</number>
+                     </property>
+                     <item row="0" column="0">
+                      <widget class="QWidget" name="callInvite" native="true">
+                       <property name="sizePolicy">
+                        <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
                          <horstretch>0</horstretch>
                          <verstretch>0</verstretch>
                         </sizepolicy>
@@ -1313,86 +1189,359 @@
                        <property name="minimumSize">
                         <size>
                          <width>200</width>
-                         <height>0</height>
+                         <height>420</height>
                         </size>
                        </property>
                        <property name="maximumSize">
                         <size>
-                         <width>640</width>
-                         <height>16777215</height>
+                         <width>200</width>
+                         <height>420</height>
                         </size>
                        </property>
-                       <property name="font">
-                        <font>
-                         <stylestrategy>PreferAntialias</stylestrategy>
-                        </font>
+                       <property name="baseSize">
+                        <size>
+                         <width>200</width>
+                         <height>420</height>
+                        </size>
                        </property>
-                       <property name="focusPolicy">
-                        <enum>Qt::NoFocus</enum>
-                       </property>
-                       <property name="frameShape">
-                        <enum>QFrame::NoFrame</enum>
-                       </property>
-                       <property name="frameShadow">
-                        <enum>QFrame::Sunken</enum>
-                       </property>
-                       <property name="horizontalScrollBarPolicy">
-                        <enum>Qt::ScrollBarAsNeeded</enum>
-                       </property>
-                       <property name="verticalScrollMode">
-                        <enum>QAbstractItemView::ScrollPerPixel</enum>
-                       </property>
-                       <property name="horizontalScrollMode">
-                        <enum>QAbstractItemView::ScrollPerPixel</enum>
-                       </property>
-                       <property name="spacing">
-                        <number>1</number>
-                       </property>
-                       <property name="wordWrap">
-                        <bool>true</bool>
-                       </property>
-                       <property name="selectionRectVisible">
-                        <bool>false</bool>
-                       </property>
+                       <layout class="QVBoxLayout" name="callInvite" stretch="0,2,1,2,1,9">
+                        <property name="spacing">
+                         <number>0</number>
+                        </property>
+                        <item>
+                         <widget class="QLabel" name="callerPhoto">
+                          <property name="sizePolicy">
+                           <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+                            <horstretch>0</horstretch>
+                            <verstretch>0</verstretch>
+                           </sizepolicy>
+                          </property>
+                          <property name="minimumSize">
+                           <size>
+                            <width>166</width>
+                            <height>166</height>
+                           </size>
+                          </property>
+                          <property name="maximumSize">
+                           <size>
+                            <width>166</width>
+                            <height>166</height>
+                           </size>
+                          </property>
+                          <property name="baseSize">
+                           <size>
+                            <width>166</width>
+                            <height>166</height>
+                           </size>
+                          </property>
+                          <property name="text">
+                           <string/>
+                          </property>
+                          <property name="alignment">
+                           <set>Qt::AlignCenter</set>
+                          </property>
+                         </widget>
+                        </item>
+                        <item>
+                         <spacer name="verticalSpacer_3">
+                          <property name="orientation">
+                           <enum>Qt::Vertical</enum>
+                          </property>
+                          <property name="sizeType">
+                           <enum>QSizePolicy::Fixed</enum>
+                          </property>
+                          <property name="sizeHint" stdset="0">
+                           <size>
+                            <width>20</width>
+                            <height>20</height>
+                           </size>
+                          </property>
+                         </spacer>
+                        </item>
+                        <item alignment="Qt::AlignHCenter">
+                         <widget class="QLabel" name="callerBestNameLabel">
+                          <property name="font">
+                           <font>
+                            <family>Segoe UI Emoji</family>
+                            <pointsize>12</pointsize>
+                           </font>
+                          </property>
+                          <property name="styleSheet">
+                           <string notr="true">color: rgb(128, 128, 128);</string>
+                          </property>
+                          <property name="text">
+                           <string>best name</string>
+                          </property>
+                         </widget>
+                        </item>
+                        <item>
+                         <widget class="QLabel" name="wantToTalkLabel">
+                          <property name="styleSheet">
+                           <string notr="true">color: rgb(174, 174, 174);</string>
+                          </property>
+                          <property name="text">
+                           <string>Wants to talk to you!</string>
+                          </property>
+                          <property name="alignment">
+                           <set>Qt::AlignCenter</set>
+                          </property>
+                         </widget>
+                        </item>
+                        <item>
+                         <spacer name="verticalSpacer_5">
+                          <property name="orientation">
+                           <enum>Qt::Vertical</enum>
+                          </property>
+                          <property name="sizeType">
+                           <enum>QSizePolicy::Fixed</enum>
+                          </property>
+                          <property name="sizeHint" stdset="0">
+                           <size>
+                            <width>20</width>
+                            <height>50</height>
+                           </size>
+                          </property>
+                         </spacer>
+                        </item>
+                        <item>
+                         <layout class="QVBoxLayout" name="verticalLayout_3" stretch="2">
+                          <property name="spacing">
+                           <number>0</number>
+                          </property>
+                          <property name="rightMargin">
+                           <number>0</number>
+                          </property>
+                          <property name="bottomMargin">
+                           <number>0</number>
+                          </property>
+                          <item>
+                           <layout class="QHBoxLayout" name="horizontalLayout_2">
+                            <property name="spacing">
+                             <number>0</number>
+                            </property>
+                            <property name="topMargin">
+                             <number>0</number>
+                            </property>
+                            <property name="rightMargin">
+                             <number>0</number>
+                            </property>
+                            <item>
+                             <layout class="QVBoxLayout" name="verticalLayout_4">
+                              <property name="spacing">
+                               <number>0</number>
+                              </property>
+                              <property name="rightMargin">
+                               <number>0</number>
+                              </property>
+                              <item>
+                               <layout class="QHBoxLayout" name="horizontalLayout_4">
+                                <property name="bottomMargin">
+                                 <number>0</number>
+                                </property>
+                                <item>
+                                 <widget class="QPushButton" name="acceptButton">
+                                  <property name="sizePolicy">
+                                   <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+                                    <horstretch>0</horstretch>
+                                    <verstretch>0</verstretch>
+                                   </sizepolicy>
+                                  </property>
+                                  <property name="minimumSize">
+                                   <size>
+                                    <width>56</width>
+                                    <height>56</height>
+                                   </size>
+                                  </property>
+                                  <property name="toolTip">
+                                   <string>Answer incoming call button</string>
+                                  </property>
+                                  <property name="icon">
+                                   <iconset resource="ressources.qrc">
+                                    <normaloff>:/images/icons/ic_done_white_24dp.png</normaloff>:/images/icons/ic_done_white_24dp.png</iconset>
+                                  </property>
+                                  <property name="iconSize">
+                                   <size>
+                                    <width>24</width>
+                                    <height>24</height>
+                                   </size>
+                                  </property>
+                                 </widget>
+                                </item>
+                               </layout>
+                              </item>
+                              <item>
+                               <widget class="QLabel" name="acceptLabel">
+                                <property name="styleSheet">
+                                 <string notr="true">color: rgb(174, 174, 174);</string>
+                                </property>
+                                <property name="text">
+                                 <string>Answer</string>
+                                </property>
+                                <property name="alignment">
+                                 <set>Qt::AlignCenter</set>
+                                </property>
+                               </widget>
+                              </item>
+                             </layout>
+                            </item>
+                            <item>
+                             <spacer name="horizontalSpacer_21">
+                              <property name="orientation">
+                               <enum>Qt::Horizontal</enum>
+                              </property>
+                              <property name="sizeType">
+                               <enum>QSizePolicy::Fixed</enum>
+                              </property>
+                              <property name="sizeHint" stdset="0">
+                               <size>
+                                <width>40</width>
+                                <height>20</height>
+                               </size>
+                              </property>
+                             </spacer>
+                            </item>
+                            <item>
+                             <layout class="QVBoxLayout" name="verticalLayout_7">
+                              <property name="spacing">
+                               <number>0</number>
+                              </property>
+                              <property name="bottomMargin">
+                               <number>0</number>
+                              </property>
+                              <item>
+                               <layout class="QHBoxLayout" name="horizontalLayout_5">
+                                <item>
+                                 <widget class="QPushButton" name="refuseButton">
+                                  <property name="sizePolicy">
+                                   <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+                                    <horstretch>0</horstretch>
+                                    <verstretch>0</verstretch>
+                                   </sizepolicy>
+                                  </property>
+                                  <property name="minimumSize">
+                                   <size>
+                                    <width>56</width>
+                                    <height>56</height>
+                                   </size>
+                                  </property>
+                                  <property name="toolTip">
+                                   <string>Ignore incoming call button</string>
+                                  </property>
+                                  <property name="layoutDirection">
+                                   <enum>Qt::RightToLeft</enum>
+                                  </property>
+                                  <property name="icon">
+                                   <iconset resource="ressources.qrc">
+                                    <normaloff>:/images/icons/ic_close_white_24dp.png</normaloff>:/images/icons/ic_close_white_24dp.png</iconset>
+                                  </property>
+                                  <property name="iconSize">
+                                   <size>
+                                    <width>24</width>
+                                    <height>24</height>
+                                   </size>
+                                  </property>
+                                 </widget>
+                                </item>
+                               </layout>
+                              </item>
+                              <item>
+                               <widget class="QLabel" name="refuseLabel">
+                                <property name="layoutDirection">
+                                 <enum>Qt::RightToLeft</enum>
+                                </property>
+                                <property name="styleSheet">
+                                 <string notr="true">color: rgb(174, 174, 174);</string>
+                                </property>
+                                <property name="text">
+                                 <string>Ignore</string>
+                                </property>
+                                <property name="alignment">
+                                 <set>Qt::AlignCenter</set>
+                                </property>
+                               </widget>
+                              </item>
+                             </layout>
+                            </item>
+                           </layout>
+                          </item>
+                         </layout>
+                        </item>
+                       </layout>
                       </widget>
                      </item>
-                     <item>
-                      <layout class="QHBoxLayout" name="imSendHBoxLayout">
-                       <property name="leftMargin">
-                        <number>16</number>
-                       </property>
-                       <property name="topMargin">
-                        <number>16</number>
-                       </property>
-                       <property name="rightMargin">
+                    </layout>
+                   </widget>
+                  </widget>
+                  <widget class="QWidget" name="messagesWidget" native="true">
+                   <property name="enabled">
+                    <bool>true</bool>
+                   </property>
+                   <property name="sizePolicy">
+                    <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+                     <horstretch>0</horstretch>
+                     <verstretch>0</verstretch>
+                    </sizepolicy>
+                   </property>
+                   <property name="minimumSize">
+                    <size>
+                     <width>358</width>
+                     <height>0</height>
+                    </size>
+                   </property>
+                   <property name="maximumSize">
+                    <size>
+                     <width>16777215</width>
+                     <height>16777215</height>
+                    </size>
+                   </property>
+                   <layout class="QVBoxLayout" name="verticalLayout_8">
+                    <property name="spacing">
+                     <number>0</number>
+                    </property>
+                    <property name="leftMargin">
+                     <number>0</number>
+                    </property>
+                    <property name="topMargin">
+                     <number>0</number>
+                    </property>
+                    <property name="rightMargin">
+                     <number>0</number>
+                    </property>
+                    <property name="bottomMargin">
+                     <number>0</number>
+                    </property>
+                    <item>
+                     <widget class="QWidget" name="messagingHeaderWidget" native="true">
+                      <property name="sizePolicy">
+                       <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+                        <horstretch>0</horstretch>
+                        <verstretch>0</verstretch>
+                       </sizepolicy>
+                      </property>
+                      <layout class="QHBoxLayout" name="horizontalLayout_10">
+                       <property name="spacing">
                         <number>6</number>
                        </property>
+                       <property name="leftMargin">
+                        <number>10</number>
+                       </property>
+                       <property name="topMargin">
+                        <number>6</number>
+                       </property>
+                       <property name="rightMargin">
+                        <number>10</number>
+                       </property>
                        <property name="bottomMargin">
-                        <number>16</number>
+                        <number>6</number>
                        </property>
                        <item>
-                        <widget class="QLineEdit" name="imMessageEdit">
-                         <property name="minimumSize">
-                          <size>
-                           <width>0</width>
-                           <height>30</height>
-                          </size>
+                        <widget class="QPushButton" name="imBackButton">
+                         <property name="sizePolicy">
+                          <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+                           <horstretch>0</horstretch>
+                           <verstretch>0</verstretch>
+                          </sizepolicy>
                          </property>
-                         <property name="font">
-                          <font>
-                           <pointsize>10</pointsize>
-                          </font>
-                         </property>
-                         <property name="inputMask">
-                          <string/>
-                         </property>
-                         <property name="placeholderText">
-                          <string>Type your message here</string>
-                         </property>
-                        </widget>
-                       </item>
-                       <item>
-                        <widget class="QPushButton" name="sendIMButton">
                          <property name="minimumSize">
                           <size>
                            <width>30</width>
@@ -1406,14 +1555,223 @@
                           </size>
                          </property>
                          <property name="toolTip">
-                          <string>send message</string>
+                          <string>Back to homepage button</string>
                          </property>
                          <property name="text">
                           <string/>
                          </property>
                          <property name="icon">
                           <iconset resource="ressources.qrc">
-                           <normaloff>:/images/icons/ic_send_24px.svg</normaloff>:/images/icons/ic_send_24px.svg</iconset>
+                           <normaloff>:/images/icons/ic_arrow_back_24px.svg</normaloff>:/images/icons/ic_arrow_back_24px.svg</iconset>
+                         </property>
+                         <property name="iconSize">
+                          <size>
+                           <width>18</width>
+                           <height>18</height>
+                          </size>
+                         </property>
+                         <property name="flat">
+                          <bool>false</bool>
+                         </property>
+                        </widget>
+                       </item>
+                       <item>
+                        <layout class="QVBoxLayout" name="verticalLayout_16">
+                         <property name="spacing">
+                          <number>0</number>
+                         </property>
+                         <property name="leftMargin">
+                          <number>0</number>
+                         </property>
+                         <property name="bottomMargin">
+                          <number>0</number>
+                         </property>
+                         <item>
+                          <widget class="QLabel" name="imNameLabel">
+                           <property name="sizePolicy">
+                            <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+                             <horstretch>0</horstretch>
+                             <verstretch>0</verstretch>
+                            </sizepolicy>
+                           </property>
+                           <property name="minimumSize">
+                            <size>
+                             <width>200</width>
+                             <height>0</height>
+                            </size>
+                           </property>
+                           <property name="font">
+                            <font>
+                             <pointsize>10</pointsize>
+                             <weight>75</weight>
+                             <bold>true</bold>
+                            </font>
+                           </property>
+                           <property name="styleSheet">
+                            <string notr="true">color: rgb(63,63,63);</string>
+                           </property>
+                           <property name="text">
+                            <string/>
+                           </property>
+                           <property name="alignment">
+                            <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+                           </property>
+                           <property name="wordWrap">
+                            <bool>false</bool>
+                           </property>
+                          </widget>
+                         </item>
+                         <item>
+                          <widget class="QLabel" name="imIdLabel">
+                           <property name="sizePolicy">
+                            <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+                             <horstretch>0</horstretch>
+                             <verstretch>0</verstretch>
+                            </sizepolicy>
+                           </property>
+                           <property name="minimumSize">
+                            <size>
+                             <width>200</width>
+                             <height>0</height>
+                            </size>
+                           </property>
+                           <property name="font">
+                            <font>
+                             <pointsize>9</pointsize>
+                            </font>
+                           </property>
+                           <property name="styleSheet">
+                            <string notr="true">color: rgb(192,192,192);</string>
+                           </property>
+                           <property name="text">
+                            <string/>
+                           </property>
+                           <property name="alignment">
+                            <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+                           </property>
+                          </widget>
+                         </item>
+                        </layout>
+                       </item>
+                       <item>
+                        <spacer name="verticalSpacer_6">
+                         <property name="orientation">
+                          <enum>Qt::Vertical</enum>
+                         </property>
+                         <property name="sizeType">
+                          <enum>QSizePolicy::Fixed</enum>
+                         </property>
+                         <property name="sizeHint" stdset="0">
+                          <size>
+                           <width>0</width>
+                           <height>40</height>
+                          </size>
+                         </property>
+                        </spacer>
+                       </item>
+                       <item>
+                        <spacer name="horizontalSpacer_13">
+                         <property name="orientation">
+                          <enum>Qt::Horizontal</enum>
+                         </property>
+                         <property name="sizeHint" stdset="0">
+                          <size>
+                           <width>40</width>
+                           <height>20</height>
+                          </size>
+                         </property>
+                        </spacer>
+                       </item>
+                       <item>
+                        <widget class="QPushButton" name="btnAudioCall">
+                         <property name="minimumSize">
+                          <size>
+                           <width>30</width>
+                           <height>30</height>
+                          </size>
+                         </property>
+                         <property name="maximumSize">
+                          <size>
+                           <width>30</width>
+                           <height>30</height>
+                          </size>
+                         </property>
+                         <property name="text">
+                          <string/>
+                         </property>
+                         <property name="icon">
+                          <iconset resource="ressources.qrc">
+                           <normaloff>:/images/icons/ic_phone_24px.svg</normaloff>:/images/icons/ic_phone_24px.svg</iconset>
+                         </property>
+                         <property name="iconSize">
+                          <size>
+                           <width>18</width>
+                           <height>18</height>
+                          </size>
+                         </property>
+                        </widget>
+                       </item>
+                       <item>
+                        <widget class="QPushButton" name="btnVideoCall">
+                         <property name="minimumSize">
+                          <size>
+                           <width>30</width>
+                           <height>30</height>
+                          </size>
+                         </property>
+                         <property name="maximumSize">
+                          <size>
+                           <width>30</width>
+                           <height>30</height>
+                          </size>
+                         </property>
+                         <property name="text">
+                          <string/>
+                         </property>
+                         <property name="icon">
+                          <iconset resource="ressources.qrc">
+                           <normaloff>:/images/icons/ic_video_call_24px.svg</normaloff>:/images/icons/ic_video_call_24px.svg</iconset>
+                         </property>
+                         <property name="iconSize">
+                          <size>
+                           <width>18</width>
+                           <height>18</height>
+                          </size>
+                         </property>
+                        </widget>
+                       </item>
+                       <item>
+                        <widget class="QPushButton" name="sendContactRequestButton">
+                         <property name="sizePolicy">
+                          <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+                           <horstretch>0</horstretch>
+                           <verstretch>0</verstretch>
+                          </sizepolicy>
+                         </property>
+                         <property name="minimumSize">
+                          <size>
+                           <width>30</width>
+                           <height>30</height>
+                          </size>
+                         </property>
+                         <property name="maximumSize">
+                          <size>
+                           <width>30</width>
+                           <height>30</height>
+                          </size>
+                         </property>
+                         <property name="toolTip">
+                          <string>Add to contacts</string>
+                         </property>
+                         <property name="styleSheet">
+                          <string notr="true"/>
+                         </property>
+                         <property name="text">
+                          <string/>
+                         </property>
+                         <property name="icon">
+                          <iconset resource="ressources.qrc">
+                           <normaloff>:/images/icons/ic_person_add_black_24dp_2x.png</normaloff>:/images/icons/ic_person_add_black_24dp_2x.png</iconset>
                          </property>
                          <property name="iconSize">
                           <size>
@@ -1424,556 +1782,77 @@
                         </widget>
                        </item>
                       </layout>
-                     </item>
-                    </layout>
-                   </widget>
-                  </item>
-                  <item>
-                   <spacer name="horizontalSpacer_2">
-                    <property name="orientation">
-                     <enum>Qt::Horizontal</enum>
-                    </property>
-                    <property name="sizeType">
-                     <enum>QSizePolicy::Minimum</enum>
-                    </property>
-                    <property name="sizeHint" stdset="0">
-                     <size>
-                      <width>0</width>
-                      <height>20</height>
-                     </size>
-                    </property>
-                   </spacer>
-                  </item>
-                 </layout>
-                </item>
-               </layout>
-              </widget>
-              <widget class="QWidget" name="callInvitePage">
-               <layout class="QVBoxLayout" name="verticalLayout_9">
-                <property name="spacing">
-                 <number>0</number>
-                </property>
-                <property name="leftMargin">
-                 <number>0</number>
-                </property>
-                <property name="topMargin">
-                 <number>0</number>
-                </property>
-                <property name="rightMargin">
-                 <number>0</number>
-                </property>
-                <property name="bottomMargin">
-                 <number>0</number>
-                </property>
-                <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
-                 <widget class="QWidget" name="callInvite" native="true">
-                  <property name="sizePolicy">
-                   <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
-                    <horstretch>0</horstretch>
-                    <verstretch>0</verstretch>
-                   </sizepolicy>
-                  </property>
-                  <property name="minimumSize">
-                   <size>
-                    <width>166</width>
-                    <height>0</height>
-                   </size>
-                  </property>
-                  <property name="baseSize">
-                   <size>
-                    <width>166</width>
-                    <height>0</height>
-                   </size>
-                  </property>
-                  <layout class="QVBoxLayout" name="verticalLayout_4">
-                   <property name="spacing">
-                    <number>0</number>
-                   </property>
-                   <property name="leftMargin">
-                    <number>0</number>
-                   </property>
-                   <property name="topMargin">
-                    <number>0</number>
-                   </property>
-                   <property name="rightMargin">
-                    <number>0</number>
-                   </property>
-                   <property name="bottomMargin">
-                    <number>10</number>
-                   </property>
-                   <item>
-                    <layout class="QVBoxLayout" name="verticalLayout_3">
-                     <property name="spacing">
-                      <number>0</number>
-                     </property>
-                     <item>
-                      <widget class="QLabel" name="callerPhoto">
-                       <property name="sizePolicy">
-                        <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
-                         <horstretch>0</horstretch>
-                         <verstretch>0</verstretch>
-                        </sizepolicy>
-                       </property>
-                       <property name="minimumSize">
-                        <size>
-                         <width>130</width>
-                         <height>130</height>
-                        </size>
-                       </property>
-                       <property name="baseSize">
-                        <size>
-                         <width>130</width>
-                         <height>130</height>
-                        </size>
-                       </property>
-                       <property name="text">
-                        <string/>
-                       </property>
-                       <property name="pixmap">
-                        <pixmap resource="ressources.qrc">:/images/default_avatar_overlay.svg</pixmap>
-                       </property>
-                       <property name="alignment">
-                        <set>Qt::AlignCenter</set>
-                       </property>
-                      </widget>
-                     </item>
-                     <item alignment="Qt::AlignHCenter">
-                      <widget class="QLabel" name="callerIdLabel">
-                       <property name="styleSheet">
-                        <string notr="true">color: rgb(174, 174, 174);</string>
-                       </property>
-                       <property name="text">
-                        <string>Call</string>
-                       </property>
-                      </widget>
-                     </item>
-                     <item alignment="Qt::AlignHCenter">
-                      <widget class="QLabel" name="callerBestIdLabel">
-                       <property name="styleSheet">
-                        <string notr="true">color: rgb(174, 174, 174); font-style: italic;</string>
-                       </property>
-                       <property name="text">
-                        <string>BestId</string>
-                       </property>
-                      </widget>
-                     </item>
-                     <item>
-                      <widget class="QLabel" name="wantToTalkLabel">
-                       <property name="styleSheet">
-                        <string notr="true">color: rgb(174, 174, 174);</string>
-                       </property>
-                       <property name="text">
-                        <string>Wants to talk to you!</string>
-                       </property>
-                       <property name="alignment">
-                        <set>Qt::AlignCenter</set>
-                       </property>
-                      </widget>
-                     </item>
-                    </layout>
-                   </item>
-                   <item>
-                    <layout class="QHBoxLayout" name="horizontalLayout">
-                     <property name="topMargin">
-                      <number>20</number>
-                     </property>
-                     <item>
-                      <widget class="QPushButton" name="acceptButton">
-                       <property name="sizePolicy">
-                        <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-                         <horstretch>0</horstretch>
-                         <verstretch>0</verstretch>
-                        </sizepolicy>
-                       </property>
-                       <property name="minimumSize">
-                        <size>
-                         <width>56</width>
-                         <height>56</height>
-                        </size>
-                       </property>
-                       <property name="toolTip">
-                        <string>Answer incoming call button</string>
-                       </property>
-                       <property name="icon">
-                        <iconset resource="ressources.qrc">
-                         <normaloff>:/images/icons/ic_done_white_24dp.png</normaloff>:/images/icons/ic_done_white_24dp.png</iconset>
-                       </property>
-                       <property name="iconSize">
-                        <size>
-                         <width>24</width>
-                         <height>24</height>
-                        </size>
-                       </property>
-                      </widget>
-                     </item>
-                     <item>
-                      <spacer name="horizontalSpacer_4">
-                       <property name="orientation">
-                        <enum>Qt::Horizontal</enum>
-                       </property>
-                       <property name="sizeType">
-                        <enum>QSizePolicy::Fixed</enum>
-                       </property>
-                       <property name="sizeHint" stdset="0">
-                        <size>
-                         <width>40</width>
-                         <height>20</height>
-                        </size>
-                       </property>
-                      </spacer>
-                     </item>
-                     <item>
-                      <widget class="QPushButton" name="refuseButton">
-                       <property name="sizePolicy">
-                        <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-                         <horstretch>0</horstretch>
-                         <verstretch>0</verstretch>
-                        </sizepolicy>
-                       </property>
-                       <property name="minimumSize">
-                        <size>
-                         <width>56</width>
-                         <height>56</height>
-                        </size>
-                       </property>
-                       <property name="toolTip">
-                        <string>Ignore incoming call button</string>
-                       </property>
-                       <property name="layoutDirection">
-                        <enum>Qt::RightToLeft</enum>
-                       </property>
-                       <property name="icon">
-                        <iconset resource="ressources.qrc">
-                         <normaloff>:/images/icons/ic_close_white_24dp.png</normaloff>:/images/icons/ic_close_white_24dp.png</iconset>
-                       </property>
-                       <property name="iconSize">
-                        <size>
-                         <width>24</width>
-                         <height>24</height>
-                        </size>
-                       </property>
-                      </widget>
-                     </item>
-                    </layout>
-                   </item>
-                   <item>
-                    <layout class="QHBoxLayout" name="horizontalLayout_7">
-                     <item>
-                      <widget class="QLabel" name="acceptLabel">
-                       <property name="styleSheet">
-                        <string notr="true">color: rgb(174, 174, 174);</string>
-                       </property>
-                       <property name="text">
-                        <string>Answer</string>
-                       </property>
-                       <property name="alignment">
-                        <set>Qt::AlignCenter</set>
-                       </property>
-                      </widget>
-                     </item>
-                     <item>
-                      <spacer name="horizontalSpacer_5">
-                       <property name="orientation">
-                        <enum>Qt::Horizontal</enum>
-                       </property>
-                       <property name="sizeType">
-                        <enum>QSizePolicy::Fixed</enum>
-                       </property>
-                       <property name="sizeHint" stdset="0">
-                        <size>
-                         <width>40</width>
-                         <height>20</height>
-                        </size>
-                       </property>
-                      </spacer>
-                     </item>
-                     <item>
-                      <widget class="QLabel" name="refuseLabel">
-                       <property name="layoutDirection">
-                        <enum>Qt::RightToLeft</enum>
-                       </property>
-                       <property name="styleSheet">
-                        <string notr="true">color: rgb(174, 174, 174);</string>
-                       </property>
-                       <property name="text">
-                        <string>Ignore</string>
-                       </property>
-                       <property name="alignment">
-                        <set>Qt::AlignCenter</set>
-                       </property>
-                      </widget>
-                     </item>
-                    </layout>
-                   </item>
-                  </layout>
-                 </widget>
-                </item>
-               </layout>
-              </widget>
-              <widget class="QWidget" name="outboundCallPage">
-               <layout class="QVBoxLayout" name="verticalLayout_11">
-                <property name="spacing">
-                 <number>0</number>
-                </property>
-                <property name="leftMargin">
-                 <number>0</number>
-                </property>
-                <property name="topMargin">
-                 <number>0</number>
-                </property>
-                <property name="rightMargin">
-                 <number>0</number>
-                </property>
-                <property name="bottomMargin">
-                 <number>0</number>
-                </property>
-                <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
-                 <widget class="QWidget" name="outboundCall" native="true">
-                  <layout class="QVBoxLayout" name="spinnerLayout">
-                   <item>
-                    <widget class="QLabel" name="callingPhoto">
-                     <property name="sizePolicy">
-                      <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
-                       <horstretch>0</horstretch>
-                       <verstretch>0</verstretch>
-                      </sizepolicy>
-                     </property>
-                     <property name="minimumSize">
-                      <size>
-                       <width>130</width>
-                       <height>130</height>
-                      </size>
-                     </property>
-                     <property name="baseSize">
-                      <size>
-                       <width>130</width>
-                       <height>130</height>
-                      </size>
-                     </property>
-                     <property name="text">
-                      <string/>
-                     </property>
-                     <property name="pixmap">
-                      <pixmap resource="ressources.qrc">:/images/default_avatar_overlay.svg</pixmap>
-                     </property>
-                     <property name="alignment">
-                      <set>Qt::AlignCenter</set>
-                     </property>
-                    </widget>
-                   </item>
-                   <item>
-                    <widget class="QLabel" name="outboundCallLabel">
-                     <property name="styleSheet">
-                      <string notr="true">color: rgb(174, 174, 174);</string>
-                     </property>
-                     <property name="text">
-                      <string>Calling</string>
-                     </property>
-                     <property name="alignment">
-                      <set>Qt::AlignCenter</set>
-                     </property>
-                    </widget>
-                   </item>
-                   <item alignment="Qt::AlignHCenter">
-                    <widget class="QLabel" name="spinnerLabel">
-                     <property name="sizePolicy">
-                      <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
-                       <horstretch>0</horstretch>
-                       <verstretch>0</verstretch>
-                      </sizepolicy>
-                     </property>
-                     <property name="minimumSize">
-                      <size>
-                       <width>20</width>
-                       <height>0</height>
-                      </size>
-                     </property>
-                     <property name="text">
-                      <string/>
-                     </property>
-                    </widget>
-                   </item>
-                   <item alignment="Qt::AlignHCenter">
-                    <widget class="QPushButton" name="cancelButton">
-                     <property name="sizePolicy">
-                      <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-                       <horstretch>0</horstretch>
-                       <verstretch>0</verstretch>
-                      </sizepolicy>
-                     </property>
-                     <property name="minimumSize">
-                      <size>
-                       <width>56</width>
-                       <height>56</height>
-                      </size>
-                     </property>
-                     <property name="maximumSize">
-                      <size>
-                       <width>64</width>
-                       <height>64</height>
-                      </size>
-                     </property>
-                     <property name="baseSize">
-                      <size>
-                       <width>64</width>
-                       <height>64</height>
-                      </size>
-                     </property>
-                     <property name="toolTip">
-                      <string>Cancel outgoing call</string>
-                     </property>
-                     <property name="layoutDirection">
-                      <enum>Qt::LeftToRight</enum>
-                     </property>
-                     <property name="icon">
-                      <iconset resource="ressources.qrc">
-                       <normaloff>:/images/icons/ic_close_white_24dp.png</normaloff>:/images/icons/ic_close_white_24dp.png</iconset>
-                     </property>
-                     <property name="iconSize">
-                      <size>
-                       <width>24</width>
-                       <height>24</height>
-                      </size>
-                     </property>
-                    </widget>
-                   </item>
-                   <item>
-                    <widget class="QLabel" name="cancelCallLabel">
-                     <property name="styleSheet">
-                      <string notr="true">color: rgb(174, 174, 174);</string>
-                     </property>
-                     <property name="text">
-                      <string>Cancel</string>
-                     </property>
-                     <property name="alignment">
-                      <set>Qt::AlignCenter</set>
-                     </property>
-                    </widget>
-                   </item>
-                  </layout>
-                 </widget>
-                </item>
-               </layout>
-              </widget>
-              <widget class="QWidget" name="videoPage">
-               <layout class="QVBoxLayout" name="verticalLayout_10">
-                <property name="spacing">
-                 <number>0</number>
-                </property>
-                <property name="leftMargin">
-                 <number>0</number>
-                </property>
-                <property name="topMargin">
-                 <number>0</number>
-                </property>
-                <property name="rightMargin">
-                 <number>0</number>
-                </property>
-                <property name="bottomMargin">
-                 <number>0</number>
-                </property>
-                <item>
-                 <widget class="QSplitter" name="splitter_2">
-                  <property name="orientation">
-                   <enum>Qt::Horizontal</enum>
-                  </property>
-                  <widget class="VideoView" name="videoWidget" native="true">
-                   <property name="sizePolicy">
-                    <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
-                     <horstretch>0</horstretch>
-                     <verstretch>0</verstretch>
-                    </sizepolicy>
-                   </property>
+                     </widget>
+                    </item>
+                    <item>
+                     <layout class="QHBoxLayout" name="messagesHBoxLayout" stretch="0,0,0">
+                      <property name="spacing">
+                       <number>0</number>
+                      </property>
+                      <property name="leftMargin">
+                       <number>0</number>
+                      </property>
+                      <property name="topMargin">
+                       <number>0</number>
+                      </property>
+                      <property name="rightMargin">
+                       <number>0</number>
+                      </property>
+                      <property name="bottomMargin">
+                       <number>0</number>
+                      </property>
+                      <item>
+                       <spacer name="horizontalSpacer_5">
+                        <property name="orientation">
+                         <enum>Qt::Horizontal</enum>
+                        </property>
+                        <property name="sizeType">
+                         <enum>QSizePolicy::Maximum</enum>
+                        </property>
+                        <property name="sizeHint" stdset="0">
+                         <size>
+                          <width>0</width>
+                          <height>20</height>
+                         </size>
+                        </property>
+                       </spacer>
+                      </item>
+                      <item>
+                       <widget class="MessageWebView" name="messageView">
+                        <property name="sizePolicy">
+                         <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+                          <horstretch>0</horstretch>
+                          <verstretch>0</verstretch>
+                         </sizepolicy>
+                        </property>
+                        <property name="maximumSize">
+                         <size>
+                          <width>680</width>
+                          <height>16777215</height>
+                         </size>
+                        </property>
+                       </widget>
+                      </item>
+                      <item>
+                       <spacer name="horizontalSpacer_14">
+                        <property name="orientation">
+                         <enum>Qt::Horizontal</enum>
+                        </property>
+                        <property name="sizeType">
+                         <enum>QSizePolicy::Maximum</enum>
+                        </property>
+                        <property name="sizeHint" stdset="0">
+                         <size>
+                          <width>0</width>
+                          <height>20</height>
+                         </size>
+                        </property>
+                       </spacer>
+                      </item>
+                     </layout>
+                    </item>
+                   </layout>
                   </widget>
-                  <widget class="InstantMessagingWidget" name="instantMessagingWidget" native="true">
-                   <property name="sizePolicy">
-                    <sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
-                     <horstretch>0</horstretch>
-                     <verstretch>0</verstretch>
-                    </sizepolicy>
-                   </property>
-                   <property name="minimumSize">
-                    <size>
-                     <width>0</width>
-                     <height>0</height>
-                    </size>
-                   </property>
-                  </widget>
-                 </widget>
-                </item>
-               </layout>
-              </widget>
-              <widget class="QWidget" name="contactRequestPage">
-               <layout class="QVBoxLayout" name="verticalLayout_8">
-                <property name="leftMargin">
-                 <number>5</number>
-                </property>
-                <property name="topMargin">
-                 <number>5</number>
-                </property>
-                <property name="rightMargin">
-                 <number>0</number>
-                </property>
-                <property name="bottomMargin">
-                 <number>0</number>
-                </property>
-                <item>
-                 <layout class="QHBoxLayout" name="horizontalLayout_2">
-                  <item>
-                   <widget class="QPushButton" name="pendingCRBackButton">
-                    <property name="minimumSize">
-                     <size>
-                      <width>30</width>
-                      <height>30</height>
-                     </size>
-                    </property>
-                    <property name="maximumSize">
-                     <size>
-                      <width>30</width>
-                      <height>30</height>
-                     </size>
-                    </property>
-                    <property name="toolTip">
-                     <string>Back to homepage button</string>
-                    </property>
-                    <property name="text">
-                     <string/>
-                    </property>
-                    <property name="icon">
-                     <iconset resource="ressources.qrc">
-                      <normaloff>:/images/icons/ic_arrow_back_white_24dp.png</normaloff>:/images/icons/ic_arrow_back_white_24dp.png</iconset>
-                    </property>
-                    <property name="iconSize">
-                     <size>
-                      <width>18</width>
-                      <height>18</height>
-                     </size>
-                    </property>
-                   </widget>
-                  </item>
-                  <item>
-                   <spacer name="horizontalSpacer_13">
-                    <property name="orientation">
-                     <enum>Qt::Horizontal</enum>
-                    </property>
-                    <property name="sizeHint" stdset="0">
-                     <size>
-                      <width>40</width>
-                      <height>20</height>
-                     </size>
-                    </property>
-                   </spacer>
-                  </item>
-                 </layout>
-                </item>
-                <item>
-                 <widget class="ContactRequestWidget" name="contactRequestWidget" native="true">
-                  <property name="sizePolicy">
-                   <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
-                    <horstretch>0</horstretch>
-                    <verstretch>0</verstretch>
-                   </sizepolicy>
-                  </property>
                  </widget>
                 </item>
                </layout>
@@ -1993,10 +1872,9 @@
  </widget>
  <customwidgets>
   <customwidget>
-   <class>InstantMessagingWidget</class>
+   <class>QWebEngineView</class>
    <extends>QWidget</extends>
-   <header>instantmessagingwidget.h</header>
-   <container>1</container>
+   <header location="global">QtWebEngineWidgets/QWebEngineView</header>
   </customwidget>
   <customwidget>
    <class>VideoView</class>
@@ -2020,18 +1898,6 @@
    <header>smartlistview.h</header>
   </customwidget>
   <customwidget>
-   <class>SendContactRequestWidget</class>
-   <extends>QWidget</extends>
-   <header>sendcontactrequestwidget.h</header>
-   <container>1</container>
-  </customwidget>
-  <customwidget>
-   <class>ContactRequestWidget</class>
-   <extends>QWidget</extends>
-   <header>contactrequestwidget.h</header>
-   <container>1</container>
-  </customwidget>
-  <customwidget>
    <class>ConversationsFilterWidget</class>
    <extends>QWidget</extends>
    <header>conversationsfilterwidget.h</header>
@@ -2043,9 +1909,9 @@
    <header>currentaccountcombobox.h</header>
   </customwidget>
   <customwidget>
-   <class>ConversationFilterButton</class>
-   <extends>QWidget</extends>
-   <header>conversationfilterbutton.h</header>
+   <class>MessageWebView</class>
+   <extends>QWebEngineView</extends>
+   <header>messagewebview.h</header>
   </customwidget>
  </customwidgets>
  <resources>
diff --git a/conversationfilterbutton.cpp b/conversationfilterbutton.cpp
deleted file mode 100644
index 7bba253..0000000
--- a/conversationfilterbutton.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/***************************************************************************

- * Copyright (C) 2018 by Savoir-faire Linux                                *

- * Author: Isa Nanic <isa.nanic@savoirfairelinux.com>                      *

- *                                                                         *

- * This program is free software; you can redistribute it and/or modify    *

- * it under the terms of the GNU General Public License as published by    *

- * the Free Software Foundation; either version 3 of the License, or       *

- * (at your option) any later version.                                     *

- *                                                                         *

- * This program is distributed in the hope that it will be useful,         *

- * but WITHOUT ANY WARRANTY; without even the implied warranty of          *

- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *

- * GNU General Public License for more details.                            *

- *                                                                         *

- * You should have received a copy of the GNU General Public License       *

- * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *

- **************************************************************************/

-

-#include "conversationfilterbutton.h"

-#include <QPainter>

-

-ConversationFilterButton::ConversationFilterButton()

-{

-    setCheckable(false);

-    connect(this, &ConversationFilterButton::pressed, this, &ConversationFilterButton::setSelected);

-}

-

-ConversationFilterButton::ConversationFilterButton(QWidget* widget)

-{

-    Q_UNUSED(widget)

-    setCheckable(false);

-    connect(this, &ConversationFilterButton::pressed, this, &ConversationFilterButton::setSelected);

-}

-

-ConversationFilterButton::~ConversationFilterButton()

-{

-}

-

-void

-ConversationFilterButton::paintEvent(QPaintEvent * e)

-{

-    QPushButton::paintEvent(e);

-    QPainter painter(this);

-

-    if (selected_) {

-        this->setStyleSheet("background-color: rgb(250, 250, 250)");

-    } else {

-        this->setStyleSheet("background-color: rgb(237, 237, 237)");

-    }

-    painter.end();

-}

-

-void

-ConversationFilterButton::setSelected()

-{

-    selected_ = true;

-}

-

-void

-ConversationFilterButton::setUnselected()

-{

-    selected_ = false;

-    update();

-}

-

diff --git a/conversationitemdelegate.cpp b/conversationitemdelegate.cpp
index 7ddb014..9c3f1f4 100644
--- a/conversationitemdelegate.cpp
+++ b/conversationitemdelegate.cpp
@@ -27,11 +27,12 @@
 #include "smartlistmodel.h"
 #include "ringthemeutils.h"
 #include "utils.h"
+#include "lrcinstance.h"
 
 #include <ciso646>
 
-ConversationItemDelegate::ConversationItemDelegate(QObject* parent) :
-    QItemDelegate(parent)
+ConversationItemDelegate::ConversationItemDelegate(QObject* parent)
+    : QItemDelegate(parent)
 {
 }
 
@@ -42,7 +43,7 @@
                         ) const
 {
     QStyleOptionViewItem opt(option);
-    painter->setRenderHint(QPainter::Antialiasing);
+    painter->setRenderHint(QPainter::Antialiasing, true);
 
     // Not having focus removes dotted lines around the item
     if (opt.state & QStyle::State_HasFocus)
@@ -139,8 +140,10 @@
 
 QSize
 ConversationItemDelegate::sizeHint(const QStyleOptionViewItem& option,
-                            const QModelIndex& index) const
+                                   const QModelIndex& index) const
 {
+    Q_UNUSED(option);
+    Q_UNUSED(index);
     return QSize(0, cellHeight_);
 }
 
@@ -150,6 +153,7 @@
                                                     const QRect& rect,
                                                     const QModelIndex& index) const
 {
+    Q_UNUSED(option);
     QFont font(painter->font());
     font.setPointSize(fontSize_);
     QPen pen(painter->pen());
@@ -221,18 +225,37 @@
     // bottom-right: last interaction snippet
     QString interactionStr = index.data(static_cast<int>(SmartListModel::Role::LastInteraction)).value<QString>();
     if (!interactionStr.isNull()) {
-        // remove phone glyphs
-        interactionStr.replace(QChar(0xd83d), "");
-        interactionStr.replace(QChar(0xdd7d), "");
-        interactionStr.replace(QChar(0xdcde), "");
-
-        font.setItalic(false);
-        font.setBold(false);
-        pen.setColor(RingTheme::grey_);
-        painter->setPen(pen);
-        painter->setFont(font);
+        painter->save();
+        interactionStr = interactionStr.simplified();
+        auto type = Utils::toEnum<lrc::api::interaction::Type>(index
+            .data(static_cast<int>(SmartListModel::Role::LastInteractionType))
+            .value<int>());
+        if (type == lrc::api::interaction::Type::CALL ||
+            type == lrc::api::interaction::Type::CONTACT) {
+            font.setItalic(false);
+            font.setBold(false);
+            pen.setColor(RingTheme::grey_.darker(140));
+            painter->setPen(pen);
+            painter->setFont(font);
+            // strip emojis if it's a call/contact type message
+            VectorUInt emojiless;
+            for (auto unicode : interactionStr.toUcs4()) {
+                if (!(unicode >= 0x1F000 && unicode <= 0x1FFFF)) {
+                    emojiless.push_back(unicode);
+                }
+            }
+            interactionStr = QString::fromUcs4(&emojiless.at(0), emojiless.size());
+        } else {
+            QFont emojiMsgFont(QStringLiteral("Segoe UI Emoji"));
+            emojiMsgFont.setItalic(false);
+            emojiMsgFont.setBold(false);
+            emojiMsgFont.setPointSize(fontSize_);
+            painter->setOpacity(0.7);
+            painter->setFont(emojiMsgFont);
+        }
         interactionStr = fontMetrics.elidedText(interactionStr, Qt::ElideRight, rectInfo2.width());
         painter->drawText(rectInfo2, Qt::AlignVCenter | Qt::AlignRight, interactionStr);
+        painter->restore();
     }
 }
 
@@ -268,7 +291,7 @@
     QFontMetrics fontMetrics(font);
 
     // The name is displayed at the avatar's right
-    QString nameStr = index.data(static_cast<int>(SmartListModel::Role::DisplayName)).value<QString>();;
+    QString nameStr = index.data(static_cast<int>(SmartListModel::Role::DisplayName)).value<QString>();
     if (!nameStr.isNull()) {
         font.setItalic(false);
         font.setBold(true);
@@ -293,6 +316,11 @@
 }
 
 void
-ConversationItemDelegate::paintSIPConversationItem(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
+ConversationItemDelegate::paintSIPConversationItem(QPainter* painter,
+                                                   const QStyleOptionViewItem& option,
+                                                   const QModelIndex& index) const
 {
+    Q_UNUSED(painter);
+    Q_UNUSED(option);
+    Q_UNUSED(index);
 }
diff --git a/conversationsfilterwidget.cpp b/conversationsfilterwidget.cpp
index 07abdf5..5a34e53 100644
--- a/conversationsfilterwidget.cpp
+++ b/conversationsfilterwidget.cpp
@@ -18,9 +18,32 @@
 
 #include "conversationsfilterwidget.h"
 
-#include "ringthemeutils.h"
-
 #include <QPainter>
+#include <QDebug>
+
+#include "ringthemeutils.h"
+#include "lrcinstance.h"
+
+ConversationsFilterWidget::ConversationsFilterWidget(QWidget *parent)
+    : QWidget(parent)
+{
+}
+
+void ConversationsFilterWidget::resizeEvent(QResizeEvent * event)
+{
+    using namespace lrc::api::profile;
+    updateNotifier(Type::RING);
+    updateNotifier(Type::PENDING);
+}
+
+void
+ConversationsFilterWidget::updateNotifier(lrc::api::profile::Type typeFilter)
+{
+    using namespace lrc::api::profile;
+    handleNotifierOverlay((typeFilter == Type::RING) ? "btnConversations" : "btnInvites",
+                          (typeFilter == Type::RING) ? unreadMessagesNotifier_ : pendingInvitesNotifier_,
+                          typeFilter);
+}
 
 static inline const QRect
 getNotifierRect(const QRect& buttonRect)
@@ -32,8 +55,8 @@
 
 void
 ConversationsFilterWidget::handleNotifierOverlay(const QString& buttonName,
-                                                 SmartlistSelectorButtonNotifier*& notifier,
-                                                 lrc::api::profile::Type filter)
+    SmartlistSelectorButtonNotifier*& notifier,
+    lrc::api::profile::Type filter)
 {
     auto button = this->findChild<QPushButton*>(buttonName);
     if (!button) {
@@ -49,18 +72,4 @@
         notifier->setGeometry(getNotifierRect(button->frameGeometry()));
         notifier->show();
     }
-}
-
-ConversationsFilterWidget::ConversationsFilterWidget(QWidget *parent)
-    : QWidget(parent)
-{
-}
-
-void ConversationsFilterWidget::paintEvent(QPaintEvent * event)
-{
-    QWidget::paintEvent(event);
-
-    using namespace lrc::api::profile;
-    handleNotifierOverlay("buttonConversations", unreadMessagesNotifier_, Type::RING);
-    handleNotifierOverlay("buttonInvites", pendingInvitesNotifier_, Type::PENDING);
-}
+}
\ No newline at end of file
diff --git a/conversationsfilterwidget.h b/conversationsfilterwidget.h
index 1ce9eef..35a788f 100644
--- a/conversationsfilterwidget.h
+++ b/conversationsfilterwidget.h
@@ -22,20 +22,21 @@
 
 #include <QWidget>
 
-class ConversationsFilterWidget : public QWidget

-{

-    Q_OBJECT

-

-public:

-    explicit ConversationsFilterWidget(QWidget *parent = 0);

-

-protected:

-    virtual void paintEvent(QPaintEvent *event);

-

-private:

+class ConversationsFilterWidget : public QWidget
+{
+    Q_OBJECT
+
+public:
+    explicit ConversationsFilterWidget(QWidget *parent = 0);
+
+protected:
+    void resizeEvent(QResizeEvent * event);
+
+private:
+    void updateNotifier(lrc::api::profile::Type typeFilter);
     void handleNotifierOverlay(const QString& buttonName,
                                SmartlistSelectorButtonNotifier*& notifier,
-                               lrc::api::profile::Type filter);

-    SmartlistSelectorButtonNotifier* unreadMessagesNotifier_{ nullptr };

-    SmartlistSelectorButtonNotifier* pendingInvitesNotifier_{ nullptr };

+                               lrc::api::profile::Type filter);
+    SmartlistSelectorButtonNotifier* unreadMessagesNotifier_{ nullptr };
+    SmartlistSelectorButtonNotifier* pendingInvitesNotifier_{ nullptr };
 };
\ No newline at end of file
diff --git a/currentaccountcombobox.cpp b/currentaccountcombobox.cpp
index e17323b..59f0b67 100644
--- a/currentaccountcombobox.cpp
+++ b/currentaccountcombobox.cpp
@@ -60,8 +60,10 @@
             });
 
     gearPixmap_.load(":/images/icons/round-settings-24px.svg");
+    gearLabel_.setPixmap(gearPixmap_);
     gearLabel_.setParent(this);
     gearLabel_.setStyleSheet("background: transparent;");
+    setupSettingsButton();
 }
 
 CurrentAccountComboBox::~CurrentAccountComboBox()
@@ -74,19 +76,13 @@
 {
     Q_UNUSED(e);
 
-    gearPoint_.setX(this->width() - gearSize_ - 4 * gearBorder_);
-    gearPoint_.setY(this->height() / 2 - gearLabel_.height() / 2 - 2 * gearBorder_);
-    gearLabel_.setGeometry(gearPoint_.x() - 3, gearPoint_.y(),
-                           gearSize_ + 2 * gearBorder_, gearSize_ + 2 * gearBorder_);
-    gearLabel_.setMargin(gearBorder_);
-
     QPoint p(12, 2);
     QPainter painter(this);
     painter.setRenderHints((QPainter::Antialiasing | QPainter::TextAntialiasing), true);
 
     QStyleOption opt;
     opt.init(this);
-    style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
+    style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter);
 
     // create box in which to draw avatar and presence indicator
     QRect avatarRect(2, 2, cellHeight_, cellHeight_); // [screen awareness]
@@ -136,10 +132,21 @@
         painter.setPen(Qt::lightGray);
         painter.drawText(comboBoxRect, (Qt::AlignBottom | Qt::AlignLeft), secondaryAccountID);
     }
+}
 
-    this->setEnabled(LRCInstance::accountModel().getAccountList().size() > 1);
+void CurrentAccountComboBox::resizeEvent(QResizeEvent * event)
+{
+    setupSettingsButton();
+}
 
-    gearLabel_.setPixmap(gearPixmap_);
+void
+CurrentAccountComboBox::setupSettingsButton()
+{
+    gearPoint_.setX(this->width() - gearSize_ - 4 * gearBorder_);
+    gearPoint_.setY(this->height() / 2 - gearLabel_.height() / 2 - 2 * gearBorder_);
+    gearLabel_.setGeometry(gearPoint_.x() - 3, gearPoint_.y(),
+        gearSize_ + 2 * gearBorder_, gearSize_ + 2 * gearBorder_);
+    gearLabel_.setMargin(gearBorder_);
 }
 
 // import account background account pixmap and scale pixmap to fit in label
@@ -191,7 +198,7 @@
 void
 CurrentAccountComboBox::showPopup()
 {
-    gearPixmap_.load("");
+    gearLabel_.hide();
     popupPresent = true;
     QComboBox::showPopup();
 }
@@ -199,7 +206,7 @@
 void
 CurrentAccountComboBox::hidePopup()
 {
-    gearPixmap_.load(":/images/icons/round-settings-24px.svg");
+    gearLabel_.show();
     popupPresent = false;
     QComboBox::hidePopup();
 
diff --git a/currentaccountcombobox.h b/currentaccountcombobox.h
index df0367a..586d911 100644
--- a/currentaccountcombobox.h
+++ b/currentaccountcombobox.h
@@ -34,20 +34,22 @@
 public:
     explicit CurrentAccountComboBox(QWidget* parent = nullptr);
     ~CurrentAccountComboBox();
-    void accountListUpdate();

-    void setCurrentIndex(int index);

+    void accountListUpdate();
+    void setCurrentIndex(int index);
 
-signals:

-    void settingsButtonClicked();

+signals:
+    void settingsButtonClicked();
 
-private:
+protected:
     void paintEvent(QPaintEvent* e);
-    void importLabelPhoto(int index);

+    void resizeEvent(QResizeEvent *event);
     void mousePressEvent(QMouseEvent* mouseEvent);
-
-    void mouseMoveEvent(QMouseEvent* event);

+    void mouseMoveEvent(QMouseEvent* event);
     void leaveEvent(QEvent * event);
 
+private:
+    void importLabelPhoto(int index);
+    void setupSettingsButton();
     void showPopup();
     void hidePopup();
 
diff --git a/fetch-deps.bat b/fetch-deps.bat
new file mode 100644
index 0000000..052af8b
--- /dev/null
+++ b/fetch-deps.bat
@@ -0,0 +1,27 @@
+@echo off

+setlocal EnableDelayedExpansion

+

+set cloneSubmodules=N

+if "%1" == "/c" (

+	set cloneSubmodules=Y

+)

+

+if not exist "winsparkle" (

+	git clone --depth=1 https://github.com/vslavik/winsparkle.git

+)

+

+if "%cloneSubmodules%" neq "N" (

+    cd winsparkle

+	git submodule init

+	git submodule update

+	cd ..

+)

+

+if not exist "qrencode-win32" (

+	git clone --depth=1 https://github.com/BlueDragon747/qrencode-win32.git

+)

+

+:cleanup

+endlocal

+@endlocal

+exit /B %ERRORLEVEL%
\ No newline at end of file
diff --git a/fetch-qrencodewin32.bat b/fetch-qrencodewin32.bat
deleted file mode 100644
index fb5f5eb..0000000
--- a/fetch-qrencodewin32.bat
+++ /dev/null
@@ -1,45 +0,0 @@
-@echo off

-setlocal EnableDelayedExpansion

-

-set SRC=%~dp0

-

-set MSBUILD_ARGS=/nologo /p:useenv=true /p:Configuration=Release-Lib /p:Platform=x64 /verbosity:normal /maxcpucount:%NUMBER_OF_PROCESSORS%

-

-@setlocal

-

-set VSInstallerFolder="%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"

-if %PROCESSOR_ARCHITECTURE%==x86 set VSInstallerFolder="%ProgramFiles%\Microsoft Visual Studio\Installer"

-

-pushd %VSInstallerFolder%

-for /f "usebackq tokens=*" %%i in (`vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do (

-  set VSLATESTDIR=%%i

-)

-popd

-

-echo VS Installation folder: %VSLATESTDIR%

-

-if not exist "%VSLATESTDIR%\VC\Auxiliary\Build\vcvarsall.bat" (

-    echo:

-    echo VSInstallDir not found or not installed correctly.

-    goto cleanup

-)

-

-if %PROCESSOR_ARCHITECTURE%==x86 (

-    set Comp_x86=x86 10.0.15063.0

-    set Comp_x64=x86_amd64 10.0.15063.0

-) else (

-    set Comp_x86=amd64_x86 10.0.15063.0

-    set Comp_x64=amd64 10.0.15063.0

-)

-

-set path=%path:"=%

-call "%VSLATESTDIR%"\\VC\\Auxiliary\\Build\\vcvarsall.bat %Comp_x64%

-

-git clone --depth=1 https://github.com/BlueDragon747/qrencode-win32.git

-

-msbuild qrencode-win32/qrencode-win32/vc8/qrcodelib/qrcodelib.vcxproj %MSBUILD_ARGS%

-

-:cleanup

-endlocal

-@endlocal

-exit /B %ERRORLEVEL%
\ No newline at end of file
diff --git a/fetch-winsparkle.bat b/fetch-winsparkle.bat
deleted file mode 100644
index e50cc6d..0000000
--- a/fetch-winsparkle.bat
+++ /dev/null
@@ -1,45 +0,0 @@
-@echo off

-setlocal EnableDelayedExpansion

-

-set SRC=%~dp0

-

-set MSBUILD_ARGS=/nologo /p:useenv=true /p:Configuration=Release /p:Platform=x64 /verbosity:normal /maxcpucount:%NUMBER_OF_PROCESSORS%

-

-@setlocal

-

-set VSInstallerFolder="%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"

-if %PROCESSOR_ARCHITECTURE%==x86 set VSInstallerFolder="%ProgramFiles%\Microsoft Visual Studio\Installer"

-

-pushd %VSInstallerFolder%

-for /f "usebackq tokens=*" %%i in (`vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do (

-  set VSLATESTDIR=%%i

-)

-popd

-

-echo VS Installation folder: %VSLATESTDIR%

-

-if not exist "%VSLATESTDIR%\VC\Auxiliary\Build\vcvarsall.bat" (

-    echo:

-    echo VSInstallDir not found or not installed correctly.

-    goto cleanup

-)

-

-if %PROCESSOR_ARCHITECTURE%==x86 (

-    set Comp_x86=x86 10.0.15063.0

-    set Comp_x64=x86_amd64 10.0.15063.0

-) else (

-    set Comp_x86=amd64_x86 10.0.15063.0

-    set Comp_x64=amd64 10.0.15063.0

-)

-

-set path=%path:"=%

-call "%VSLATESTDIR%"\\VC\\Auxiliary\\Build\\vcvarsall.bat %Comp_x64%

-

-git clone --depth=1 https://github.com/vslavik/winsparkle.git

-

-msbuild winsparkle/WinSparkle-2015.vcxproj %MSBUILD_ARGS%

-

-:cleanup

-endlocal

-@endlocal

-exit /B %ERRORLEVEL%
\ No newline at end of file
diff --git a/imdelegate.cpp b/imdelegate.cpp
deleted file mode 100644
index 83ca35b..0000000
--- a/imdelegate.cpp
+++ /dev/null
@@ -1,191 +0,0 @@
-/***************************************************************************
- * Copyright (C) 2015-2018 by Savoir-faire Linux                           *
- * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
- * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>          *
- *                                                                         *
- * This program is free software; you can redistribute it and/or modify    *
- * it under the terms of the GNU General Public License as published by    *
- * the Free Software Foundation; either version 3 of the License, or       *
- * (at your option) any later version.                                     *
- *                                                                         *
- * This program is distributed in the hope that it will be useful,         *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
- * GNU General Public License for more details.                            *
- *                                                                         *
- * You should have received a copy of the GNU General Public License       *
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
- **************************************************************************/
-
-#include "imdelegate.h"
-
-#include <QApplication>
-#include <QSettings>
-#include <QDateTime>
-
-#include "media/text.h"
-#include "media/textrecording.h"
-
-#include "ringthemeutils.h"
-#include "settingskey.h"
-#include "messagemodel.h"
-#include "utils.h"
-
-ImDelegate::ImDelegate(QObject *parent)
-    : QItemDelegate(parent)
-{
-}
-
-void
-ImDelegate::formatMsg(const QModelIndex& index, QString& msgString) const
-{
-    auto date = index.data(static_cast<int>(MessageModel::Role::InteractionDate)).value<QDateTime>();
-    auto now = QDateTime::currentDateTime();
-    QString dateString;
-    if (now.date() == date.date()) {
-        dateString = date.time().toString();
-    } else {
-        dateString = date.toString();
-    }
-    msgString = QString("%1<br><footer><i>%2</i></footer>").arg(msgString, dateString);
-}
-
-void
-ImDelegate::paint(QPainter* painter,
-                  const QStyleOptionViewItem& option,
-                  const QModelIndex& index) const
-{
-    if (!index.isValid()) {
-        return;
-    }
-
-    auto msg = index.data(static_cast<int>(MessageModel::Role::Body)).toString();
-    auto type = static_cast<lrc::api::interaction::Type>(index.data(static_cast<int>(MessageModel::Role::Type)).value<int>());
-    auto isOutgoing = index.data(static_cast<int>(MessageModel::Role::Direction)).value<bool>();
-    auto isGenerated = Utils::isInteractionGenerated(type);
-    auto dir = isGenerated ? Qt::AlignHCenter : (isOutgoing ? Qt::AlignRight : Qt::AlignLeft);
-
-    QStyleOptionViewItem opt = option;
-
-    painter->setRenderHint(QPainter::Antialiasing);
-
-    opt.font = fontMsg_;
-    painter->setFont(fontMsg_);
-
-    opt.text.clear();
-
-    formatMsg(index, msg);
-
-    QTextDocument document;
-    document.setDefaultStyleSheet(defaultStylesheet_);
-    document.setDefaultFont(fontMsg_);
-    document.setHtml(msg);
-    auto textOptions = QTextOption(Qt::AlignLeft);
-    textOptions.setWrapMode(QTextOption::WrapMode::WordWrap);
-    document.setDefaultTextOption(textOptions);
-
-    QRect textRect = getBoundingRect(dir, opt, document);
-    document.setTextWidth(textRect.width());
-
-    if (dir == Qt::AlignLeft) {
-        // avatar
-        opt.decorationSize = QSize(sizeImage_, sizeImage_);
-        opt.decorationPosition = QStyleOptionViewItem::Left;
-        opt.decorationAlignment = Qt::AlignCenter;
-        QRect rectAvatar(margin_ + opt.rect.left(),
-                         margin_ + opt.rect.top(),
-                         sizeImage_, sizeImage_);
-        drawDecoration(painter, opt, rectAvatar,
-                       QPixmap::fromImage(index.data(Qt::DecorationRole).value<QImage>())
-                       .scaled(sizeImage_, sizeImage_, Qt::KeepAspectRatio, Qt::SmoothTransformation));
-    } else {
-        opt.decorationSize = QSize();
-        opt.decorationPosition = QStyleOptionViewItem::Right;
-    }
-
-    // message bubble
-    QPainterPath path;
-    path.addRoundedRect(textRect, bubbleRadius_, bubbleRadius_);
-
-    if (dir == Qt::AlignRight) {
-        painter->fillPath(path, RingTheme::imGrey_);
-    } else if (dir == Qt::AlignHCenter) {
-        painter->fillPath(path, Qt::transparent);
-    } else {
-        painter->fillPath(path, RingTheme::imBlue_);
-    }
-
-    painter->save();
-
-    // text
-    painter->translate(textRect.topLeft());
-    document.drawContents(painter);
-    painter->restore();
-}
-
-QRect ImDelegate::getBoundingRect(const Qt::AlignmentFlag& dir,
-                                  const QStyleOptionViewItem &option,
-                                  QTextDocument& txtDoc) const
-{
-    QRect textRect;
-
-    if (dir == Qt::AlignLeft) {
-        txtDoc.setTextWidth(option.rect.width() - sizeImage_ - padding_);
-        textRect.setRect(option.rect.left() + sizeImage_ + padding_,
-                         option.rect.top() + padding_,
-                         txtDoc.idealWidth(),
-                         txtDoc.size().height());
-    } else if (dir == Qt::AlignHCenter) {
-        txtDoc.setTextWidth(option.rect.width() - padding_);
-        auto optCenter = option.rect.left() + option.rect.width() / 2;
-        textRect.setRect(optCenter - txtDoc.idealWidth() / 2,
-                         option.rect.top() + padding_,
-                         txtDoc.idealWidth(),
-                         txtDoc.size().height());
-    } else {
-        txtDoc.setTextWidth(option.rect.width() - padding_);
-        textRect.setRect(option.rect.right() - padding_ - txtDoc.idealWidth(),
-                         option.rect.top() + padding_,
-                         txtDoc.idealWidth(),
-                         txtDoc.size().height());
-    }
-    return textRect;
-}
-
-QSize
-ImDelegate::sizeHint(const QStyleOptionViewItem& option,
-                     const QModelIndex& index) const
-{
-    QStyleOptionViewItem opt = option;
-    opt.font = fontMsg_;
-
-    QString msg = index.data(static_cast<int>(media::TextRecording::Role::FormattedHtml)).toString();
-
-    auto isOutgoing = index.data(static_cast<int>(MessageModel::Role::Direction)).value<bool>();
-    auto isGenerated = Utils::isInteractionGenerated(
-        static_cast<lrc::api::interaction::Type>(index.data(static_cast<int>(MessageModel::Role::Type)).value<int>())
-    );
-    auto dir = isGenerated ? Qt::AlignHCenter : (isOutgoing ? Qt::AlignRight : Qt::AlignLeft);
-
-    formatMsg(index, msg);
-
-    QTextDocument document;
-    document.setDefaultFont(fontMsg_);
-    document.setHtml(msg);
-    auto textOptions = QTextOption(Qt::AlignLeft);
-    textOptions.setWrapMode(QTextOption::WrapMode::WordWrap);
-    document.setDefaultTextOption(textOptions);
-
-    QRect boundingRect = getBoundingRect(dir, opt, document);
-
-    QSize size(boundingRect.width() + 2 * margin_, boundingRect.height());
-
-    /* Keep the minimum height needed. */
-    if(size.height() < sizeImage_)
-        size.setHeight(sizeImage_);
-
-    size.setHeight(size.height() + 2 * margin_);
-
-    return size;
-}
-
diff --git a/imdelegate.h b/imdelegate.h
deleted file mode 100644
index b05eb4e..0000000
--- a/imdelegate.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/***************************************************************************
- * Copyright (C) 2015-2017 by Savoir-faire Linux                           *
- * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
- *                                                                         *
- * This program is free software; you can redistribute it and/or modify    *
- * it under the terms of the GNU General Public License as published by    *
- * the Free Software Foundation; either version 3 of the License, or       *
- * (at your option) any later version.                                     *
- *                                                                         *
- * This program is distributed in the hope that it will be useful,         *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
- * GNU General Public License for more details.                            *
- *                                                                         *
- * You should have received a copy of the GNU General Public License       *
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
- **************************************************************************/
-
-#pragma once
-
-#include <QPainter>
-#include <QTextDocument>
-#include <QItemDelegate>
-
-class ImDelegate : public QItemDelegate
-{
-    Q_OBJECT
-public:
-    explicit ImDelegate(QObject *parent = 0);
-
-protected:
-    void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
-    QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
-private:
-    void formatMsg(const QModelIndex& index, QString& msg) const;
-    QRect getBoundingRect(const Qt::AlignmentFlag& dir, const QStyleOptionViewItem &option,
-        QTextDocument& txtDoc) const;
-
-    const QFont fontMsg_ = QFont("Arial", 10);
-    const QString defaultStylesheet_ = QString("body { color : black; } i { opacity: 100; font-size : 10px; text-align : right; }");
-    constexpr static int sizeImage_ = 38;
-    constexpr static int margin_ = 5;
-    constexpr static int padding_ = 5;
-    constexpr static int bubbleRadius_ = 12;
-};
-
diff --git a/instantmessagingwidget.cpp b/instantmessagingwidget.cpp
deleted file mode 100644
index add65b8..0000000
--- a/instantmessagingwidget.cpp
+++ /dev/null
@@ -1,154 +0,0 @@
-/***************************************************************************
- * Copyright (C) 2015-2018 by Savoir-faire Linux                           *
- * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
- * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>          *
- *                                                                         *
- * This program is free software; you can redistribute it and/or modify    *
- * it under the terms of the GNU General Public License as published by    *
- * the Free Software Foundation; either version 3 of the License, or       *
- * (at your option) any later version.                                     *
- *                                                                         *
- * This program is distributed in the hope that it will be useful,         *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
- * GNU General Public License for more details.                            *
- *                                                                         *
- * You should have received a copy of the GNU General Public License       *
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
- **************************************************************************/
-
-#include "instantmessagingwidget.h"
-#include "ui_instantmessagingwidget.h"
-
-#include <QApplication>
-#include <QClipboard>
-#include <QMenu>
-
-#include "media/text.h"
-#include "media/textrecording.h"
-
-#include "imdelegate.h"
-#include "globalsystemtray.h"
-#include "settingskey.h"
-#include "lrcinstance.h"
-#include "utils.h"
-
-InstantMessagingWidget::InstantMessagingWidget(QWidget *parent) :
-    QWidget(parent),
-    ui(new Ui::InstantMessagingWidget)
-{
-    ui->setupUi(this);
-
-    this->hide();
-
-    imDelegate_ = new ImDelegate();
-    ui->listMessageView->setItemDelegate(imDelegate_);
-    ui->listMessageView->setContextMenuPolicy(Qt::ActionsContextMenu);
-    auto copyAction = new QAction(tr("Copy"), this);
-    ui->listMessageView->addAction(copyAction);
-    connect(copyAction, &QAction::triggered, [=]() {
-        copyToClipboard();
-    });
-}
-
-InstantMessagingWidget::~InstantMessagingWidget()
-{
-    delete ui;
-    delete imDelegate_;
-}
-
-void
-InstantMessagingWidget::setupCallMessaging(const std::string& callId,
-                                           MessageModel *messageModel)
-{
-    ui->listMessageView->disconnect();
-    ui->messageEdit->disconnect();
-    if (messageModel == nullptr) {
-        return;
-    }
-
-    using namespace lrc::api;
-
-    ui->listMessageView->setModel(messageModel);
-    ui->listMessageView->scrollToBottom();
-    connect(ui->messageEdit, &QLineEdit::returnPressed,
-        [=]() {
-            auto msg = ui->messageEdit->text();
-            if (msg.trimmed().isEmpty()) {
-                return;
-            }
-            ui->messageEdit->clear();
-            try {
-                auto selectedConvUid = LRCInstance::getSelectedConvUid();
-                LRCInstance::getCurrentConversationModel()->sendMessage(selectedConvUid, msg.toStdString());
-            }
-            catch (...) {
-                qDebug() << "exception when sending message";
-            }
-            ui->listMessageView->scrollToBottom();
-        });
-
-    QObject::disconnect(newInteractionConnection_);
-    newInteractionConnection_ = QObject::connect(LRCInstance::getCurrentConversationModel(), &ConversationModel::newInteraction,
-        [this](const std::string& convUid, uint64_t interactionId, const lrc::api::interaction::Info& interaction) {
-            onIncomingMessage(convUid, interactionId, interaction);
-        });
-}
-
-void
-InstantMessagingWidget::keyPressEvent(QKeyEvent *event)
-{
-    if (event->matches(QKeySequence::Copy)) {
-        copyToClipboard();
-    }
-}
-
-void
-InstantMessagingWidget::showEvent(QShowEvent *event)
-{
-    Q_UNUSED(event)
-    ui->messageEdit->setFocus();
-}
-
-void
-InstantMessagingWidget::copyToClipboard()
-{
-    auto idx = ui->listMessageView->currentIndex();
-    if (idx.isValid()) {
-        auto text = ui->listMessageView->model()->data(idx);
-
-        QApplication::clipboard()->setText(text.value<QString>());
-    }
-}

-

-void

-InstantMessagingWidget::updateConversationView(const std::string & convUid)

-{

-    auto& currentAccountInfo = LRCInstance::getCurrentAccountInfo();
-    auto currentConversationModel = currentAccountInfo.conversationModel.get();
-    currentConversationModel->clearUnreadInteractions(convUid);
-    auto currentConversation = Utils::getConversationFromUid(convUid, *currentConversationModel);
-    if (currentConversation == currentConversationModel->allFilteredConversations().end()) {
-        return;
-    }
-    messageModel_.reset(new MessageModel(*currentConversation, currentAccountInfo, this->parent()));
-    ui->listMessageView->setModel(messageModel_.get());
-    ui->listMessageView->scrollToBottom();
-    this->show();

-}
-
-void
-InstantMessagingWidget::on_sendButton_clicked()
-{
-    emit ui->messageEdit->returnPressed();
-}
-
-void
-InstantMessagingWidget::onIncomingMessage(const std::string& convUid, uint64_t interactionId, const lrc::api::interaction::Info& interaction)
-{
-    if (!QApplication::activeWindow() && settings_.value(SettingsKey::enableNotifications).toBool()) {
-        GlobalSystemTray::instance().showMessage("Ring: Message Received", QString::fromStdString(interaction.body));
-        QApplication::alert(this, 5000);
-    }
-    updateConversationView(convUid);
-}
diff --git a/instantmessagingwidget.h b/instantmessagingwidget.h
deleted file mode 100644
index ed4a79a..0000000
--- a/instantmessagingwidget.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/***************************************************************************
- * Copyright (C) 2015-2017 by Savoir-faire Linux                                *
- * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
- *                                                                         *
- * This program is free software; you can redistribute it and/or modify    *
- * it under the terms of the GNU General Public License as published by    *
- * the Free Software Foundation; either version 3 of the License, or       *
- * (at your option) any later version.                                     *
- *                                                                         *
- * This program is distributed in the hope that it will be useful,         *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
- * GNU General Public License for more details.                            *
- *                                                                         *
- * You should have received a copy of the GNU General Public License       *
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
- **************************************************************************/
-
-#pragma once
-
-#include <QWidget>
-#include <QKeyEvent>
-#include <QSettings>
-
-#include "call.h"
-#include "media/media.h"
-
-#include "imdelegate.h"
-#include "messagemodel.h"
-
-namespace Ui {
-class InstantMessagingWidget;
-}
-
-class InstantMessagingWidget final : public QWidget
-{
-    Q_OBJECT
-
-public:
-    explicit InstantMessagingWidget(QWidget *parent = 0);
-    ~InstantMessagingWidget();
-    void setupCallMessaging(const std::string& callId,
-                            MessageModel *messageModel);
-
-protected:
-    virtual void keyPressEvent(QKeyEvent *event) override;
-    virtual void showEvent(QShowEvent * event) override;
-
-//UI SLOTS
-private slots:
-    void on_sendButton_clicked();
-
-private slots:
-    void onIncomingMessage(const std::string& convUid, uint64_t interactionId, const lrc::api::interaction::Info& interaction);
-
-private:
-    Ui::InstantMessagingWidget *ui;
-    ImDelegate* imDelegate_;
-    std::unique_ptr<MessageModel> messageModel_;
-    QSettings settings_;
-    QMetaObject::Connection newInteractionConnection_;
-
-    void copyToClipboard();
-    void updateConversationView(const std::string& convUid);
-
-};
-
diff --git a/instantmessagingwidget.ui b/instantmessagingwidget.ui
deleted file mode 100644
index 9dd2bec..0000000
--- a/instantmessagingwidget.ui
+++ /dev/null
@@ -1,124 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>InstantMessagingWidget</class>
- <widget class="QWidget" name="InstantMessagingWidget">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>400</width>
-    <height>300</height>
-   </rect>
-  </property>
-  <property name="sizePolicy">
-   <sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
-    <horstretch>0</horstretch>
-    <verstretch>0</verstretch>
-   </sizepolicy>
-  </property>
-  <property name="windowTitle">
-   <string/>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <property name="leftMargin">
-    <number>0</number>
-   </property>
-   <property name="topMargin">
-    <number>0</number>
-   </property>
-   <property name="rightMargin">
-    <number>0</number>
-   </property>
-   <property name="bottomMargin">
-    <number>0</number>
-   </property>
-   <item>
-    <widget class="QListView" name="listMessageView">
-     <property name="toolTip">
-      <string>Message list</string>
-     </property>
-     <property name="frameShape">
-      <enum>QFrame::NoFrame</enum>
-     </property>
-     <property name="verticalScrollMode">
-      <enum>QAbstractItemView::ScrollPerPixel</enum>
-     </property>
-     <property name="horizontalScrollMode">
-      <enum>QAbstractItemView::ScrollPerPixel</enum>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <layout class="QHBoxLayout" name="horizontalLayout">
-     <property name="leftMargin">
-      <number>6</number>
-     </property>
-     <property name="topMargin">
-      <number>6</number>
-     </property>
-     <property name="rightMargin">
-      <number>6</number>
-     </property>
-     <property name="bottomMargin">
-      <number>6</number>
-     </property>
-     <item>
-      <widget class="QLineEdit" name="messageEdit">
-       <property name="minimumSize">
-        <size>
-         <width>0</width>
-         <height>30</height>
-        </size>
-       </property>
-       <property name="toolTip">
-        <string>Message input</string>
-       </property>
-       <property name="placeholderText">
-        <string>Send text message...</string>
-       </property>
-       <property name="clearButtonEnabled">
-        <bool>true</bool>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QPushButton" name="sendButton">
-       <property name="minimumSize">
-        <size>
-         <width>30</width>
-         <height>30</height>
-        </size>
-       </property>
-       <property name="maximumSize">
-        <size>
-         <width>30</width>
-         <height>30</height>
-        </size>
-       </property>
-       <property name="toolTip">
-        <string>Send message button</string>
-       </property>
-       <property name="text">
-        <string/>
-       </property>
-       <property name="icon">
-        <iconset resource="ressources.qrc">
-         <normaloff>:/images/icons/ic_send_white_24dp.png</normaloff>:/images/icons/ic_send_white_24dp.png</iconset>
-       </property>
-       <property name="iconSize">
-        <size>
-         <width>18</width>
-         <height>18</height>
-        </size>
-       </property>
-      </widget>
-     </item>
-    </layout>
-   </item>
-  </layout>
- </widget>
- <resources>
-  <include location="ressources.qrc"/>
- </resources>
- <connections/>
-</ui>
diff --git a/invitebuttonswidget.cpp b/invitebuttonswidget.cpp
index ee18f90..cbe2504 100644
--- a/invitebuttonswidget.cpp
+++ b/invitebuttonswidget.cpp
@@ -37,11 +37,10 @@
         [=]() {
             emit btnBlockInviteClicked();
         });
-
 }
 
 InviteButtonsWidget::~InviteButtonsWidget()
 {
     disconnect(this);
     delete ui;
-}
\ No newline at end of file
+}
diff --git a/invitebuttonswidget.h b/invitebuttonswidget.h
index 7e1919b..7f53a57 100644
--- a/invitebuttonswidget.h
+++ b/invitebuttonswidget.h
@@ -19,6 +19,7 @@
 #pragma once
 
 #include <QWidget>
+#include <QItemDelegate>
 
 namespace Ui {
 class InviteButtonsWidget;
diff --git a/linkify.js b/linkify.js
new file mode 100644
index 0000000..8da2f89
--- /dev/null
+++ b/linkify.js
@@ -0,0 +1,1271 @@
+/*
+ * Copyright (c) 2016 SoapBox Innovations Inc.
+ *
+ * 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.
+*/
+
+;(function () {
+'use strict';
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
+
+(function (exports) {
+	'use strict';
+
+	function inherits(parent, child) {
+		var props = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+
+		var extended = Object.create(parent.prototype);
+		for (var p in props) {
+			extended[p] = props[p];
+		}
+		extended.constructor = child;
+		child.prototype = extended;
+		return child;
+	}
+
+	var defaults = {
+		defaultProtocol: 'http',
+		events: null,
+		format: noop,
+		formatHref: noop,
+		nl2br: false,
+		tagName: 'a',
+		target: typeToTarget,
+		validate: true,
+		ignoreTags: [],
+		attributes: null,
+		className: 'linkified' };
+
+	function Options(opts) {
+		opts = opts || {};
+
+		this.defaultProtocol = opts.defaultProtocol || defaults.defaultProtocol;
+		this.events = opts.events || defaults.events;
+		this.format = opts.format || defaults.format;
+		this.formatHref = opts.formatHref || defaults.formatHref;
+		this.nl2br = opts.nl2br || defaults.nl2br;
+		this.tagName = opts.tagName || defaults.tagName;
+		this.target = opts.target || defaults.target;
+		this.validate = opts.validate || defaults.validate;
+		this.ignoreTags = [];
+
+		// linkAttributes and linkClass is deprecated
+		this.attributes = opts.attributes || opts.linkAttributes || defaults.attributes;
+		this.className = opts.className || opts.linkClass || defaults.className;
+
+		// Make all tags names upper case
+
+		var ignoredTags = opts.ignoreTags || defaults.ignoreTags;
+		for (var i = 0; i < ignoredTags.length; i++) {
+			this.ignoreTags.push(ignoredTags[i].toUpperCase());
+		}
+	}
+
+	Options.prototype = {
+		/**
+   * Given the token, return all options for how it should be displayed
+   */
+		resolve: function resolve(token) {
+			var href = token.toHref(this.defaultProtocol);
+			return {
+				formatted: this.get('format', token.toString(), token),
+				formattedHref: this.get('formatHref', href, token),
+				tagName: this.get('tagName', href, token),
+				className: this.get('className', href, token),
+				target: this.get('target', href, token),
+				events: this.getObject('events', href, token),
+				attributes: this.getObject('attributes', href, token)
+			};
+		},
+
+
+		/**
+   * Returns true or false based on whether a token should be displayed as a
+   * link based on the user options. By default,
+   */
+		check: function check(token) {
+			return this.get('validate', token.toString(), token);
+		},
+
+
+		// Private methods
+
+		/**
+   * Resolve an option's value based on the value of the option and the given
+   * params.
+   * @param [String] key Name of option to use
+   * @param operator will be passed to the target option if it's method
+   * @param [MultiToken] token The token from linkify.tokenize
+   */
+		get: function get(key, operator, token) {
+			var option = this[key];
+
+			if (!option) {
+				return option;
+			}
+
+			switch (typeof option === 'undefined' ? 'undefined' : _typeof(option)) {
+				case 'function':
+					return option(operator, token.type);
+				case 'object':
+					var optionValue = option[token.type] || defaults[key];
+					return typeof optionValue === 'function' ? optionValue(operator, token.type) : optionValue;
+			}
+
+			return option;
+		},
+		getObject: function getObject(key, operator, token) {
+			var option = this[key];
+			return typeof option === 'function' ? option(operator, token.type) : option;
+		}
+	};
+
+	/**
+  * Quick indexOf replacement for checking the ignoreTags option
+  */
+	function contains(arr, value) {
+		for (var i = 0; i < arr.length; i++) {
+			if (arr[i] === value) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	function noop(val) {
+		return val;
+	}
+
+	function typeToTarget(href, type) {
+		return type === 'url' ? '_blank' : null;
+	}
+
+	var options = Object.freeze({
+		defaults: defaults,
+		Options: Options,
+		contains: contains
+	});
+
+	function createStateClass() {
+		return function (tClass) {
+			this.j = [];
+			this.T = tClass || null;
+		};
+	}
+
+	/**
+ 	A simple state machine that can emit token classes
+
+ 	The `j` property in this class refers to state jumps. It's a
+ 	multidimensional array where for each element:
+
+ 	* index [0] is a symbol or class of symbols to transition to.
+ 	* index [1] is a State instance which matches
+
+ 	The type of symbol will depend on the target implementation for this class.
+ 	In Linkify, we have a two-stage scanner. Each stage uses this state machine
+ 	but with a slighly different (polymorphic) implementation.
+
+ 	The `T` property refers to the token class.
+
+ 	TODO: Can the `on` and `next` methods be combined?
+
+ 	@class BaseState
+ */
+	var BaseState = createStateClass();
+	BaseState.prototype = {
+		defaultTransition: false,
+
+		/**
+  	@method constructor
+  	@param {Class} tClass Pass in the kind of token to emit if there are
+  		no jumps after this state and the state is accepting.
+  */
+
+		/**
+  	On the given symbol(s), this machine should go to the given state
+  		@method on
+  	@param {Array|Mixed} symbol
+  	@param {BaseState} state Note that the type of this state should be the
+  		same as the current instance (i.e., don't pass in a different
+  		subclass)
+  */
+		on: function on(symbol, state) {
+			if (symbol instanceof Array) {
+				for (var i = 0; i < symbol.length; i++) {
+					this.j.push([symbol[i], state]);
+				}
+				return this;
+			}
+			this.j.push([symbol, state]);
+			return this;
+		},
+
+
+		/**
+  	Given the next item, returns next state for that item
+  	@method next
+  	@param {Mixed} item Should be an instance of the symbols handled by
+  		this particular machine.
+  	@return {State} state Returns false if no jumps are available
+  */
+		next: function next(item) {
+			for (var i = 0; i < this.j.length; i++) {
+				var jump = this.j[i];
+				var symbol = jump[0]; // Next item to check for
+				var state = jump[1]; // State to jump to if items match
+
+				// compare item with symbol
+				if (this.test(item, symbol)) {
+					return state;
+				}
+			}
+
+			// Nowhere left to jump!
+			return this.defaultTransition;
+		},
+
+
+		/**
+  	Does this state accept?
+  	`true` only of `this.T` exists
+  		@method accepts
+  	@return {Boolean}
+  */
+		accepts: function accepts() {
+			return !!this.T;
+		},
+
+
+		/**
+  	Determine whether a given item "symbolizes" the symbol, where symbol is
+  	a class of items handled by this state machine.
+  		This method should be overriden in extended classes.
+  		@method test
+  	@param {Mixed} item Does this item match the given symbol?
+  	@param {Mixed} symbol
+  	@return {Boolean}
+  */
+		test: function test(item, symbol) {
+			return item === symbol;
+		},
+
+
+		/**
+  	Emit the token for this State (just return it in this case)
+  	If this emits a token, this instance is an accepting state
+  	@method emit
+  	@return {Class} T
+  */
+		emit: function emit() {
+			return this.T;
+		}
+	};
+
+	/**
+ 	State machine for string-based input
+
+ 	@class CharacterState
+ 	@extends BaseState
+ */
+	var CharacterState = inherits(BaseState, createStateClass(), {
+		/**
+  	Does the given character match the given character or regular
+  	expression?
+  		@method test
+  	@param {String} char
+  	@param {String|RegExp} charOrRegExp
+  	@return {Boolean}
+  */
+		test: function test(character, charOrRegExp) {
+			return character === charOrRegExp || charOrRegExp instanceof RegExp && charOrRegExp.test(character);
+		}
+	});
+
+	/**
+ 	State machine for input in the form of TextTokens
+
+ 	@class TokenState
+ 	@extends BaseState
+ */
+	var State = inherits(BaseState, createStateClass(), {
+
+		/**
+   * Similar to `on`, but returns the state the results in the transition from
+   * the given item
+   * @method jump
+   * @param {Mixed} item
+   * @param {Token} [token]
+   * @return state
+   */
+		jump: function jump(token) {
+			var tClass = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+			var state = this.next(new token('')); // dummy temp token
+			if (state === this.defaultTransition) {
+				// Make a new state!
+				state = new this.constructor(tClass);
+				this.on(token, state);
+			} else if (tClass) {
+				state.T = tClass;
+			}
+			return state;
+		},
+
+
+		/**
+  	Is the given token an instance of the given token class?
+  		@method test
+  	@param {TextToken} token
+  	@param {Class} tokenClass
+  	@return {Boolean}
+  */
+		test: function test(token, tokenClass) {
+			return token instanceof tokenClass;
+		}
+	});
+
+	/**
+ 	Given a non-empty target string, generates states (if required) for each
+ 	consecutive substring of characters in str starting from the beginning of
+ 	the string. The final state will have a special value, as specified in
+ 	options. All other "in between" substrings will have a default end state.
+
+ 	This turns the state machine into a Trie-like data structure (rather than a
+ 	intelligently-designed DFA).
+
+ 	Note that I haven't really tried these with any strings other than
+ 	DOMAIN.
+
+ 	@param {String} str
+ 	@param {CharacterState} start State to jump from the first character
+ 	@param {Class} endToken Token class to emit when the given string has been
+ 		matched and no more jumps exist.
+ 	@param {Class} defaultToken "Filler token", or which token type to emit when
+ 		we don't have a full match
+ 	@return {Array} list of newly-created states
+ */
+	function stateify(str, start, endToken, defaultToken) {
+		var i = 0,
+		    len = str.length,
+		    state = start,
+		    newStates = [],
+		    nextState = void 0;
+
+		// Find the next state without a jump to the next character
+		while (i < len && (nextState = state.next(str[i]))) {
+			state = nextState;
+			i++;
+		}
+
+		if (i >= len) {
+			return [];
+		} // no new tokens were added
+
+		while (i < len - 1) {
+			nextState = new CharacterState(defaultToken);
+			newStates.push(nextState);
+			state.on(str[i], nextState);
+			state = nextState;
+			i++;
+		}
+
+		nextState = new CharacterState(endToken);
+		newStates.push(nextState);
+		state.on(str[len - 1], nextState);
+
+		return newStates;
+	}
+
+	function createTokenClass() {
+		return function (value) {
+			if (value) {
+				this.v = value;
+			}
+		};
+	}
+
+	/******************************************************************************
+ 	Text Tokens
+ 	Tokens composed of strings
+ ******************************************************************************/
+
+	/**
+ 	Abstract class used for manufacturing text tokens.
+ 	Pass in the value this token represents
+
+ 	@class TextToken
+ 	@abstract
+ */
+	var TextToken = createTokenClass();
+	TextToken.prototype = {
+		toString: function toString() {
+			return this.v + '';
+		}
+	};
+
+	function inheritsToken(value) {
+		var props = value ? { v: value } : {};
+		return inherits(TextToken, createTokenClass(), props);
+	}
+
+	/**
+ 	A valid domain token
+ 	@class DOMAIN
+ 	@extends TextToken
+ */
+	var DOMAIN = inheritsToken();
+
+	/**
+ 	@class AT
+ 	@extends TextToken
+ */
+	var AT = inheritsToken('@');
+
+	/**
+ 	Represents a single colon `:` character
+
+ 	@class COLON
+ 	@extends TextToken
+ */
+	var COLON = inheritsToken(':');
+
+	/**
+ 	@class DOT
+ 	@extends TextToken
+ */
+	var DOT = inheritsToken('.');
+
+	/**
+ 	A character class that can surround the URL, but which the URL cannot begin
+ 	or end with. Does not include certain English punctuation like parentheses.
+
+ 	@class PUNCTUATION
+ 	@extends TextToken
+ */
+	var PUNCTUATION = inheritsToken();
+
+	/**
+ 	The word localhost (by itself)
+ 	@class LOCALHOST
+ 	@extends TextToken
+ */
+	var LOCALHOST = inheritsToken();
+
+	/**
+ 	Newline token
+ 	@class NL
+ 	@extends TextToken
+ */
+	var TNL = inheritsToken('\n');
+
+	/**
+ 	@class NUM
+ 	@extends TextToken
+ */
+	var NUM = inheritsToken();
+
+	/**
+ 	@class PLUS
+ 	@extends TextToken
+ */
+	var PLUS = inheritsToken('+');
+
+	/**
+ 	@class POUND
+ 	@extends TextToken
+ */
+	var POUND = inheritsToken('#');
+
+	/**
+ 	Represents a web URL protocol. Supported types include
+
+ 	* `http:`
+ 	* `https:`
+ 	* `ftp:`
+ 	* `ftps:`
+ 	* There's Another super weird one
+
+ 	@class PROTOCOL
+ 	@extends TextToken
+ */
+	var PROTOCOL = inheritsToken();
+
+	/**
+ 	@class QUERY
+ 	@extends TextToken
+ */
+	var QUERY = inheritsToken('?');
+
+	/**
+ 	@class SLASH
+ 	@extends TextToken
+ */
+	var SLASH = inheritsToken('/');
+
+	/**
+ 	@class UNDERSCORE
+ 	@extends TextToken
+ */
+	var UNDERSCORE = inheritsToken('_');
+
+	/**
+ 	One ore more non-whitespace symbol.
+ 	@class SYM
+ 	@extends TextToken
+ */
+	var SYM = inheritsToken();
+
+	/**
+ 	@class TLD
+ 	@extends TextToken
+ */
+	var TLD = inheritsToken();
+
+	/**
+ 	Represents a string of consecutive whitespace characters
+
+ 	@class WS
+ 	@extends TextToken
+ */
+	var WS = inheritsToken();
+
+	/**
+ 	Opening/closing bracket classes
+ */
+
+	var OPENBRACE = inheritsToken('{');
+	var OPENBRACKET = inheritsToken('[');
+	var OPENANGLEBRACKET = inheritsToken('<');
+	var OPENPAREN = inheritsToken('(');
+	var CLOSEBRACE = inheritsToken('}');
+	var CLOSEBRACKET = inheritsToken(']');
+	var CLOSEANGLEBRACKET = inheritsToken('>');
+	var CLOSEPAREN = inheritsToken(')');
+
+	var TOKENS = Object.freeze({
+		Base: TextToken,
+		DOMAIN: DOMAIN,
+		AT: AT,
+		COLON: COLON,
+		DOT: DOT,
+		PUNCTUATION: PUNCTUATION,
+		LOCALHOST: LOCALHOST,
+		NL: TNL,
+		NUM: NUM,
+		PLUS: PLUS,
+		POUND: POUND,
+		QUERY: QUERY,
+		PROTOCOL: PROTOCOL,
+		SLASH: SLASH,
+		UNDERSCORE: UNDERSCORE,
+		SYM: SYM,
+		TLD: TLD,
+		WS: WS,
+		OPENBRACE: OPENBRACE,
+		OPENBRACKET: OPENBRACKET,
+		OPENANGLEBRACKET: OPENANGLEBRACKET,
+		OPENPAREN: OPENPAREN,
+		CLOSEBRACE: CLOSEBRACE,
+		CLOSEBRACKET: CLOSEBRACKET,
+		CLOSEANGLEBRACKET: CLOSEANGLEBRACKET,
+		CLOSEPAREN: CLOSEPAREN
+	});
+
+	/**
+ 	The scanner provides an interface that takes a string of text as input, and
+ 	outputs an array of tokens instances that can be used for easy URL parsing.
+
+ 	@module linkify
+ 	@submodule scanner
+ 	@main scanner
+ */
+
+	var tlds = 'aaa|aarp|abb|abbott|abogado|ac|academy|accenture|accountant|accountants|aco|active|actor|ad|adac|ads|adult|ae|aeg|aero|af|afl|ag|agency|ai|aig|airforce|airtel|al|alibaba|alipay|allfinanz|alsace|am|amica|amsterdam|an|analytics|android|ao|apartments|app|apple|aq|aquarelle|ar|aramco|archi|army|arpa|arte|as|asia|associates|at|attorney|au|auction|audi|audio|author|auto|autos|avianca|aw|ax|axa|az|azure|ba|baidu|band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bb|bbc|bbva|bcg|bcn|bd|be|beats|beer|bentley|berlin|best|bet|bf|bg|bh|bharti|bi|bible|bid|bike|bing|bingo|bio|biz|bj|black|blackfriday|bloomberg|blue|bm|bms|bmw|bn|bnl|bnpparibas|bo|boats|boehringer|bom|bond|boo|book|boots|bosch|bostik|bot|boutique|br|bradesco|bridgestone|broadway|broker|brother|brussels|bs|bt|budapest|bugatti|build|builders|business|buy|buzz|bv|bw|by|bz|bzh|ca|cab|cafe|cal|call|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|cc|cd|ceb|center|ceo|cern|cf|cfa|cfd|cg|ch|chanel|channel|chase|chat|cheap|chloe|christmas|chrome|church|ci|cipriani|circle|cisco|citic|city|cityeats|ck|cl|claims|cleaning|click|clinic|clinique|clothing|cloud|club|clubmed|cm|cn|co|coach|codes|coffee|college|cologne|com|commbank|community|company|compare|computer|comsec|condos|construction|consulting|contact|contractors|cooking|cool|coop|corsica|country|coupon|coupons|courses|cr|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc|cu|cuisinella|cv|cw|cx|cy|cymru|cyou|cz|dabur|dad|dance|date|dating|datsun|day|dclk|de|dealer|deals|degree|delivery|dell|deloitte|delta|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory|discount|dj|dk|dm|dnp|do|docs|dog|doha|domains|download|drive|dubai|durban|dvag|dz|earth|eat|ec|edeka|edu|education|ee|eg|email|emerck|energy|engineer|engineering|enterprises|epson|equipment|er|erni|es|esq|estate|et|eu|eurovision|eus|events|everbank|exchange|expert|exposed|express|fage|fail|fairwinds|faith|family|fan|fans|farm|fashion|fast|feedback|ferrero|fi|film|final|finance|financial|firestone|firmdale|fish|fishing|fit|fitness|fj|fk|flickr|flights|florist|flowers|flsmidth|fly|fm|fo|foo|football|ford|forex|forsale|forum|foundation|fox|fr|fresenius|frl|frogans|frontier|fund|furniture|futbol|fyi|ga|gal|gallery|gallup|game|garden|gb|gbiz|gd|gdn|ge|gea|gent|genting|gf|gg|ggee|gh|gi|gift|gifts|gives|giving|gl|glass|gle|global|globo|gm|gmail|gmbh|gmo|gmx|gn|gold|goldpoint|golf|goo|goog|google|gop|got|gov|gp|gq|gr|grainger|graphics|gratis|green|gripe|group|gs|gt|gu|gucci|guge|guide|guitars|guru|gw|gy|hamburg|hangout|haus|hdfcbank|health|healthcare|help|helsinki|here|hermes|hiphop|hitachi|hiv|hk|hm|hn|hockey|holdings|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house|how|hr|hsbc|ht|hu|hyundai|ibm|icbc|ice|icu|id|ie|ifm|iinet|il|im|immo|immobilien|in|industries|infiniti|info|ing|ink|institute|insurance|insure|int|international|investments|io|ipiranga|iq|ir|irish|is|iselect|ist|istanbul|it|itau|iwc|jaguar|java|jcb|je|jetzt|jewelry|jlc|jll|jm|jmp|jo|jobs|joburg|jot|joy|jp|jpmorgan|jprs|juegos|kaufen|kddi|ke|kerryhotels|kerrylogistics|kerryproperties|kfh|kg|kh|ki|kia|kim|kinder|kitchen|kiwi|km|kn|koeln|komatsu|kp|kpn|kr|krd|kred|kuokgroup|kw|ky|kyoto|kz|la|lacaixa|lamborghini|lamer|lancaster|land|landrover|lanxess|lasalle|lat|latrobe|law|lawyer|lb|lc|lds|lease|leclerc|legal|lexus|lgbt|li|liaison|lidl|life|lifeinsurance|lifestyle|lighting|like|limited|limo|lincoln|linde|link|live|living|lixil|lk|loan|loans|local|locus|lol|london|lotte|lotto|love|lr|ls|lt|ltd|ltda|lu|lupin|luxe|luxury|lv|ly|ma|madrid|maif|maison|makeup|man|management|mango|market|marketing|markets|marriott|mba|mc|md|me|med|media|meet|melbourne|meme|memorial|men|menu|meo|mg|mh|miami|microsoft|mil|mini|mk|ml|mm|mma|mn|mo|mobi|mobily|moda|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov|movie|movistar|mp|mq|mr|ms|mt|mtn|mtpc|mtr|mu|museum|mutuelle|mv|mw|mx|my|mz|na|nadex|nagoya|name|natura|navy|nc|ne|nec|net|netbank|network|neustar|new|news|nexus|nf|ng|ngo|nhk|ni|nico|nikon|ninja|nissan|nl|no|nokia|norton|nowruz|np|nr|nra|nrw|ntt|nu|nyc|nz|obi|office|okinawa|om|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|origins|osaka|otsuka|ovh|pa|page|pamperedchef|panerai|paris|pars|partners|parts|party|passagens|pe|pet|pf|pg|ph|pharmacy|philips|photo|photography|photos|physio|piaget|pics|pictet|pictures|pid|pin|ping|pink|pizza|pk|pl|place|play|playstation|plumbing|plus|pm|pn|pohl|poker|porn|post|pr|praxi|press|pro|prod|productions|prof|promo|properties|property|protection|ps|pt|pub|pw|pwc|py|qa|qpon|quebec|quest|racing|re|read|realtor|realty|recipes|red|redstone|redumbrella|rehab|reise|reisen|reit|ren|rent|rentals|repair|report|republican|rest|restaurant|review|reviews|rexroth|rich|ricoh|rio|rip|ro|rocher|rocks|rodeo|room|rs|rsvp|ru|ruhr|run|rw|rwe|ryukyu|sa|saarland|safe|safety|sakura|sale|salon|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|sas|saxo|sb|sbs|sc|sca|scb|schaeffler|schmidt|scholarships|school|schule|schwarz|science|scor|scot|sd|se|seat|security|seek|select|sener|services|seven|sew|sex|sexy|sfr|sg|sh|sharp|shell|shia|shiksha|shoes|show|shriram|si|singles|site|sj|sk|ski|skin|sky|skype|sl|sm|smile|sn|sncf|so|soccer|social|softbank|software|sohu|solar|solutions|song|sony|soy|space|spiegel|spot|spreadbetting|sr|srl|st|stada|star|starhub|statefarm|statoil|stc|stcgroup|stockholm|storage|store|studio|study|style|su|sucks|supplies|supply|support|surf|surgery|suzuki|sv|swatch|swiss|sx|sy|sydney|symantec|systems|sz|tab|taipei|taobao|tatamotors|tatar|tattoo|tax|taxi|tc|tci|td|team|tech|technology|tel|telecity|telefonica|temasek|tennis|tf|tg|th|thd|theater|theatre|tickets|tienda|tiffany|tips|tires|tirol|tj|tk|tl|tm|tmall|tn|to|today|tokyo|tools|top|toray|toshiba|total|tours|town|toyota|toys|tp|tr|trade|trading|training|travel|travelers|travelersinsurance|trust|trv|tt|tube|tui|tunes|tushu|tv|tvs|tw|tz|ua|ubs|ug|uk|unicom|university|uno|uol|us|uy|uz|va|vacations|vana|vc|ve|vegas|ventures|verisign|versicherung|vet|vg|vi|viajes|video|viking|villas|vin|vip|virgin|vision|vista|vistaprint|viva|vlaanderen|vn|vodka|volkswagen|vote|voting|voto|voyage|vu|vuelos|wales|walter|wang|wanggou|watch|watches|weather|weatherchannel|webcam|weber|website|wed|wedding|weir|wf|whoswho|wien|wiki|williamhill|win|windows|wine|wme|wolterskluwer|work|works|world|ws|wtc|wtf|xbox|xerox|xin|xperia|xxx|xyz|yachts|yahoo|yamaxun|yandex|ye|yodobashi|yoga|yokohama|youtube|yt|za|zara|zero|zip|zm|zone|zuerich|zw'.split('|'); // macro, see gulpfile.js
+
+	var NUMBERS = '0123456789'.split('');
+	var ALPHANUM = '0123456789abcdefghijklmnopqrstuvwxyz'.split('');
+	var WHITESPACE = [' ', '\f', '\r', '\t', '\v', ' ', '?', '?']; // excluding line breaks
+
+	var domainStates = []; // states that jump to DOMAIN on /[a-z0-9]/
+	var makeState = function makeState(tokenClass) {
+		return new CharacterState(tokenClass);
+	};
+
+	// Frequently used states
+	var S_START = makeState();
+	var S_NUM = makeState(NUM);
+	var S_DOMAIN = makeState(DOMAIN);
+	var S_DOMAIN_HYPHEN = makeState(); // domain followed by 1 or more hyphen characters
+	var S_WS = makeState(WS);
+
+	// States for special URL symbols
+	S_START.on('@', makeState(AT)).on('.', makeState(DOT)).on('+', makeState(PLUS)).on('#', makeState(POUND)).on('?', makeState(QUERY)).on('/', makeState(SLASH)).on('_', makeState(UNDERSCORE)).on(':', makeState(COLON)).on('{', makeState(OPENBRACE)).on('[', makeState(OPENBRACKET)).on('<', makeState(OPENANGLEBRACKET)).on('(', makeState(OPENPAREN)).on('}', makeState(CLOSEBRACE)).on(']', makeState(CLOSEBRACKET)).on('>', makeState(CLOSEANGLEBRACKET)).on(')', makeState(CLOSEPAREN)).on([',', ';', '!', '"', '\''], makeState(PUNCTUATION));
+
+	// Whitespace jumps
+	// Tokens of only non-newline whitespace are arbitrarily long
+	S_START.on('\n', makeState(TNL)).on(WHITESPACE, S_WS);
+
+	// If any whitespace except newline, more whitespace!
+	S_WS.on(WHITESPACE, S_WS);
+
+	// Generates states for top-level domains
+	// Note that this is most accurate when tlds are in alphabetical order
+	for (var i = 0; i < tlds.length; i++) {
+		var newStates = stateify(tlds[i], S_START, TLD, DOMAIN);
+		domainStates.push.apply(domainStates, newStates);
+	}
+
+	// Collect the states generated by different protocls
+	var partialProtocolFileStates = stateify('file', S_START, DOMAIN, DOMAIN);
+	var partialProtocolFtpStates = stateify('ftp', S_START, DOMAIN, DOMAIN);
+	var partialProtocolHttpStates = stateify('http', S_START, DOMAIN, DOMAIN);
+
+	// Add the states to the array of DOMAINeric states
+	domainStates.push.apply(domainStates, partialProtocolFileStates);
+	domainStates.push.apply(domainStates, partialProtocolFtpStates);
+	domainStates.push.apply(domainStates, partialProtocolHttpStates);
+
+	// Protocol states
+	var S_PROTOCOL_FILE = partialProtocolFileStates.pop();
+	var S_PROTOCOL_FTP = partialProtocolFtpStates.pop();
+	var S_PROTOCOL_HTTP = partialProtocolHttpStates.pop();
+	var S_PROTOCOL_SECURE = makeState(DOMAIN);
+	var S_FULL_PROTOCOL = makeState(PROTOCOL); // Full protocol ends with COLON
+
+	// Secure protocols (end with 's')
+	S_PROTOCOL_FTP.on('s', S_PROTOCOL_SECURE).on(':', S_FULL_PROTOCOL);
+
+	S_PROTOCOL_HTTP.on('s', S_PROTOCOL_SECURE).on(':', S_FULL_PROTOCOL);
+
+	domainStates.push(S_PROTOCOL_SECURE);
+
+	// Become protocol tokens after a COLON
+	S_PROTOCOL_FILE.on(':', S_FULL_PROTOCOL);
+	S_PROTOCOL_SECURE.on(':', S_FULL_PROTOCOL);
+
+	// Localhost
+	var partialLocalhostStates = stateify('localhost', S_START, LOCALHOST, DOMAIN);
+	domainStates.push.apply(domainStates, partialLocalhostStates);
+
+	// Everything else
+	// DOMAINs make more DOMAINs
+	// Number and character transitions
+	S_START.on(NUMBERS, S_NUM);
+	S_NUM.on('-', S_DOMAIN_HYPHEN).on(NUMBERS, S_NUM).on(ALPHANUM, S_DOMAIN); // number becomes DOMAIN
+
+	S_DOMAIN.on('-', S_DOMAIN_HYPHEN).on(ALPHANUM, S_DOMAIN);
+
+	// All the generated states should have a jump to DOMAIN
+	for (var _i = 0; _i < domainStates.length; _i++) {
+		domainStates[_i].on('-', S_DOMAIN_HYPHEN).on(ALPHANUM, S_DOMAIN);
+	}
+
+	S_DOMAIN_HYPHEN.on('-', S_DOMAIN_HYPHEN).on(NUMBERS, S_DOMAIN).on(ALPHANUM, S_DOMAIN);
+
+	// Set default transition
+	S_START.defaultTransition = makeState(SYM);
+
+	/**
+ 	Given a string, returns an array of TOKEN instances representing the
+ 	composition of that string.
+
+ 	@method run
+ 	@param {String} str Input string to scan
+ 	@return {Array} Array of TOKEN instances
+ */
+	var run = function run(str) {
+
+		// The state machine only looks at lowercase strings.
+		// This selective `toLowerCase` is used because lowercasing the entire
+		// string causes the length and character position to vary in some in some
+		// non-English strings. This happens only on V8-based runtimes.
+		var lowerStr = str.replace(/[A-Z]/g, function (c) {
+			return c.toLowerCase();
+		});
+		var len = str.length;
+		var tokens = []; // return value
+
+		var cursor = 0;
+
+		// Tokenize the string
+		while (cursor < len) {
+			var state = S_START;
+			var secondState = null;
+			var nextState = null;
+			var tokenLength = 0;
+			var latestAccepting = null;
+			var sinceAccepts = -1;
+
+			while (cursor < len && (nextState = state.next(lowerStr[cursor]))) {
+				secondState = null;
+				state = nextState;
+
+				// Keep track of the latest accepting state
+				if (state.accepts()) {
+					sinceAccepts = 0;
+					latestAccepting = state;
+				} else if (sinceAccepts >= 0) {
+					sinceAccepts++;
+				}
+
+				tokenLength++;
+				cursor++;
+			}
+
+			if (sinceAccepts < 0) {
+				continue;
+			} // Should never happen
+
+			// Roll back to the latest accepting state
+			cursor -= sinceAccepts;
+			tokenLength -= sinceAccepts;
+
+			// Get the class for the new token
+			var TOKEN = latestAccepting.emit(); // Current token class
+
+			// No more jumps, just make a new token
+			tokens.push(new TOKEN(str.substr(cursor - tokenLength, tokenLength)));
+		}
+
+		return tokens;
+	};
+
+	var start = S_START;
+
+	var scanner = Object.freeze({
+		State: CharacterState,
+		TOKENS: TOKENS,
+		run: run,
+		start: start
+	});
+
+	/******************************************************************************
+ 	Multi-Tokens
+ 	Tokens composed of arrays of TextTokens
+ ******************************************************************************/
+
+	// Is the given token a valid domain token?
+	// Should nums be included here?
+	function isDomainToken(token) {
+		return token instanceof DOMAIN || token instanceof TLD;
+	}
+
+	/**
+ 	Abstract class used for manufacturing tokens of text tokens. That is rather
+ 	than the value for a token being a small string of text, it's value an array
+ 	of text tokens.
+
+ 	Used for grouping together URLs, emails, hashtags, and other potential
+ 	creations.
+
+ 	@class MultiToken
+ 	@abstract
+ */
+	var MultiToken = createTokenClass();
+
+	MultiToken.prototype = {
+		/**
+  	String representing the type for this token
+  	@property type
+  	@default 'TOKEN'
+  */
+		type: 'token',
+
+		/**
+  	Is this multitoken a link?
+  	@property isLink
+  	@default false
+  */
+		isLink: false,
+
+		/**
+  	Return the string this token represents.
+  	@method toString
+  	@return {String}
+  */
+		toString: function toString() {
+			var result = [];
+			for (var _i2 = 0; _i2 < this.v.length; _i2++) {
+				result.push(this.v[_i2].toString());
+			}
+			return result.join('');
+		},
+
+
+		/**
+  	What should the value for this token be in the `href` HTML attribute?
+  	Returns the `.toString` value by default.
+  		@method toHref
+  	@return {String}
+  */
+		toHref: function toHref() {
+			return this.toString();
+		},
+
+
+		/**
+  	Returns a hash of relevant values for this token, which includes keys
+  	* type - Kind of token ('url', 'email', etc.)
+  	* value - Original text
+  	* href - The value that should be added to the anchor tag's href
+  		attribute
+  		@method toObject
+  	@param {String} [protocol] `'http'` by default
+  	@return {Object}
+  */
+		toObject: function toObject() {
+			var protocol = arguments.length <= 0 || arguments[0] === undefined ? 'http' : arguments[0];
+
+			return {
+				type: this.type,
+				value: this.toString(),
+				href: this.toHref(protocol)
+			};
+		}
+	};
+
+	/**
+ 	Represents a list of tokens making up a valid email address
+ 	@class EMAIL
+ 	@extends MultiToken
+ */
+	var EMAIL = inherits(MultiToken, createTokenClass(), {
+		type: 'email',
+		isLink: true,
+		toHref: function toHref() {
+			return 'mailto:' + this.toString();
+		}
+	});
+
+	/**
+ 	Represents some plain text
+ 	@class TEXT
+ 	@extends MultiToken
+ */
+	var TEXT = inherits(MultiToken, createTokenClass(), { type: 'text' });
+
+	/**
+ 	Multi-linebreak token - represents a line break
+ 	@class NL
+ 	@extends MultiToken
+ */
+	var NL = inherits(MultiToken, createTokenClass(), { type: 'nl' });
+
+	/**
+ 	Represents a list of tokens making up a valid URL
+ 	@class URL
+ 	@extends MultiToken
+ */
+	var URL = inherits(MultiToken, createTokenClass(), {
+		type: 'url',
+		isLink: true,
+
+		/**
+  	Lowercases relevant parts of the domain and adds the protocol if
+  	required. Note that this will not escape unsafe HTML characters in the
+  	URL.
+  		@method href
+  	@param {String} protocol
+  	@return {String}
+  */
+		toHref: function toHref() {
+			var protocol = arguments.length <= 0 || arguments[0] === undefined ? 'http' : arguments[0];
+
+			var hasProtocol = false;
+			var hasSlashSlash = false;
+			var tokens = this.v;
+			var result = [];
+			var i = 0;
+
+			// Make the first part of the domain lowercase
+			// Lowercase protocol
+			while (tokens[i] instanceof PROTOCOL) {
+				hasProtocol = true;
+				result.push(tokens[i].toString().toLowerCase());
+				i++;
+			}
+
+			// Skip slash-slash
+			while (tokens[i] instanceof SLASH) {
+				hasSlashSlash = true;
+				result.push(tokens[i].toString());
+				i++;
+			}
+
+			// Lowercase all other characters in the domain
+			while (isDomainToken(tokens[i])) {
+				result.push(tokens[i].toString().toLowerCase());
+				i++;
+			}
+
+			// Leave all other characters as they were written
+			for (; i < tokens.length; i++) {
+				result.push(tokens[i].toString());
+			}
+
+			result = result.join('');
+
+			if (!(hasProtocol || hasSlashSlash)) {
+				result = protocol + '://' + result;
+			}
+
+			return result;
+		},
+		hasProtocol: function hasProtocol() {
+			return this.v[0] instanceof PROTOCOL;
+		}
+	});
+
+	var TOKENS$1 = Object.freeze({
+		Base: MultiToken,
+		EMAIL: EMAIL,
+		NL: NL,
+		TEXT: TEXT,
+		URL: URL
+	});
+
+	/**
+ 	Not exactly parser, more like the second-stage scanner (although we can
+ 	theoretically hotswap the code here with a real parser in the future... but
+ 	for a little URL-finding utility abstract syntax trees may be a little
+ 	overkill).
+
+ 	URL format: http://en.wikipedia.org/wiki/URI_scheme
+ 	Email format: http://en.wikipedia.org/wiki/Email_address (links to RFC in
+ 	reference)
+
+ 	@module linkify
+ 	@submodule parser
+ 	@main parser
+ */
+
+	var makeState$1 = function makeState$1(tokenClass) {
+		return new State(tokenClass);
+	};
+
+	// The universal starting state.
+	var S_START$1 = makeState$1();
+
+	// Intermediate states for URLs. Note that domains that begin with a protocol
+	// are treated slighly differently from those that don't.
+	var S_PROTOCOL = makeState$1(); // e.g., 'http:'
+	var S_PROTOCOL_SLASH = makeState$1(); // e.g., '/', 'http:/''
+	var S_PROTOCOL_SLASH_SLASH = makeState$1(); // e.g., '//', 'http://'
+	var S_DOMAIN$1 = makeState$1(); // parsed string ends with a potential domain name (A)
+	var S_DOMAIN_DOT = makeState$1(); // (A) domain followed by DOT
+	var S_TLD = makeState$1(URL); // (A) Simplest possible URL with no query string
+	var S_TLD_COLON = makeState$1(); // (A) URL followed by colon (potential port number here)
+	var S_TLD_PORT = makeState$1(URL); // TLD followed by a port number
+	var S_URL = makeState$1(URL); // Long URL with optional port and maybe query string
+	var S_URL_NON_ACCEPTING = makeState$1(); // URL followed by some symbols (will not be part of the final URL)
+	var S_URL_OPENBRACE = makeState$1(); // URL followed by {
+	var S_URL_OPENBRACKET = makeState$1(); // URL followed by [
+	var S_URL_OPENANGLEBRACKET = makeState$1(); // URL followed by <
+	var S_URL_OPENPAREN = makeState$1(); // URL followed by (
+	var S_URL_OPENBRACE_Q = makeState$1(URL); // URL followed by { and some symbols that the URL can end it
+	var S_URL_OPENBRACKET_Q = makeState$1(URL); // URL followed by [ and some symbols that the URL can end it
+	var S_URL_OPENANGLEBRACKET_Q = makeState$1(URL); // URL followed by < and some symbols that the URL can end it
+	var S_URL_OPENPAREN_Q = makeState$1(URL); // URL followed by ( and some symbols that the URL can end it
+	var S_URL_OPENBRACE_SYMS = makeState$1(); // S_URL_OPENBRACE_Q followed by some symbols it cannot end it
+	var S_URL_OPENBRACKET_SYMS = makeState$1(); // S_URL_OPENBRACKET_Q followed by some symbols it cannot end it
+	var S_URL_OPENANGLEBRACKET_SYMS = makeState$1(); // S_URL_OPENANGLEBRACKET_Q followed by some symbols it cannot end it
+	var S_URL_OPENPAREN_SYMS = makeState$1(); // S_URL_OPENPAREN_Q followed by some symbols it cannot end it
+	var S_EMAIL_DOMAIN = makeState$1(); // parsed string starts with local email info + @ with a potential domain name (C)
+	var S_EMAIL_DOMAIN_DOT = makeState$1(); // (C) domain followed by DOT
+	var S_EMAIL = makeState$1(EMAIL); // (C) Possible email address (could have more tlds)
+	var S_EMAIL_COLON = makeState$1(); // (C) URL followed by colon (potential port number here)
+	var S_EMAIL_PORT = makeState$1(EMAIL); // (C) Email address with a port
+	var S_LOCALPART = makeState$1(); // Local part of the email address
+	var S_LOCALPART_AT = makeState$1(); // Local part of the email address plus @
+	var S_LOCALPART_DOT = makeState$1(); // Local part of the email address plus '.' (localpart cannot end in .)
+	var S_NL = makeState$1(NL); // single new line
+
+	// Make path from start to protocol (with '//')
+	S_START$1.on(TNL, S_NL).on(PROTOCOL, S_PROTOCOL).on(SLASH, S_PROTOCOL_SLASH);
+
+	S_PROTOCOL.on(SLASH, S_PROTOCOL_SLASH);
+	S_PROTOCOL_SLASH.on(SLASH, S_PROTOCOL_SLASH_SLASH);
+
+	// The very first potential domain name
+	S_START$1.on(TLD, S_DOMAIN$1).on(DOMAIN, S_DOMAIN$1).on(LOCALHOST, S_TLD).on(NUM, S_DOMAIN$1);
+
+	// Force URL for anything sane followed by protocol
+	S_PROTOCOL_SLASH_SLASH.on(TLD, S_URL).on(DOMAIN, S_URL).on(NUM, S_URL).on(LOCALHOST, S_URL);
+
+	// Account for dots and hyphens
+	// hyphens are usually parts of domain names
+	S_DOMAIN$1.on(DOT, S_DOMAIN_DOT);
+	S_EMAIL_DOMAIN.on(DOT, S_EMAIL_DOMAIN_DOT);
+
+	// Hyphen can jump back to a domain name
+
+	// After the first domain and a dot, we can find either a URL or another domain
+	S_DOMAIN_DOT.on(TLD, S_TLD).on(DOMAIN, S_DOMAIN$1).on(NUM, S_DOMAIN$1).on(LOCALHOST, S_DOMAIN$1);
+
+	S_EMAIL_DOMAIN_DOT.on(TLD, S_EMAIL).on(DOMAIN, S_EMAIL_DOMAIN).on(NUM, S_EMAIL_DOMAIN).on(LOCALHOST, S_EMAIL_DOMAIN);
+
+	// S_TLD accepts! But the URL could be longer, try to find a match greedily
+	// The `run` function should be able to "rollback" to the accepting state
+	S_TLD.on(DOT, S_DOMAIN_DOT);
+	S_EMAIL.on(DOT, S_EMAIL_DOMAIN_DOT);
+
+	// Become real URLs after `SLASH` or `COLON NUM SLASH`
+	// Here PSS and non-PSS converge
+	S_TLD.on(COLON, S_TLD_COLON).on(SLASH, S_URL);
+	S_TLD_COLON.on(NUM, S_TLD_PORT);
+	S_TLD_PORT.on(SLASH, S_URL);
+	S_EMAIL.on(COLON, S_EMAIL_COLON);
+	S_EMAIL_COLON.on(NUM, S_EMAIL_PORT);
+
+	// Types of characters the URL can definitely end in
+	var qsAccepting = [DOMAIN, AT, LOCALHOST, NUM, PLUS, POUND, PROTOCOL, SLASH, TLD, UNDERSCORE, SYM];
+
+	// Types of tokens that can follow a URL and be part of the query string
+	// but cannot be the very last characters
+	// Characters that cannot appear in the URL at all should be excluded
+	var qsNonAccepting = [COLON, DOT, QUERY, PUNCTUATION, CLOSEBRACE, CLOSEBRACKET, CLOSEANGLEBRACKET, CLOSEPAREN, OPENBRACE, OPENBRACKET, OPENANGLEBRACKET, OPENPAREN];
+
+	// These states are responsible primarily for determining whether or not to
+	// include the final round bracket.
+
+	// URL, followed by an opening bracket
+	S_URL.on(OPENBRACE, S_URL_OPENBRACE).on(OPENBRACKET, S_URL_OPENBRACKET).on(OPENANGLEBRACKET, S_URL_OPENANGLEBRACKET).on(OPENPAREN, S_URL_OPENPAREN);
+
+	// URL with extra symbols at the end, followed by an opening bracket
+	S_URL_NON_ACCEPTING.on(OPENBRACE, S_URL_OPENBRACE).on(OPENBRACKET, S_URL_OPENBRACKET).on(OPENANGLEBRACKET, S_URL_OPENANGLEBRACKET).on(OPENPAREN, S_URL_OPENPAREN);
+
+	// Closing bracket component. This character WILL be included in the URL
+	S_URL_OPENBRACE.on(CLOSEBRACE, S_URL);
+	S_URL_OPENBRACKET.on(CLOSEBRACKET, S_URL);
+	S_URL_OPENANGLEBRACKET.on(CLOSEANGLEBRACKET, S_URL);
+	S_URL_OPENPAREN.on(CLOSEPAREN, S_URL);
+	S_URL_OPENBRACE_Q.on(CLOSEBRACE, S_URL);
+	S_URL_OPENBRACKET_Q.on(CLOSEBRACKET, S_URL);
+	S_URL_OPENANGLEBRACKET_Q.on(CLOSEANGLEBRACKET, S_URL);
+	S_URL_OPENPAREN_Q.on(CLOSEPAREN, S_URL);
+	S_URL_OPENBRACE_SYMS.on(CLOSEBRACE, S_URL);
+	S_URL_OPENBRACKET_SYMS.on(CLOSEBRACKET, S_URL);
+	S_URL_OPENANGLEBRACKET_SYMS.on(CLOSEANGLEBRACKET, S_URL);
+	S_URL_OPENPAREN_SYMS.on(CLOSEPAREN, S_URL);
+
+	// URL that beings with an opening bracket, followed by a symbols.
+	// Note that the final state can still be `S_URL_OPENBRACE_Q` (if the URL only
+	// has a single opening bracket for some reason).
+	S_URL_OPENBRACE.on(qsAccepting, S_URL_OPENBRACE_Q);
+	S_URL_OPENBRACKET.on(qsAccepting, S_URL_OPENBRACKET_Q);
+	S_URL_OPENANGLEBRACKET.on(qsAccepting, S_URL_OPENANGLEBRACKET_Q);
+	S_URL_OPENPAREN.on(qsAccepting, S_URL_OPENPAREN_Q);
+	S_URL_OPENBRACE.on(qsNonAccepting, S_URL_OPENBRACE_SYMS);
+	S_URL_OPENBRACKET.on(qsNonAccepting, S_URL_OPENBRACKET_SYMS);
+	S_URL_OPENANGLEBRACKET.on(qsNonAccepting, S_URL_OPENANGLEBRACKET_SYMS);
+	S_URL_OPENPAREN.on(qsNonAccepting, S_URL_OPENPAREN_SYMS);
+
+	// URL that begins with an opening bracket, followed by some symbols
+	S_URL_OPENBRACE_Q.on(qsAccepting, S_URL_OPENBRACE_Q);
+	S_URL_OPENBRACKET_Q.on(qsAccepting, S_URL_OPENBRACKET_Q);
+	S_URL_OPENANGLEBRACKET_Q.on(qsAccepting, S_URL_OPENANGLEBRACKET_Q);
+	S_URL_OPENPAREN_Q.on(qsAccepting, S_URL_OPENPAREN_Q);
+	S_URL_OPENBRACE_Q.on(qsNonAccepting, S_URL_OPENBRACE_Q);
+	S_URL_OPENBRACKET_Q.on(qsNonAccepting, S_URL_OPENBRACKET_Q);
+	S_URL_OPENANGLEBRACKET_Q.on(qsNonAccepting, S_URL_OPENANGLEBRACKET_Q);
+	S_URL_OPENPAREN_Q.on(qsNonAccepting, S_URL_OPENPAREN_Q);
+
+	S_URL_OPENBRACE_SYMS.on(qsAccepting, S_URL_OPENBRACE_Q);
+	S_URL_OPENBRACKET_SYMS.on(qsAccepting, S_URL_OPENBRACKET_Q);
+	S_URL_OPENANGLEBRACKET_SYMS.on(qsAccepting, S_URL_OPENANGLEBRACKET_Q);
+	S_URL_OPENPAREN_SYMS.on(qsAccepting, S_URL_OPENPAREN_Q);
+	S_URL_OPENBRACE_SYMS.on(qsNonAccepting, S_URL_OPENBRACE_SYMS);
+	S_URL_OPENBRACKET_SYMS.on(qsNonAccepting, S_URL_OPENBRACKET_SYMS);
+	S_URL_OPENANGLEBRACKET_SYMS.on(qsNonAccepting, S_URL_OPENANGLEBRACKET_SYMS);
+	S_URL_OPENPAREN_SYMS.on(qsNonAccepting, S_URL_OPENPAREN_SYMS);
+
+	// Account for the query string
+	S_URL.on(qsAccepting, S_URL);
+	S_URL_NON_ACCEPTING.on(qsAccepting, S_URL);
+
+	S_URL.on(qsNonAccepting, S_URL_NON_ACCEPTING);
+	S_URL_NON_ACCEPTING.on(qsNonAccepting, S_URL_NON_ACCEPTING);
+
+	// Email address-specific state definitions
+	// Note: We are not allowing '/' in email addresses since this would interfere
+	// with real URLs
+
+	// Tokens allowed in the localpart of the email
+	var localpartAccepting = [DOMAIN, NUM, PLUS, POUND, QUERY, UNDERSCORE, SYM, TLD];
+
+	// Some of the tokens in `localpartAccepting` are already accounted for here and
+	// will not be overwritten (don't worry)
+	S_DOMAIN$1.on(localpartAccepting, S_LOCALPART).on(AT, S_LOCALPART_AT);
+	S_TLD.on(localpartAccepting, S_LOCALPART).on(AT, S_LOCALPART_AT);
+	S_DOMAIN_DOT.on(localpartAccepting, S_LOCALPART);
+
+	// Okay we're on a localpart. Now what?
+	// TODO: IP addresses and what if the email starts with numbers?
+	S_LOCALPART.on(localpartAccepting, S_LOCALPART).on(AT, S_LOCALPART_AT) // close to an email address now
+	.on(DOT, S_LOCALPART_DOT);
+	S_LOCALPART_DOT.on(localpartAccepting, S_LOCALPART);
+	S_LOCALPART_AT.on(TLD, S_EMAIL_DOMAIN).on(DOMAIN, S_EMAIL_DOMAIN).on(LOCALHOST, S_EMAIL);
+	// States following `@` defined above
+
+	var run$1 = function run$1(tokens) {
+		var len = tokens.length;
+		var cursor = 0;
+		var multis = [];
+		var textTokens = [];
+
+		while (cursor < len) {
+			var state = S_START$1;
+			var secondState = null;
+			var nextState = null;
+			var multiLength = 0;
+			var latestAccepting = null;
+			var sinceAccepts = -1;
+
+			while (cursor < len && !(secondState = state.next(tokens[cursor]))) {
+				// Starting tokens with nowhere to jump to.
+				// Consider these to be just plain text
+				textTokens.push(tokens[cursor++]);
+			}
+
+			while (cursor < len && (nextState = secondState || state.next(tokens[cursor]))) {
+
+				// Get the next state
+				secondState = null;
+				state = nextState;
+
+				// Keep track of the latest accepting state
+				if (state.accepts()) {
+					sinceAccepts = 0;
+					latestAccepting = state;
+				} else if (sinceAccepts >= 0) {
+					sinceAccepts++;
+				}
+
+				cursor++;
+				multiLength++;
+			}
+
+			if (sinceAccepts < 0) {
+
+				// No accepting state was found, part of a regular text token
+				// Add all the tokens we looked at to the text tokens array
+				for (var _i3 = cursor - multiLength; _i3 < cursor; _i3++) {
+					textTokens.push(tokens[_i3]);
+				}
+			} else {
+
+				// Accepting state!
+
+				// First close off the textTokens (if available)
+				if (textTokens.length > 0) {
+					multis.push(new TEXT(textTokens));
+					textTokens = [];
+				}
+
+				// Roll back to the latest accepting state
+				cursor -= sinceAccepts;
+				multiLength -= sinceAccepts;
+
+				// Create a new multitoken
+				var MULTI = latestAccepting.emit();
+				multis.push(new MULTI(tokens.slice(cursor - multiLength, cursor)));
+			}
+		}
+
+		// Finally close off the textTokens (if available)
+		if (textTokens.length > 0) {
+			multis.push(new TEXT(textTokens));
+		}
+
+		return multis;
+	};
+
+	var parser = Object.freeze({
+		State: State,
+		TOKENS: TOKENS$1,
+		run: run$1,
+		start: S_START$1
+	});
+
+	if (!Array.isArray) {
+		Array.isArray = function (arg) {
+			return Object.prototype.toString.call(arg) === '[object Array]';
+		};
+	}
+
+	/**
+ 	Converts a string into tokens that represent linkable and non-linkable bits
+ 	@method tokenize
+ 	@param {String} str
+ 	@return {Array} tokens
+ */
+	var tokenize = function tokenize(str) {
+		return run$1(run(str));
+	};
+
+	/**
+ 	Returns a list of linkable items in the given string.
+ */
+	var find = function find(str) {
+		var type = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+		var tokens = tokenize(str);
+		var filtered = [];
+
+		for (var i = 0; i < tokens.length; i++) {
+			var token = tokens[i];
+			if (token.isLink && (!type || token.type === type)) {
+				filtered.push(token.toObject());
+			}
+		}
+
+		return filtered;
+	};
+
+	/**
+ 	Is the given string valid linkable text of some sort
+ 	Note that this does not trim the text for you.
+
+ 	Optionally pass in a second `type` param, which is the type of link to test
+ 	for.
+
+ 	For example,
+
+ 		test(str, 'email');
+
+ 	Will return `true` if str is a valid email.
+ */
+	var test = function test(str) {
+		var type = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+		var tokens = tokenize(str);
+		return tokens.length === 1 && tokens[0].isLink && (!type || tokens[0].type === type);
+	};
+
+	exports.find = find;
+	exports.inherits = inherits;
+	exports.options = options;
+	exports.parser = parser;
+	exports.scanner = scanner;
+	exports.test = test;
+	exports.tokenize = tokenize;
+})(window.linkify = window.linkify || {});
+})();
diff --git a/lrcinstance.h b/lrcinstance.h
index 517eaae..1377dd6 100644
--- a/lrcinstance.h
+++ b/lrcinstance.h
@@ -93,6 +93,14 @@
         instance().selectedConvUid = convUid;
     };
 
+    static void reset(bool newInstance = false) {
+        if (newInstance) {
+            instance().lrc_.reset(new lrc::api::Lrc());
+        } else {
+            instance().lrc_.reset();
+        }
+    };
+
 private:
     std::unique_ptr<lrc::api::Lrc> lrc_;
 
diff --git a/main.cpp b/main.cpp
index 5e3d70f..469351c 100644
--- a/main.cpp
+++ b/main.cpp
@@ -17,6 +17,7 @@
  **************************************************************************/
 
 #include "mainwindow.h"
+
 #include <QApplication>
 #include <QFile>
 
@@ -35,11 +36,13 @@
 
 #include <ciso646>
 
+#include "utils.h"
+
 #ifdef Q_OS_WIN
 #include <windows.h>
 #endif
 
-#ifdef _MSC_VER
+#if defined _MSC_VER && !COMPILE_ONLY
 #include <gnutls/gnutls.h>
 #endif
 
@@ -88,7 +91,7 @@
     auto startMinimized = false;
     QString uri = "";
 
-#ifdef _MSC_VER
+#if defined _MSC_VER && !COMPILE_ONLY
     gnutls_global_init();
 #endif
 
diff --git a/mainwindow.cpp b/mainwindow.cpp
index efaa475..176853c 100644
--- a/mainwindow.cpp
+++ b/mainwindow.cpp
@@ -38,6 +38,7 @@
 #include "callwidget.h"
 #include "utils.h"
 #include "wizarddialog.h"
+#include "version.h"
 
 MainWindow::MainWindow(QWidget* parent) :
     QMainWindow(parent),
@@ -102,7 +103,7 @@
     readSettingsFromRegistry();
 
     win_sparkle_set_appcast_url("http://dl.ring.cx/windows/winsparkle-ring.xml");
-    win_sparkle_set_app_details(L"Savoir-faire Linux", L"Ring", QString(NIGHTLY_VERSION).toStdWString().c_str());
+    win_sparkle_set_app_details(L"Savoir-faire Linux", L"Ring", QString(VERSION_STRING).toStdWString().c_str());
     win_sparkle_set_shutdown_request_callback([]() {QCoreApplication::exit();});
     win_sparkle_set_did_find_update_callback([]() {MainWindow::instance().showNormal();});
     win_sparkle_init();
@@ -243,6 +244,7 @@
         settings.setValue(SettingsKey::geometry, saveGeometry());
         settings.setValue(SettingsKey::windowState, saveState());
     }
+    LRCInstance::reset();
     QMainWindow::closeEvent(event);
 }
 
diff --git a/messagemodel.cpp b/messagemodel.cpp
deleted file mode 100644
index 998c97f..0000000
--- a/messagemodel.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-/***************************************************************************
-* Copyright (C) 2017 by Savoir-faire Linux                                 *
-* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>           *
-*                                                                          *
-* This program is free software; you can redistribute it and/or modify     *
-* it under the terms of the GNU General Public License as published by     *
-* the Free Software Foundation; either version 3 of the License, or        *
-* (at your option) any later version.                                      *
-*                                                                          *
-* This program is distributed in the hope that it will be useful,          *
-* but WITHOUT ANY WARRANTY; without even the implied warranty of           *
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
-* GNU General Public License for more details.                             *
-*                                                                          *
-* You should have received a copy of the GNU General Public License        *
-* along with this program.  If not, see <http://www.gnu.org/licenses/>.    *
-***************************************************************************/
-
-#include "messagemodel.h"
-
-// Qt
-#include <QDateTime>
-
-// LRC
-#include "globalinstances.h"
-#include "api/contactmodel.h"
-#include "api/conversationmodel.h"
-
-// Client
-#include "pixbufmanipulator.h"
-#include "utils.h"
-
-MessageModel::MessageModel(const ConversationInfo& conv, const AccountInfo& acc, QObject *parent)
-    : QAbstractItemModel(parent),
-    conv_(conv),
-    acc_(acc)
-{
-}
-
-int MessageModel::rowCount(const QModelIndex &parent) const
-{
-    if (!parent.isValid()) {
-        return conv_.interactions.size();
-    }
-    return 0; // A valid QModelIndex returns 0 as no entry has sub-elements
-}
-
-int MessageModel::columnCount(const QModelIndex &parent) const
-{
-    Q_UNUSED(parent);
-    return 1;
-}
-
-QVariant MessageModel::data(const QModelIndex &index, int role) const
-{
-    if (!index.isValid() || conv_.interactions.size() == 0) {
-        return QVariant();
-    }
-
-    auto it = conv_.interactions.begin();
-    std::advance(it, index.row());
-    const auto& item = (*it).second;
-    switch (role) {
-    case Role::Body:
-        return QVariant(QString::fromStdString(item.body));
-    case Role::Picture:
-    case Qt::DecorationRole:
-        return GlobalInstances::pixmapManipulator().decorationRole(conv_, acc_);
-    case Role::DisplayName:
-    case Qt::DisplayRole:
-    {
-        auto& contact = acc_.contactModel->getContact(conv_.participants[0]);
-        return QVariant(QString::fromStdString(Utils::bestNameForContact(contact)));
-    }
-    case Role::Presence:
-    {
-        auto& contact = acc_.contactModel->getContact(conv_.participants[0]);
-        return QVariant(contact.isPresent);
-    }
-    case Role::InteractionDate:
-    {
-        auto& date = item.timestamp;
-        return QVariant(QDateTime::fromTime_t(date));
-    }
-    case Role::Status:
-        return QVariant::fromValue(static_cast<int>(item.status));
-    case Role::Direction:
-        return QVariant::fromValue(lrc::api::interaction::isOutgoing(item));
-    case Role::Type:
-        return QVariant::fromValue(static_cast<int>(item.type));
-    }
-    return QVariant();
-}
-
-QModelIndex MessageModel::index(int row, int column, const QModelIndex &parent) const
-{
-    Q_UNUSED(parent);
-    if (column != 0) {
-        return QModelIndex();
-    }
-
-    if (row >= 0 && row < rowCount()) {
-        return createIndex(row, column);
-    }
-    return QModelIndex();
-}
-
-QModelIndex MessageModel::parent(const QModelIndex &child) const
-{
-    Q_UNUSED(child);
-    return QModelIndex();
-}
-
-Qt::ItemFlags MessageModel::flags(const QModelIndex &index) const
-{
-    auto flags = QAbstractItemModel::flags(index) | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable;
-    if (!index.isValid()) {
-        return QAbstractItemModel::flags(index);
-    } else {
-        flags &= ~(Qt::ItemIsSelectable);
-    }
-    return flags;
-}
diff --git a/messagemodel.h b/messagemodel.h
deleted file mode 100644
index b8411c3..0000000
--- a/messagemodel.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/***************************************************************************
-* Copyright (C) 2017 by Savoir-faire Linux                                 *
-* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>           *
-*                                                                          *
-* This program is free software; you can redistribute it and/or modify     *
-* it under the terms of the GNU General Public License as published by     *
-* the Free Software Foundation; either version 3 of the License, or        *
-* (at your option) any later version.                                      *
-*                                                                          *
-* This program is distributed in the hope that it will be useful,          *
-* but WITHOUT ANY WARRANTY; without even the implied warranty of           *
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
-* GNU General Public License for more details.                             *
-*                                                                          *
-* You should have received a copy of the GNU General Public License        *
-* along with this program.  If not, see <http://www.gnu.org/licenses/>.    *
-***************************************************************************/
-#pragma once
-
-// Qt include
-#include <QAbstractItemModel>
-
-// LRC
-#include "api/account.h"
-#include "api/conversation.h"
-#include "api/contact.h"
-
-class MessageModel : public QAbstractItemModel
-{
-    Q_OBJECT
-public:
-    using AccountInfo = lrc::api::account::Info;
-    using ConversationInfo = lrc::api::conversation::Info;
-    using ContactInfo = lrc::api::contact::Info;
-
-    enum Role {
-        Body = Qt::UserRole + 1,
-        DisplayName,
-        Picture,
-        Presence,
-        Status,
-        DataTransferStatus,
-        InteractionDate,
-        Direction,
-        Type
-    };
-
-    explicit MessageModel(const ConversationInfo& conv, const AccountInfo& acc, QObject *parent = 0);
-
-    // QAbstractItemModel
-    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
-    int columnCount(const QModelIndex &parent) const override;
-    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
-    QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const;
-    QModelIndex parent(const QModelIndex &child) const;
-    Qt::ItemFlags flags(const QModelIndex &index) const;
-
-private:
-    ConversationInfo conv_;
-    const AccountInfo& acc_;
-};
diff --git a/conversationfilterbutton.h b/messagewebpage.cpp
similarity index 66%
copy from conversationfilterbutton.h
copy to messagewebpage.cpp
index 4877227..441fc27 100644
--- a/conversationfilterbutton.h
+++ b/messagewebpage.cpp
@@ -1,45 +1,42 @@
-/***************************************************************************

- * Copyright (C) 2018 by Savoir-faire Linux                                *

- * Author: Isa Nanic <isa.nanic@savoirfairelinux.com>                      *

- *                                                                         *

- * This program is free software; you can redistribute it and/or modify    *

- * it under the terms of the GNU General Public License as published by    *

- * the Free Software Foundation; either version 3 of the License, or       *

- * (at your option) any later version.                                     *

- *                                                                         *

- * This program is distributed in the hope that it will be useful,         *

- * but WITHOUT ANY WARRANTY; without even the implied warranty of          *

- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *

- * GNU General Public License for more details.                            *

- *                                                                         *

- * You should have received a copy of the GNU General Public License       *

- * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *

- **************************************************************************/

-

-#pragma once

-

-#include <QPushButton>

-

-namespace Ui{

-    class ConvesationFilterButton;

-}

-

-class ConversationFilterButton : public QPushButton

-{

-    Q_OBJECT

-    ConversationFilterButton(const ConversationFilterButton& cpy);

-public:

-    ConversationFilterButton();

-    ConversationFilterButton(QWidget * widget);

-    ~ConversationFilterButton();

-

-public slots:

-    void setSelected();

-    void setUnselected();

-

-

-private:

-    void paintEvent(QPaintEvent* e);

-    bool selected_ = false;

-};

-

+/***************************************************************************
+ * Copyright (C) 2018 by Savoir-faire Linux                                *
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>          *
+ *                                                                         *
+ * This program is free software; you can redistribute it and/or modify    *
+ * it under the terms of the GNU General Public License as published by    *
+ * the Free Software Foundation; either version 3 of the License, or       *
+ * (at your option) any later version.                                     *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful,         *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License       *
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ **************************************************************************/
+
+#include <QDesktopServices>
+
+#include "messagewebpage.h"
+
+MessageWebPage::MessageWebPage(QWidget *parent)
+    : QWebEnginePage(parent)
+{
+}
+
+MessageWebPage::~MessageWebPage()
+{
+}
+
+bool
+MessageWebPage::acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool isMainFrame)
+{
+    qDebug() << "acceptNavigationRequest(" << url << "," << type << "," << isMainFrame << ")";
+
+    if (type == QWebEnginePage::NavigationTypeLinkClicked) {
+        QDesktopServices::openUrl(url);
+        return false;
+    }
+    return true;
+}
diff --git a/conversationfilterbutton.h b/messagewebpage.h
similarity index 70%
rename from conversationfilterbutton.h
rename to messagewebpage.h
index 4877227..e8c4cea 100644
--- a/conversationfilterbutton.h
+++ b/messagewebpage.h
@@ -1,45 +1,35 @@
-/***************************************************************************

- * Copyright (C) 2018 by Savoir-faire Linux                                *

- * Author: Isa Nanic <isa.nanic@savoirfairelinux.com>                      *

- *                                                                         *

- * This program is free software; you can redistribute it and/or modify    *

- * it under the terms of the GNU General Public License as published by    *

- * the Free Software Foundation; either version 3 of the License, or       *

- * (at your option) any later version.                                     *

- *                                                                         *

- * This program is distributed in the hope that it will be useful,         *

- * but WITHOUT ANY WARRANTY; without even the implied warranty of          *

- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *

- * GNU General Public License for more details.                            *

- *                                                                         *

- * You should have received a copy of the GNU General Public License       *

- * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *

- **************************************************************************/

-

-#pragma once

-

-#include <QPushButton>

-

-namespace Ui{

-    class ConvesationFilterButton;

-}

-

-class ConversationFilterButton : public QPushButton

-{

-    Q_OBJECT

-    ConversationFilterButton(const ConversationFilterButton& cpy);

-public:

-    ConversationFilterButton();

-    ConversationFilterButton(QWidget * widget);

-    ~ConversationFilterButton();

-

-public slots:

-    void setSelected();

-    void setUnselected();

-

-

-private:

-    void paintEvent(QPaintEvent* e);

-    bool selected_ = false;

-};

-

+/***************************************************************************
+ * Copyright (C) 2018 by Savoir-faire Linux                                *
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>          *
+ *                                                                         *
+ * This program is free software; you can redistribute it and/or modify    *
+ * it under the terms of the GNU General Public License as published by    *
+ * the Free Software Foundation; either version 3 of the License, or       *
+ * (at your option) any later version.                                     *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful,         *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License       *
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ **************************************************************************/
+
+#pragma once
+
+#include <QtWebEngineWidgets/QWebEngineView>
+
+class MessageWebPage : public QWebEnginePage
+{
+    Q_OBJECT
+public:
+    explicit MessageWebPage(QWidget* parent = nullptr);
+    ~MessageWebPage();
+
+protected:
+    virtual bool acceptNavigationRequest(const QUrl &url,
+                                         QWebEnginePage::NavigationType type,
+                                         bool isMainFrame);
+
+};
\ No newline at end of file
diff --git a/messagewebview.cpp b/messagewebview.cpp
new file mode 100644
index 0000000..5262edd
--- /dev/null
+++ b/messagewebview.cpp
@@ -0,0 +1,348 @@
+/***************************************************************************
+ * Copyright (C) 2017-2018 by Savoir-faire Linux                           *
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>          *
+ * Author: Alexandre Viau <alexandre.viau@savoirfairelinux.com>            *
+ * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>            *
+ * Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>              *
+ *                                                                         *
+ * This program is free software; you can redistribute it and/or modify    *
+ * it under the terms of the GNU General Public License as published by    *
+ * the Free Software Foundation; either version 3 of the License, or       *
+ * (at your option) any later version.                                     *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful,         *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License       *
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ **************************************************************************/
+
+#include "messagewebview.h"
+
+#include <QScrollBar>
+#include <QMouseEvent>
+#include <QDebug>
+#include <QMenu>
+#include <QDesktopServices>
+#include <QFileDialog>
+#include <QWebEnginePage>
+#include <QWebEngineScript>
+#include <QWebEngineScriptCollection>
+#include <QWebEngineSettings>
+#include <QWebChannel>
+#include <QTimer>
+
+#include <ciso646>
+#include <fstream>
+
+#include "utils.h"
+#include "webchathelpers.h"
+#include "messagewebpage.h"
+#include "lrcinstance.h"
+
+MessageWebView::MessageWebView(QWidget *parent)
+    : QWebEngineView(parent)
+{
+    setPage(new MessageWebPage());
+
+    settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
+    settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true);
+    settings()->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, false);
+    settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+    settings()->setAttribute(QWebEngineSettings::PluginsEnabled, false);
+    settings()->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, false);
+    settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, false);
+    settings()->setAttribute(QWebEngineSettings::LinksIncludedInFocusChain, false);
+    settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
+
+    setContextMenuPolicy(Qt::ContextMenuPolicy::NoContextMenu);
+
+    jsBridge_ = new PrivateBridging(this);
+    webChannel_ = new QWebChannel(page());
+    webChannel_->registerObject(QStringLiteral("jsbridge"), jsBridge_);
+    page()->setWebChannel(webChannel_);
+
+    connect(this, &QWebEngineView::renderProcessTerminated,
+        [this](QWebEnginePage::RenderProcessTerminationStatus termStatus, int statusCode) {
+            Q_UNUSED(statusCode);
+            QString status;
+            switch (termStatus) {
+            case QWebEnginePage::NormalTerminationStatus:
+                qDebug() << "MessageWebView - Render process normal exit";
+                break;
+            case QWebEnginePage::AbnormalTerminationStatus:
+                qDebug() << "MessageWebView - Render process abnormal exit";
+                break;
+            case QWebEnginePage::CrashedTerminationStatus:
+                qDebug() << "MessageWebView - Render process crashed";
+                break;
+            case QWebEnginePage::KilledTerminationStatus:
+                qDebug() << "MessageWebView - Render process killed";
+                break;
+            }
+        });
+}
+
+MessageWebView::~MessageWebView()
+{
+}
+
+void MessageWebView::buildView()
+{
+    auto html = Utils::QByteArrayFromFile(":/web/chatview.html");
+    page()->setHtml(html, QUrl(":/web/chatview.html"));
+    connect(this, &QWebEngineView::loadFinished, this, &MessageWebView::slotLoadFinished);
+}
+
+void
+MessageWebView::slotLoadFinished()
+{
+    insertStyleSheet("chatcss", Utils::QByteArrayFromFile(":/web/chatview.css"));
+    page()->runJavaScript(Utils::QByteArrayFromFile(":/web/linkify.js"), QWebEngineScript::MainWorld);
+    page()->runJavaScript(Utils::QByteArrayFromFile(":/web/linkify-html.js"), QWebEngineScript::MainWorld);
+    page()->runJavaScript(Utils::QByteArrayFromFile(":/web/linkify-string.js"), QWebEngineScript::MainWorld);
+    page()->runJavaScript(Utils::QByteArrayFromFile(":/web/qwebchannel.js"), QWebEngineScript::MainWorld);
+    page()->runJavaScript(Utils::QByteArrayFromFile(":/web/chatview.js"), QWebEngineScript::MainWorld);
+}
+
+void
+MessageWebView::insertStyleSheet(const QString &name, const QString &source)
+{
+    QWebEngineScript script;
+    auto simplifiedCSS = source.simplified().replace("'", "\"");
+    QString s = QString::fromLatin1("(function() {"\
+                                    "    var node = document.createElement('style');"\
+                                    "    node.id = '%1';"\
+                                    "    node.innerHTML = '%2';"\
+                                    "    document.head.appendChild(node);"\
+                                    "})()").arg(name).arg(simplifiedCSS);
+    page()->runJavaScript(s);
+
+    script.setName(name);
+    script.setSourceCode(s);
+    script.setInjectionPoint(QWebEngineScript::DocumentReady);
+    script.setRunsOnSubFrames(true);
+    script.setWorldId(QWebEngineScript::MainWorld);
+    page()->scripts().insert(script);
+}
+
+void
+MessageWebView::removeStyleSheet(const QString &name)
+{
+    QString s = QString::fromLatin1("(function() {"\
+                                    "    var element = document.getElementById('%1');"\
+                                    "    element.outerHTML = '';"\
+                                    "    delete element;"\
+                                    "})()").arg(name);
+
+    page()->runJavaScript(s);
+
+    QWebEngineScript script = page()->scripts().findScript(name);
+    page()->scripts().remove(script);
+}
+
+void MessageWebView::clear()
+{
+    QString s = QString::fromLatin1("clearMessages();");
+    page()->runJavaScript(s, QWebEngineScript::MainWorld);
+}
+
+void
+MessageWebView::setDisplayLinks(bool display)
+{
+    QString s = QString::fromLatin1("setDisplayLinks('%1');")
+        .arg(display ? "true" : "false");
+    page()->runJavaScript(s, QWebEngineScript::MainWorld);
+}
+
+void
+MessageWebView::printNewInteraction(lrc::api::ConversationModel& conversationModel,
+                                    uint64_t msgId,
+                                    const lrc::api::interaction::Info& interaction)
+{
+    auto interactionObject = interactionToJsonInteractionObject(conversationModel, msgId, interaction).toUtf8();
+    QString s = QString::fromLatin1("addMessage(%1);")
+        .arg(interactionObject.constData());
+    page()->runJavaScript(s, QWebEngineScript::MainWorld);
+}
+
+void
+MessageWebView::updateInteraction(lrc::api::ConversationModel& conversationModel,
+                                  uint64_t msgId,
+                                  const lrc::api::interaction::Info& interaction)
+{
+    auto interactionObject = interactionToJsonInteractionObject(conversationModel, msgId, interaction).toUtf8();
+    QString s = QString::fromLatin1("updateMessage(%1);")
+        .arg(interactionObject.constData());
+    page()->runJavaScript(s, QWebEngineScript::MainWorld);
+}
+
+void
+MessageWebView::removeInteraction(uint64_t interactionId)
+{
+    QString s = QString::fromLatin1("removeInteraction(%1);")
+        .arg(QString::number(interactionId));
+    page()->runJavaScript(s, QWebEngineScript::MainWorld);
+}
+
+void
+MessageWebView::printHistory(lrc::api::ConversationModel& conversationModel,
+                             const std::map<uint64_t,
+                             lrc::api::interaction::Info> interactions)
+{
+    auto interactionsStr = interactionsToJsonArrayObject(conversationModel, interactions).toUtf8();
+    QString s = QString::fromLatin1("printHistory(%1);")
+        .arg(interactionsStr.constData());
+    page()->runJavaScript(s, QWebEngineScript::MainWorld);
+}
+
+void
+MessageWebView::setSenderImage(const std::string& sender,
+                               const std::string& senderImage)
+{
+    QJsonObject setSenderImageObject = QJsonObject();
+    setSenderImageObject.insert("sender_contact_method", QJsonValue(QString(sender.c_str())));
+    setSenderImageObject.insert("sender_image", QJsonValue(QString(senderImage.c_str())));
+
+    auto setSenderImageObjectString = QString(QJsonDocument(setSenderImageObject).toJson(QJsonDocument::Compact));
+    QString s = QString::fromLatin1("setSenderImage(%1);")
+        .arg(setSenderImageObjectString.toUtf8().constData());
+    page()->runJavaScript(s, QWebEngineScript::MainWorld);
+}
+
+void
+MessageWebView::setInvitation(bool show, const std::string& contactUri)
+{
+    QString s = QString::fromLatin1(show ? "showInvitation(\"%1\")" : "showInvitation()")
+        .arg(QString(contactUri.c_str()));
+    page()->runJavaScript(s, QWebEngineScript::MainWorld);
+}
+
+void
+MessageWebView::hideMessages()
+{
+    QString s = QString::fromLatin1("hideBody();");
+    page()->runJavaScript(s, QWebEngineScript::MainWorld);
+}
+
+// JS bridging incoming
+Q_INVOKABLE int
+PrivateBridging::deleteInteraction(const QString& arg)
+{
+    bool ok;
+    uint64_t interactionUid = arg.toULongLong(&ok);
+    if (ok) {
+        LRCInstance::getCurrentConversationModel()->clearInteractionFromConversation(
+            LRCInstance::getSelectedConvUid(),
+            interactionUid
+        );
+    } else {
+        qDebug() << "deleteInteraction - invalid arg" << arg;
+    }
+    return 0;
+}
+
+Q_INVOKABLE int
+PrivateBridging::retryInteraction(const QString& arg)
+{
+    bool ok;
+    uint64_t interactionUid = arg.toULongLong(&ok);
+    if (ok) {
+        LRCInstance::getCurrentConversationModel()->retryInteraction(
+            LRCInstance::getSelectedConvUid(),
+            interactionUid
+        );
+    } else {
+        qDebug() << "retryInteraction - invalid arg" << arg;
+    }
+    return 0;
+}
+
+Q_INVOKABLE int
+PrivateBridging::openFile(const QString& arg)
+{
+    QDesktopServices::openUrl(arg);
+    return 0;
+}
+
+Q_INVOKABLE int
+PrivateBridging::acceptFile(const QString& arg)
+{
+    try {
+        auto interactionUid = std::stoull(arg.toStdString());
+
+        lrc::api::datatransfer::Info info = {};
+        auto convUid = LRCInstance::getSelectedConvUid();
+        LRCInstance::getCurrentConversationModel()->getTransferInfo(interactionUid, info);
+
+        auto downloadsFolder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation).toStdString();
+        // get full path
+        std::string filename = downloadsFolder.c_str();
+        if (!filename.empty() && filename.back() != '/')
+            filename += "/";
+        auto wantedFilename = filename + info.displayName;
+        auto duplicate = 0;
+        while (std::ifstream(wantedFilename).good()) {
+            ++duplicate;
+            auto extensionIdx = info.displayName.find_last_of(".");
+            if (extensionIdx == std::string::npos)
+                wantedFilename = filename + info.displayName + " (" + std::to_string(duplicate) + ")";
+            else
+                wantedFilename = filename + info.displayName.substr(0, extensionIdx) + " (" + std::to_string(duplicate) + ")" + info.displayName.substr(extensionIdx);
+        }
+        LRCInstance::getCurrentConversationModel()->acceptTransfer(convUid, interactionUid, wantedFilename);
+    } catch (...) {
+        qDebug() << "JS bridging - exception during acceptFile: " << arg;
+    }
+    return 0;
+}
+
+Q_INVOKABLE int
+PrivateBridging::refuseFile(const QString& arg)
+{
+    try {
+        auto interactionUid = std::stoull(arg.toStdString());
+        auto convUid = LRCInstance::getSelectedConvUid();
+        LRCInstance::getCurrentConversationModel()->cancelTransfer(convUid, interactionUid);
+    } catch (...) {
+        qDebug() << "JS bridging - exception during refuseFile:" << arg;
+    }
+    return 0;
+}
+
+Q_INVOKABLE int
+PrivateBridging::sendMessage(const QString& arg)
+{
+    try {
+        auto convUid = LRCInstance::getSelectedConvUid();
+        LRCInstance::getCurrentConversationModel()->sendMessage(convUid, arg.toStdString());
+    } catch (...) {
+        qDebug() << "JS bridging - exception during sendMessage:" << arg;
+    }
+    return 0;
+}
+
+Q_INVOKABLE int
+PrivateBridging::sendFile()
+{
+    qDebug() << "JS bridging - MessageWebView::sendFile";
+    QString filePath = QFileDialog::getOpenFileName((QWidget*)this->parent(), tr("Choose File"), "", tr("Files (*)"));
+    QFileInfo fi(filePath);
+    QString fileName = fi.fileName();
+    try {
+        auto convUid = LRCInstance::getSelectedConvUid();
+        LRCInstance::getCurrentConversationModel()->sendFile(convUid, filePath.toStdString(), fileName.toStdString());
+    } catch (...) {
+        qDebug() << "JS bridging - exception during sendFile";
+    }
+    return 0;
+}
+
+Q_INVOKABLE int
+PrivateBridging::log(const QString& arg)
+{
+    qDebug() << "JS log: " << arg;
+    return 0;
+}
\ No newline at end of file
diff --git a/messagewebview.h b/messagewebview.h
new file mode 100644
index 0000000..72b15e7
--- /dev/null
+++ b/messagewebview.h
@@ -0,0 +1,81 @@
+/***************************************************************************
+ * Copyright (C) 2018 by Savoir-faire Linux                                *
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>          *
+ *                                                                         *
+ * This program is free software; you can redistribute it and/or modify    *
+ * it under the terms of the GNU General Public License as published by    *
+ * the Free Software Foundation; either version 3 of the License, or       *
+ * (at your option) any later version.                                     *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful,         *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License       *
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ **************************************************************************/
+
+#pragma once
+
+#include <QDebug>
+#include <QWebEngineView>
+
+#include "api/conversationmodel.h"
+
+class PrivateBridging : public QObject
+{
+    Q_OBJECT
+public:
+    explicit PrivateBridging(QObject* parent = nullptr) : QObject(parent) {};
+    ~PrivateBridging() {};
+
+    // exposed to JS through QWebChannel
+    Q_INVOKABLE int deleteInteraction(const QString& arg);
+    Q_INVOKABLE int retryInteraction(const QString& arg);
+    Q_INVOKABLE int openFile(const QString& arg);
+    Q_INVOKABLE int acceptFile(const QString& arg);
+    Q_INVOKABLE int refuseFile(const QString& arg);
+    Q_INVOKABLE int sendMessage(const QString & arg);
+    Q_INVOKABLE int sendFile();
+    Q_INVOKABLE int log(const QString& arg);
+
+};
+
+class MessageWebView : public QWebEngineView
+{
+    Q_OBJECT
+public:
+    explicit MessageWebView(QWidget* parent = nullptr);
+    ~MessageWebView();
+
+    void buildView();
+
+    void insertStyleSheet(const QString &name, const QString &source);
+    void removeStyleSheet(const QString &name);
+
+    void clear();
+    void setDisplayLinks(bool display);
+    void printNewInteraction(lrc::api::ConversationModel& conversationModel,
+                             uint64_t msgId,
+                             const lrc::api::interaction::Info& interaction);
+    void updateInteraction(lrc::api::ConversationModel& conversationModel,
+                           uint64_t msgId,
+                           const lrc::api::interaction::Info& interaction);
+    void removeInteraction(uint64_t interactionId);
+    void printHistory(lrc::api::ConversationModel& conversationModel,
+                      const std::map<uint64_t,
+                      lrc::api::interaction::Info> interactions);
+    void setSenderImage(const std::string& sender,
+                        const std::string& senderImage);
+    void setInvitation(bool show, const std::string& contactUri);
+    void hideMessages();
+
+private slots:
+    void slotLoadFinished();
+
+private:
+    QWebChannel* webChannel_;
+    PrivateBridging* jsBridge_;
+
+};
diff --git a/ressources.qrc b/ressources.qrc
index 5cf0ca6..51b40be 100644
--- a/ressources.qrc
+++ b/ressources.qrc
@@ -1,55 +1,62 @@
 <RCC>
-    <qresource prefix="/">
-        <file>stylesheet.css</file>
-        <file>images/ring.png</file>
-        <file>images/logo-ring-standard-coul.png</file>
-        <file>images/icons/ic_arrow_back_white_24dp.png</file>
-        <file>images/icons/ic_mic_off_white_24dp.png</file>
-        <file>images/icons/ic_person_add_white_24dp.png</file>
-        <file>images/icons/ic_search_black_18dp_2x.png</file>
-        <file>images/icons/ic_content_copy_white_24dp.png</file>
-        <file>images/icons/ic_send_white_24dp.png</file>
-        <file>images/icons/ic_group_add_white_24dp.png</file>
-        <file>images/icons/ic_videocam_off_white_24dp.png</file>
-        <file>images/icons/ic_high_quality_white_24dp.png</file>
-        <file>images/icons/ic_pause_white_24dp.png</file>
-        <file>images/icons/ic_chat_white_24dp.png</file>
-        <file>images/icons/ic_done_white_24dp.png</file>
-        <file>images/icons/ic_close_white_24dp.png</file>
-        <file>images/icons/ic_call_transfer_white_24px.png</file>
-        <file>images/icons/ic_pause_white_100px.png</file>
-        <file>images/icons/ic_videocam_white.png</file>
-        <file>images/icons/ic_add_black_18dp_2x.png</file>
-        <file>images/icons/ic_delete_black_18dp_2x.png</file>
-        <file>images/icons/ic_arrow_drop_down_black_18dp_2x.png</file>
-        <file>images/icons/ic_arrow_drop_up_black_18dp_2x.png</file>
-        <file>images/icons/ic_check_white_18dp_2x.png</file>
-        <file>images/icons/ic_folder_black_18dp_2x.png</file>
-        <file>images/icons/ic_arrow_forward_white_48dp_2x.png</file>
-        <file>images/icons/ic_settings_white_48dp_2x.png</file>
-        <file>images/icons/ic_voicemail_white_24dp_2x.png</file>
-        <file>images/background-light.png</file>
-        <file>images/background-dark.png</file>
-        <file>images/icons/ic_arrow_drop_down_black_9dp_2x.png</file>
-        <file>images/icons/ic_arrow_drop_up_black_9dp_2x.png</file>
-        <file>images/icons/ic_arrow_tab_next_black_9dp_2x.png</file>
-        <file>images/icons/ic_arrow_tab_previous_black_9dp_2x.png</file>
-        <file>images/spikeMask.png</file>
-        <file>images/icons/ic_photo_camera_white_24dp_2x.png</file>
-        <file>images/qrcode.png</file>
-        <file>images/icons/ic_share_black_48dp_2x.png</file>
-        <file>images/loading.gif</file>
-        <file>images/FontAwesome.otf</file>
-        <file>images/icons/ic_chat_black_24dp_2x.png</file>
-        <file>images/icons/ic_person_add_black_24dp_2x.png</file>
-        <file>images/waiting.gif</file>
-        <file>images/default_avatar_overlay.svg</file>
-	<file>images/icons/ic_clear_24px.svg</file>
-	<file>images/icons/ic_block_24px.svg</file>
-	<file>images/icons/ic_phone_24px.svg</file>
-	<file>images/icons/ic_video_call_24px.svg</file>
-	<file>images/icons/ic_arrow_back_24px.svg</file>
-	<file>images/icons/ic_send_24px.svg</file>
-	<file>images/icons/round-settings-24px.svg</file>	
-    </qresource>
+  <qresource prefix="/">
+    <file>stylesheet.css</file>
+    <file>images/ring.png</file>
+    <file>images/logo-ring-standard-coul.png</file>
+    <file>images/icons/ic_arrow_back_white_24dp.png</file>
+    <file>images/icons/ic_mic_off_white_24dp.png</file>
+    <file>images/icons/ic_person_add_white_24dp.png</file>
+    <file>images/icons/ic_search_black_18dp_2x.png</file>
+    <file>images/icons/ic_content_copy_white_24dp.png</file>
+    <file>images/icons/ic_send_white_24dp.png</file>
+    <file>images/icons/ic_group_add_white_24dp.png</file>
+    <file>images/icons/ic_videocam_off_white_24dp.png</file>
+    <file>images/icons/ic_high_quality_white_24dp.png</file>
+    <file>images/icons/ic_pause_white_24dp.png</file>
+    <file>images/icons/ic_chat_white_24dp.png</file>
+    <file>images/icons/ic_done_white_24dp.png</file>
+    <file>images/icons/ic_close_white_24dp.png</file>
+    <file>images/icons/ic_call_transfer_white_24px.png</file>
+    <file>images/icons/ic_pause_white_100px.png</file>
+    <file>images/icons/ic_videocam_white.png</file>
+    <file>images/icons/ic_add_black_18dp_2x.png</file>
+    <file>images/icons/ic_delete_black_18dp_2x.png</file>
+    <file>images/icons/ic_arrow_drop_down_black_18dp_2x.png</file>
+    <file>images/icons/ic_arrow_drop_up_black_18dp_2x.png</file>
+    <file>images/icons/ic_check_white_18dp_2x.png</file>
+    <file>images/icons/ic_folder_black_18dp_2x.png</file>
+    <file>images/icons/ic_arrow_forward_white_48dp_2x.png</file>
+    <file>images/icons/ic_settings_white_48dp_2x.png</file>
+    <file>images/icons/ic_voicemail_white_24dp_2x.png</file>
+    <file>images/background-light.png</file>
+    <file>images/background-dark.png</file>
+    <file>images/icons/ic_arrow_drop_down_black_9dp_2x.png</file>
+    <file>images/icons/ic_arrow_drop_up_black_9dp_2x.png</file>
+    <file>images/icons/ic_arrow_tab_next_black_9dp_2x.png</file>
+    <file>images/icons/ic_arrow_tab_previous_black_9dp_2x.png</file>
+    <file>images/spikeMask.png</file>
+    <file>images/icons/ic_photo_camera_white_24dp_2x.png</file>
+    <file>images/qrcode.png</file>
+    <file>images/icons/ic_share_black_48dp_2x.png</file>
+    <file>images/loading.gif</file>
+    <file>images/FontAwesome.otf</file>
+    <file>images/icons/ic_chat_black_24dp_2x.png</file>
+    <file>images/icons/ic_person_add_black_24dp_2x.png</file>
+    <file>images/waiting.gif</file>
+    <file>images/default_avatar_overlay.svg</file>
+    <file>images/icons/ic_clear_24px.svg</file>
+    <file>images/icons/ic_block_24px.svg</file>
+    <file>images/icons/ic_phone_24px.svg</file>
+    <file>images/icons/ic_video_call_24px.svg</file>
+    <file>images/icons/ic_arrow_back_24px.svg</file>
+    <file>images/icons/ic_send_24px.svg</file>
+    <file>images/icons/round-settings-24px.svg</file>
+    <file>web/chatview.css</file>
+    <file>web/chatview.html</file>
+    <file>web/chatview.js</file>
+    <file>web/linkify-html.js</file>
+    <file>web/linkify-string.js</file>
+    <file>web/linkify.js</file>
+    <file>web/qwebchannel.js</file>
+  </qresource>
 </RCC>
diff --git a/ring-client-windows.sln b/ring-client-windows.sln
index 0eb69bd..9106f8c 100644
--- a/ring-client-windows.sln
+++ b/ring-client-windows.sln
@@ -5,14 +5,14 @@
 MinimumVisualStudioVersion = 10.0.40219.1

 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ring-client-windows", "ring-client-windows.vcxproj", "{0F6318E4-4C06-384E-BCA8-F344DA187957}"

 	ProjectSection(ProjectDependencies) = postProject

-		{103832FC-21E8-3D6C-8C85-21D927092562} = {103832FC-21E8-3D6C-8C85-21D927092562}

+		{A604BA33-C1DB-34F6-8584-C429857717A8} = {A604BA33-C1DB-34F6-8584-C429857717A8}

 	EndProjectSection

 EndProject

 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ring-daemon", "..\daemon\MSVC\ring-daemon.vcxproj", "{79F8DE42-595D-49D9-A66F-55244FD9DCC3}"

 EndProject

-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ringclient_static", "..\lrc\msvc\ringclient_static.vcxproj", "{103832FC-21E8-3D6C-8C85-21D927092562}"

+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ringclient_static", "..\lrc\msvc\ringclient_static.vcxproj", "{A604BA33-C1DB-34F6-8584-C429857717A8}"

 EndProject

-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "qtwrapper", "..\lrc\msvc\src\qtwrapper\qtwrapper.vcxproj", "{75CF859A-E546-32BB-8806-448BDB78A8C4}"

+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "qtwrapper", "..\lrc\msvc\src\qtwrapper\qtwrapper.vcxproj", "{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}"

 	ProjectSection(ProjectDependencies) = postProject

 		{79F8DE42-595D-49D9-A66F-55244FD9DCC3} = {79F8DE42-595D-49D9-A66F-55244FD9DCC3}

 	EndProjectSection

@@ -99,60 +99,60 @@
 		{79F8DE42-595D-49D9-A66F-55244FD9DCC3}.RelWithDebInfo|x64.Build.0 = ReleaseLib|x64

 		{79F8DE42-595D-49D9-A66F-55244FD9DCC3}.RelWithDebInfo|x86.ActiveCfg = ReleaseLib|Win32

 		{79F8DE42-595D-49D9-A66F-55244FD9DCC3}.RelWithDebInfo|x86.Build.0 = ReleaseLib|Win32

-		{103832FC-21E8-3D6C-8C85-21D927092562}.Debug|x64.ActiveCfg = Debug|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.Debug|x64.Build.0 = Debug|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.Debug|x86.ActiveCfg = Debug|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.DebugLib_win32|x64.ActiveCfg = Debug|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.DebugLib_win32|x64.Build.0 = Debug|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.DebugLib_win32|x86.ActiveCfg = MinSizeRel|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.DebugLib_win32|x86.Build.0 = MinSizeRel|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.DebugLib|x64.ActiveCfg = Debug|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.DebugLib|x64.Build.0 = Debug|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.DebugLib|x86.ActiveCfg = MinSizeRel|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.DebugLib|x86.Build.0 = MinSizeRel|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.MinSizeRel|x64.Build.0 = MinSizeRel|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.Release|x64.ActiveCfg = Release|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.Release|x86.ActiveCfg = Release|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.ReleaseLib_win32|x64.ActiveCfg = Release|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.ReleaseLib_win32|x64.Build.0 = Release|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.ReleaseLib_win32|x86.ActiveCfg = MinSizeRel|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.ReleaseLib_win32|x86.Build.0 = MinSizeRel|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.ReleaseLib|x64.ActiveCfg = Release|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.ReleaseLib|x64.Build.0 = Release|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.ReleaseLib|x86.ActiveCfg = MinSizeRel|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.ReleaseLib|x86.Build.0 = MinSizeRel|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64

-		{103832FC-21E8-3D6C-8C85-21D927092562}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.Debug|x64.ActiveCfg = Debug|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.Debug|x64.Build.0 = Debug|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.Debug|x86.ActiveCfg = Debug|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.DebugLib_win32|x64.ActiveCfg = Debug|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.DebugLib_win32|x64.Build.0 = Debug|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.DebugLib_win32|x86.ActiveCfg = MinSizeRel|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.DebugLib_win32|x86.Build.0 = MinSizeRel|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.DebugLib|x64.ActiveCfg = Debug|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.DebugLib|x64.Build.0 = Debug|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.DebugLib|x86.ActiveCfg = MinSizeRel|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.DebugLib|x86.Build.0 = MinSizeRel|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.MinSizeRel|x64.Build.0 = MinSizeRel|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.Release|x64.ActiveCfg = Release|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.Release|x86.ActiveCfg = Release|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.ReleaseLib_win32|x64.ActiveCfg = Release|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.ReleaseLib_win32|x64.Build.0 = Release|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.ReleaseLib_win32|x86.ActiveCfg = MinSizeRel|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.ReleaseLib_win32|x86.Build.0 = MinSizeRel|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.ReleaseLib|x64.ActiveCfg = Release|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.ReleaseLib|x64.Build.0 = Release|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.ReleaseLib|x86.ActiveCfg = MinSizeRel|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.ReleaseLib|x86.Build.0 = MinSizeRel|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64

-		{75CF859A-E546-32BB-8806-448BDB78A8C4}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.Debug|x64.ActiveCfg = Debug|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.Debug|x64.Build.0 = Debug|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.Debug|x86.ActiveCfg = Debug|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.DebugLib_win32|x64.ActiveCfg = Debug|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.DebugLib_win32|x64.Build.0 = Debug|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.DebugLib_win32|x86.ActiveCfg = MinSizeRel|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.DebugLib_win32|x86.Build.0 = MinSizeRel|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.DebugLib|x64.ActiveCfg = Debug|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.DebugLib|x64.Build.0 = Debug|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.DebugLib|x86.ActiveCfg = MinSizeRel|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.DebugLib|x86.Build.0 = MinSizeRel|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.MinSizeRel|x64.Build.0 = MinSizeRel|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.Release|x64.ActiveCfg = Release|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.Release|x86.ActiveCfg = Release|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.ReleaseLib_win32|x64.ActiveCfg = Release|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.ReleaseLib_win32|x64.Build.0 = Release|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.ReleaseLib_win32|x86.ActiveCfg = MinSizeRel|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.ReleaseLib_win32|x86.Build.0 = MinSizeRel|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.ReleaseLib|x64.ActiveCfg = Release|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.ReleaseLib|x64.Build.0 = Release|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.ReleaseLib|x86.ActiveCfg = MinSizeRel|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.ReleaseLib|x86.Build.0 = MinSizeRel|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64

+		{A604BA33-C1DB-34F6-8584-C429857717A8}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.Debug|x64.ActiveCfg = Debug|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.Debug|x64.Build.0 = Debug|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.Debug|x86.ActiveCfg = Debug|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.DebugLib_win32|x64.ActiveCfg = Debug|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.DebugLib_win32|x64.Build.0 = Debug|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.DebugLib_win32|x86.ActiveCfg = MinSizeRel|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.DebugLib_win32|x86.Build.0 = MinSizeRel|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.DebugLib|x64.ActiveCfg = Debug|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.DebugLib|x64.Build.0 = Debug|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.DebugLib|x86.ActiveCfg = MinSizeRel|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.DebugLib|x86.Build.0 = MinSizeRel|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.MinSizeRel|x64.Build.0 = MinSizeRel|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.Release|x64.ActiveCfg = Release|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.Release|x86.ActiveCfg = Release|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.ReleaseLib_win32|x64.ActiveCfg = Release|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.ReleaseLib_win32|x64.Build.0 = Release|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.ReleaseLib_win32|x86.ActiveCfg = MinSizeRel|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.ReleaseLib_win32|x86.Build.0 = MinSizeRel|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.ReleaseLib|x64.ActiveCfg = Release|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.ReleaseLib|x64.Build.0 = Release|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.ReleaseLib|x86.ActiveCfg = MinSizeRel|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.ReleaseLib|x86.Build.0 = MinSizeRel|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64

+		{8C3AD0D4-A52C-3EFB-A2C6-04EA3AB5F00E}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64

 	EndGlobalSection

 	GlobalSection(SolutionProperties) = preSolution

 		HideSolutionNode = FALSE

diff --git a/ring-client-windows.vcxproj b/ring-client-windows.vcxproj
index 7217d11..6019a7f 100644
--- a/ring-client-windows.vcxproj
+++ b/ring-client-windows.vcxproj
@@ -1,6 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>

 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

   <ItemGroup Label="ProjectConfigurations">

+    <ProjectConfiguration Include="ReleaseCompile|x64">

+      <Configuration>ReleaseCompile</Configuration>

+      <Platform>x64</Platform>

+    </ProjectConfiguration>

     <ProjectConfiguration Include="Release|x64">

       <Configuration>Release</Configuration>

       <Platform>x64</Platform>

@@ -22,6 +26,15 @@
     <IntermediateDirectory>release\</IntermediateDirectory>

     <PrimaryOutput>Ring</PrimaryOutput>

   </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'" Label="Configuration">

+    <PlatformToolset>v141</PlatformToolset>

+    <OutputDirectory>release\</OutputDirectory>

+    <ATLMinimizesCRunTimeLibraryUsage>false</ATLMinimizesCRunTimeLibraryUsage>

+    <CharacterSet>NotSet</CharacterSet>

+    <ConfigurationType>StaticLibrary</ConfigurationType>

+    <IntermediateDirectory>release\</IntermediateDirectory>

+    <PrimaryOutput>Ring</PrimaryOutput>

+  </PropertyGroup>

   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />

   <PropertyGroup Condition="'$(QtMsBuild)'=='' or !Exists('$(QtMsBuild)\qt.targets')">

     <QtMsBuild>$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild>

@@ -36,17 +49,28 @@
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">

     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />

   </ImportGroup>

+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'" Label="PropertySheets">

+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />

+  </ImportGroup>

   <PropertyGroup Label="UserMacros" />

   <PropertyGroup>

     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\</OutDir>

+    <OutDir Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">$(Platform)\$(Configuration)\</OutDir>

     <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\</IntDir>

+    <IntDir Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">$(Platform)\$(Configuration)\</IntDir>

     <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Ring</TargetName>

+    <TargetName Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">Ring</TargetName>

     <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</IgnoreImportLibrary>

+    <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">true</IgnoreImportLibrary>

     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>

+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">false</LinkIncremental>

+  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">

+    <TargetExt>.lib</TargetExt>

   </PropertyGroup>

   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">

     <ClCompile>

-      <AdditionalIncludeDirectories>.\GeneratedFiles\$(ConfigurationName);.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

+      <AdditionalIncludeDirectories>.\GeneratedFiles\$(ConfigurationName);.\GeneratedFiles;.;$(ProjectDir)..\ring-daemon\contrib\msvc\include;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\ring-lrc\src;$(ProjectDir)..\lrc\src;$(ProjectDir)winsparkle\include;$(ProjectDir)qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtWebEngineWidgets;$(QTDIR)\include\QtWebChannel;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

       <AdditionalOptions>-Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>

       <AssemblerListingLocation>release\</AssemblerListingLocation>

       <BrowseInformation>false</BrowseInformation>

@@ -55,7 +79,7 @@
       <ExceptionHandling>Sync</ExceptionHandling>

       <ObjectFileName>$(IntDir)</ObjectFileName>

       <Optimization>MaxSpeed</Optimization>

-      <PreprocessorDefinitions>_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>

+      <PreprocessorDefinitions>_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>

       <PreprocessToFile>false</PreprocessToFile>

       <ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>

       <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>

@@ -68,7 +92,82 @@
       <FunctionLevelLinking>true</FunctionLevelLinking>

     </ClCompile>

     <Link>

-      <AdditionalDependencies>..\daemon\MSVC\x64\ReleaseLib_win32\bin\dring.lib;..\lrc\msvc\src\qtwrapper\Release\qtwrapper.lib;..\lrc\msvc\release\ringclient_static.lib;.\winsparkle\x64\release\WinSparkle.lib;.\qrencode-win32\qrencode-win32\vc8\qrcodelib\x64\Release-Lib\qrcodelib.lib;shell32.lib;Ole32.lib;Advapi32.lib;Shlwapi.lib;User32.lib;Gdi32.lib;Crypt32.lib;Strmiids.lib;$(QTDIR)\lib\qtmain.lib;$(QTDIR)\lib\Qt5Svg.lib;$(QTDIR)\lib\Qt5Widgets.lib;$(QTDIR)\lib\Qt5WinExtras.lib;$(QTDIR)\lib\Qt5Gui.lib;$(QTDIR)\lib\Qt5Xml.lib;$(QTDIR)\lib\Qt5Network.lib;$(QTDIR)\lib\Qt5Core.lib;$(QTDIR)\lib\Qt5Sql.lib;%(AdditionalDependencies)</AdditionalDependencies>

+      <AdditionalDependencies>dring.lib;qtwrapper.lib;ringclient_static.lib;WinSparkle.lib;qrcodelib.lib;shell32.lib;Ole32.lib;Advapi32.lib;Shlwapi.lib;User32.lib;Gdi32.lib;Crypt32.lib;Strmiids.lib;qtmain.lib;Qt5Svg.lib;Qt5Widgets.lib;Qt5WinExtras.lib;Qt5Gui.lib;Qt5Xml.lib;Qt5Network.lib;Qt5Core.lib;$(QTDIR)\lib\Qt5Sql.lib;$(QTDIR)\lib\Qt5WebEngineWidgets.lib;$(QTDIR)\lib\Qt5WebChannel.lib;%(AdditionalDependencies)</AdditionalDependencies>

+      <AdditionalLibraryDirectories>$(QTDIR)\lib;..\ring-daemon\contrib\msvc\lib\x64;..\daemon\contrib\msvc\lib\x64;..\ring-daemon\MSVC\x64\ReleaseLib_win32\bin;..\daemon\MSVC\x64\ReleaseLib_win32\bin;..\ring-lrc\msvc\release;..\lrc\msvc\release;..\ring-lrc\msvc\src\qtwrapper\Release;..\lrc\msvc\src\qtwrapper\Release;.\winsparkle\x64\release;.\qrencode-win32\qrencode-win32\vc8\qrcodelib\x64\Release-Lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>

+      <AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /ignore:4006,4049,4078,4098 /LTCG /NODEFAULTLIB:LIBCMT %(AdditionalOptions)</AdditionalOptions>

+      <DataExecutionPrevention>true</DataExecutionPrevention>

+      <GenerateDebugInformation>true</GenerateDebugInformation>

+      <IgnoreImportLibrary>true</IgnoreImportLibrary>

+      <LinkIncremental>false</LinkIncremental>

+      <OutputFile>$(OutDir)\Ring.exe</OutputFile>

+      <RandomizedBaseAddress>true</RandomizedBaseAddress>

+      <SubSystem>Windows</SubSystem>

+      <SuppressStartupBanner>true</SuppressStartupBanner>

+      <Version>2.0</Version>

+      <ForceFileOutput>MultiplyDefinedSymbolOnly</ForceFileOutput>

+    </Link>

+    <Midl>

+      <DefaultCharType>Unsigned</DefaultCharType>

+      <EnableErrorChecks>None</EnableErrorChecks>

+      <WarningLevel>0</WarningLevel>

+    </Midl>

+    <ResourceCompile>

+      <PreprocessorDefinitions>_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;QT_SVG_LIB;QT_WIDGETS_LIB;QT_WINEXTRAS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_NETWORK_LIB;QT_CORE_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>

+    </ResourceCompile>

+    <QtMoc>

+      <QTDIR>$(QTDIR)</QTDIR>

+      <OutputFile>.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</OutputFile>

+      <Define>_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG;%(PreprocessorDefinitions)</Define>

+      <CompilerFlavor>msvc</CompilerFlavor>

+      <Include>$(Configuration)/moc_predefs.h</Include>

+      <ExecutionDescription>Moc'ing %(Identity)...</ExecutionDescription>

+      <InputFile>%(FullPath)</InputFile>

+      <DynamicSource>output</DynamicSource>

+      <IncludePath>.\GeneratedFiles\$(ConfigurationName);.\GeneratedFiles;.;$(ProjectDir)..\ring-daemon\contrib\msvc\include;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\ring-lrc\src;$(ProjectDir)..\lrc\src;$(ProjectDir)winsparkle\include;$(ProjectDir)qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtWebEngineWidgets;$(QTDIR)\include\QtWebChannel;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release;%(AdditionalIncludeDirectories)</IncludePath>

+    </QtMoc>

+    <QtRcc>

+      <InitFuncName>ressources</InitFuncName>

+      <OutputFile>.\GeneratedFiles\qrc_%(Filename).cpp</OutputFile>

+      <QTDIR>$(QTDIR)</QTDIR>

+      <ExecutionDescription>Rcc'ing %(Identity)...</ExecutionDescription>

+      <Compression>default</Compression>

+      <InputFile>%(FullPath)</InputFile>

+    </QtRcc>

+    <QtUic>

+      <InputFile>%(FullPath)</InputFile>

+      <ExecutionDescription>Uic'ing %(Identity)...</ExecutionDescription>

+      <OutputFile>.\GeneratedFiles\ui_%(Filename).h</OutputFile>

+      <QTDIR>$(QTDIR)</QTDIR>

+    </QtUic>

+    <PreBuildEvent>

+      <Command>call touch_res.bat</Command>

+    </PreBuildEvent>

+  </ItemDefinitionGroup>

+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">

+    <ClCompile>

+      <AdditionalIncludeDirectories>.\GeneratedFiles\$(ConfigurationName);.\GeneratedFiles;.;$(ProjectDir)..\ring-daemon\contrib\msvc\include;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\ring-lrc\src;$(ProjectDir)..\lrc\src;$(ProjectDir)winsparkle\include;$(ProjectDir)qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtWebEngineWidgets;$(QTDIR)\include\QtWebChannel;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

+      <AdditionalOptions>-Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>

+      <AssemblerListingLocation>release\</AssemblerListingLocation>

+      <BrowseInformation>false</BrowseInformation>

+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>

+      <DisableSpecificWarnings>4068;4099;4189;4267;4577;4467;4715;%(DisableSpecificWarnings)</DisableSpecificWarnings>

+      <ExceptionHandling>Sync</ExceptionHandling>

+      <ObjectFileName>$(IntDir)</ObjectFileName>

+      <Optimization>MaxSpeed</Optimization>

+      <PreprocessorDefinitions>COMPILE_ONLY;_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>

+      <PreprocessToFile>false</PreprocessToFile>

+      <ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>

+      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>

+      <SuppressStartupBanner>true</SuppressStartupBanner>

+      <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>

+      <WarningLevel>Level3</WarningLevel>

+      <MultiProcessorCompilation>true</MultiProcessorCompilation>

+      <SDLCheck>true</SDLCheck>

+      <IntrinsicFunctions>true</IntrinsicFunctions>

+      <FunctionLevelLinking>true</FunctionLevelLinking>

+    </ClCompile>

+    <Link>

+      <AdditionalDependencies>..\daemon\MSVC\x64\ReleaseLib_win32\bin\dring.lib;..\lrc\msvc\src\qtwrapper\Release\qtwrapper.lib;..\lrc\msvc\release\ringclient_static.lib;.\winsparkle\x64\release\WinSparkle.lib;.\qrencode-win32\qrencode-win32\vc8\qrcodelib\x64\Release-Lib\qrcodelib.lib;shell32.lib;Ole32.lib;Advapi32.lib;Shlwapi.lib;User32.lib;Gdi32.lib;Crypt32.lib;Strmiids.lib;$(QTDIR)\lib\qtmain.lib;$(QTDIR)\lib\Qt5Svg.lib;$(QTDIR)\lib\Qt5Widgets.lib;$(QTDIR)\lib\Qt5WinExtras.lib;$(QTDIR)\lib\Qt5Gui.lib;$(QTDIR)\lib\Qt5Xml.lib;$(QTDIR)\lib\Qt5Network.lib;$(QTDIR)\lib\Qt5Core.lib;$(QTDIR)\lib\Qt5Sql.lib;$(QTDIR)\lib\Qt5WebEngineWidgets.lib;$(QTDIR)\lib\Qt5WebChannel.lib;%(AdditionalDependencies)</AdditionalDependencies>

       <AdditionalLibraryDirectories>$(QTDIR)\lib;..\daemon\contrib\msvc\lib\x64;..\daemon\MSVC\x64\ReleaseLib_win32\bin;..\lrc\msvc\release;.\winsparkle\x64\release;.\qrencode-win32\qrencode-win32\vc8\qrcodelib\x64\Release-Lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>

       <AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /ignore:4006,4049,4078,4098 /LTCG /NODEFAULTLIB:LIBCMT %(AdditionalOptions)</AdditionalOptions>

       <DataExecutionPrevention>true</DataExecutionPrevention>

@@ -93,13 +192,13 @@
     <QtMoc>

       <QTDIR>$(QTDIR)</QTDIR>

       <OutputFile>.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</OutputFile>

-      <Define>_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG;%(PreprocessorDefinitions)</Define>

+      <Define>COMPILE_ONLY;_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG;%(PreprocessorDefinitions)</Define>

       <CompilerFlavor>msvc</CompilerFlavor>

       <Include>$(Configuration)/moc_predefs.h</Include>

       <ExecutionDescription>Moc'ing %(Identity)...</ExecutionDescription>

       <InputFile>%(FullPath)</InputFile>

       <DynamicSource>output</DynamicSource>

-      <IncludePath>.\GeneratedFiles\$(ConfigurationName);.\GeneratedFiles;.;$(ProjectDir)../daemon/contrib/msvc/include;$(ProjectDir)../lrc/src;$(ProjectDir)../client-windows/winsparkle/include;$(ProjectDir)../client-windows/qrencode-win32/qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;release;%(AdditionalIncludeDirectories)</IncludePath>

+      <IncludePath>.\GeneratedFiles\$(ConfigurationName);.\GeneratedFiles;.;$(ProjectDir)..\ring-daemon\contrib\msvc\include;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\ring-lrc\src;$(ProjectDir)..\lrc\src;$(ProjectDir)winsparkle\include;$(ProjectDir)qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtWebEngineWidgets;$(QTDIR)\include\QtWebChannel;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release;%(AdditionalIncludeDirectories)</IncludePath>

     </QtMoc>

     <QtRcc>

       <InitFuncName>ressources</InitFuncName>

@@ -115,9 +214,12 @@
       <OutputFile>.\GeneratedFiles\ui_%(Filename).h</OutputFile>

       <QTDIR>$(QTDIR)</QTDIR>

     </QtUic>

+    <PreBuildEvent>

+      <Command>call touch_res.bat</Command>

+    </PreBuildEvent>

   </ItemDefinitionGroup>

   <ItemGroup>

-    <ClCompile Include="conversationfilterbutton.cpp" />

+    <ClCompile Include="animationhelpers.cpp" />

     <ClCompile Include="currentaccountcombobox.cpp" />

     <ClCompile Include="aboutdialog.cpp" />

     <ClCompile Include="accountdetails.cpp" />

@@ -129,9 +231,13 @@
     <ClCompile Include="callutilsdialog.cpp" />

     <ClCompile Include="callwidget.cpp">

       <OutputFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\%(Filename).moc</OutputFile>

+      <OutputFile Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\%(Filename).moc</OutputFile>

       <DynamicSource Condition="'$(Configuration)|$(Platform)'=='Release|x64'">input</DynamicSource>

+      <DynamicSource Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">input</DynamicSource>

       <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

       <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

+      <Define Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

     </ClCompile>

     <ClCompile Include="configurationwidget.cpp" />

     <ClCompile Include="contactpicker.cpp" />

@@ -139,12 +245,11 @@
     <ClCompile Include="deleteaccountdialog.cpp" />

     <ClCompile Include="globalsystemtray.cpp" />

     <ClCompile Include="idlabel.cpp" />

-    <ClCompile Include="imdelegate.cpp" />

-    <ClCompile Include="instantmessagingwidget.cpp" />

     <ClCompile Include="invitebuttonswidget.cpp" />

     <ClCompile Include="main.cpp" />

     <ClCompile Include="mainwindow.cpp" />

-    <ClCompile Include="messagemodel.cpp" />

+    <ClCompile Include="messagewebpage.cpp" />

+    <ClCompile Include="messagewebview.cpp" />

     <ClCompile Include="navwidget.cpp" />

     <ClCompile Include="photoboothdialog.cpp" />

     <ClCompile Include="photoboothwidget.cpp" />

@@ -156,9 +261,13 @@
     <ClCompile Include="sendcontactrequestwidget.cpp" />

     <ClCompile Include="conversationsfilterwidget.cpp">

       <OutputFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\%(Filename).moc</OutputFile>

+      <OutputFile Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\%(Filename).moc</OutputFile>

       <DynamicSource Condition="'$(Configuration)|$(Platform)'=='Release|x64'">input</DynamicSource>

+      <DynamicSource Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">input</DynamicSource>

       <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

       <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

+      <Define Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

     </ClCompile>

     <ClCompile Include="smartlistselectorbuttonnotifier.cpp" />

     <ClCompile Include="smartlistview.cpp" />

@@ -168,6 +277,7 @@
     <ClCompile Include="videooverlay.cpp" />

     <ClCompile Include="videoview.cpp" />

     <ClCompile Include="videowidget.cpp" />

+    <ClCompile Include="webchathelpers.cpp" />

     <ClCompile Include="windowscontactbackend.cpp" />

     <ClCompile Include="wizarddialog.cpp" />

     <ClCompile Include="wizardwidget.cpp" />

@@ -175,7 +285,9 @@
   <ItemGroup>

     <QtMoc Include="wizardwidget.h">

       <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

       <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

+      <Define Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

     </QtMoc>

     <QtMoc Include="aboutdialog.h">

     </QtMoc>

@@ -198,11 +310,15 @@
     </QtMoc>

     <QtMoc Include="accountlistmodel.h">

       <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

       <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG;QT_SVG_LIB;QT_WIDGETS_LIB;QT_WINEXTRAS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_NETWORK_LIB;QT_CORE_LIB</Define>

+      <Define Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG;QT_SVG_LIB;QT_WIDGETS_LIB;QT_WINEXTRAS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_NETWORK_LIB;QT_CORE_LIB</Define>

     </QtMoc>

     <QtMoc Include="accountitemdelegate.h">

       <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

       <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG;QT_SVG_LIB;QT_WIDGETS_LIB;QT_WINEXTRAS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_NETWORK_LIB;QT_CORE_LIB</Define>

+      <Define Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG;QT_SVG_LIB;QT_WIDGETS_LIB;QT_WINEXTRAS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_NETWORK_LIB;QT_CORE_LIB</Define>

     </QtMoc>

     <QtMoc Include="contactrequestwidget.h">

     </QtMoc>

@@ -212,10 +328,6 @@
     </QtMoc>

     <QtMoc Include="idlabel.h">

     </QtMoc>

-    <QtMoc Include="imdelegate.h">

-    </QtMoc>

-    <QtMoc Include="instantmessagingwidget.h">

-    </QtMoc>

     <QtMoc Include="mainwindow.h">

     </QtMoc>

     <QtMoc Include="navwidget.h">

@@ -226,20 +338,34 @@
     </QtMoc>

     <QtMoc Include="invitebuttonswidget.h">

       <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

       <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

+      <Define Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

     </QtMoc>

     <QtMoc Include="currentaccountcombobox.h">

       <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

       <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

+      <Define Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

     </QtMoc>

-    <QtMoc Include="conversationfilterbutton.h">

-      <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+    <QtMoc Include="animationhelpers.h">

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtWebEngineWidgets;$(QTDIR)\include\QtWebChannel;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtWebEngineWidgets;$(QTDIR)\include\QtWebChannel;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

       <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

+      <Define Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

     </QtMoc>

     <ClInclude Include="lrcinstance.h" />

-    <QtMoc Include="messagemodel.h">

-      <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

-      <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG;QT_SVG_LIB;QT_WIDGETS_LIB;QT_WINEXTRAS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_NETWORK_LIB;QT_CORE_LIB</Define>

+    <QtMoc Include="messagewebview.h">

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtWebEngineWidgets;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtWebEngineWidgets;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

+      <Define Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

+    </QtMoc>

+    <QtMoc Include="messagewebpage.h">

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtWebEngineWidgets;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtWebEngineWidgets;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

+      <Define Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

     </QtMoc>

     <ClInclude Include="pixbufmanipulator.h" />

     <QtMoc Include="qualitydialog.h">

@@ -256,23 +382,33 @@
     <ClInclude Include="settingskey.h" />

     <QtMoc Include="smartlistview.h">

       <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

       <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG;QT_SVG_LIB;QT_WIDGETS_LIB;QT_WINEXTRAS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_NETWORK_LIB;QT_CORE_LIB</Define>

+      <Define Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG;QT_SVG_LIB;QT_WIDGETS_LIB;QT_WINEXTRAS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_NETWORK_LIB;QT_CORE_LIB</Define>

     </QtMoc>

     <QtMoc Include="conversationitemdelegate.h">

       <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

       <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

+      <Define Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

     </QtMoc>

     <QtMoc Include="smartlistmodel.h">

       <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

       <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG;QT_SVG_LIB;QT_WIDGETS_LIB;QT_WINEXTRAS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_NETWORK_LIB;QT_CORE_LIB</Define>

+      <Define Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG;QT_SVG_LIB;QT_WIDGETS_LIB;QT_WINEXTRAS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_NETWORK_LIB;QT_CORE_LIB</Define>

     </QtMoc>

     <QtMoc Include="conversationsfilterwidget.h">

       <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

       <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

+      <Define Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

     </QtMoc>

     <QtMoc Include="smartlistselectorbuttonnotifier.h">

       <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

+      <IncludePath Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">.\GeneratedFiles\$(ConfigurationName)\.;.\GeneratedFiles;.;$(ProjectDir)..\daemon\contrib\msvc\include;$(ProjectDir)..\lrc\src;$(ProjectDir)..\client-windows\winsparkle\include;$(ProjectDir)..\client-windows\qrencode-win32\qrencode-win32;$(QTDIR)\include;$(QTDIR)\include\QtSvg;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtXml;$(QTDIR)\include\QtNetwork;$(QTDIR)\include\QtCore;$(QTDIR)\mkspecs\win32-msvc;.\release</IncludePath>

       <Define Condition="'$(Configuration)|$(Platform)'=='Release|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

+      <Define Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">_WINDOWS;UNICODE;_UNICODE;WIN32;WIN64;NIGHTLY_VERSION=20180706;ENABLE_AUTOUPDATE;QT_NO_DEBUG;NDEBUG</Define>

     </QtMoc>

     <ClInclude Include="utils.h" />

     <QtMoc Include="videooverlay.h">

@@ -281,6 +417,8 @@
     </QtMoc>

     <QtMoc Include="videowidget.h">

     </QtMoc>

+    <ClInclude Include="version.h" />

+    <ClInclude Include="webchathelpers.h" />

     <ClInclude Include="windowscontactbackend.h" />

     <QtMoc Include="wizarddialog.h">

     </QtMoc>

@@ -289,13 +427,18 @@
     <CustomBuild Include="debug\moc_predefs.h.cbt">

       <FileType>Document</FileType>

       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>

+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">true</ExcludedFromBuild>

     </CustomBuild>

     <CustomBuild Include="release\moc_predefs.h.cbt">

       <FileType>Document</FileType>

       <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs)</AdditionalInputs>

+      <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">$(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs)</AdditionalInputs>

       <Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">cl -Bx"$(QTDIR)\bin\qmake.exe" -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -O2 -MD -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E $(QTDIR)\mkspecs\features\data\dummy.cpp 2&gt;NUL &gt;release\moc_predefs.h</Command>

+      <Command Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">cl -Bx"$(QTDIR)\bin\qmake.exe" -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -O2 -MD -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E $(QTDIR)\mkspecs\features\data\dummy.cpp 2&gt;NUL &gt;release\moc_predefs.h</Command>

       <Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Generate moc_predefs.h</Message>

+      <Message Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">Generate moc_predefs.h</Message>

       <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">release\moc_predefs.h;%(Outputs)</Outputs>

+      <Outputs Condition="'$(Configuration)|$(Platform)'=='ReleaseCompile|x64'">release\moc_predefs.h;%(Outputs)</Outputs>

     </CustomBuild>

     <ClInclude Include="ui_aboutdialog.h" />

     <ClInclude Include="ui_accountdetails.h" />

@@ -306,7 +449,6 @@
     <ClInclude Include="ui_contactpicker.h" />

     <ClInclude Include="ui_contactrequestwidget.h" />

     <ClInclude Include="ui_deleteaccountdialog.h" />

-    <ClInclude Include="ui_instantmessagingwidget.h" />

     <ClInclude Include="ui_mainwindow.h" />

     <ClInclude Include="ui_photoboothdialog.h" />

     <ClInclude Include="ui_photoboothwidget.h" />

@@ -371,12 +513,22 @@
     <None Include="translations\ring_client_windows_zh.ts" />

     <None Include="translations\ring_client_windows_zh_CN.ts" />

     <None Include="translations\ring_client_windows_zh_TW.ts" />

+    <None Include="web\chatview.css" />

+    <None Include="web\chatview.html" />

+    <None Include="web\chatview.js" />

+    <None Include="web\linkify-html.js" />

+    <None Include="web\linkify-string.js" />

+    <None Include="web\linkify.js" />

+    <None Include="web\qwebchannel.js" />

   </ItemGroup>

   <ItemGroup>

     <QtUic Include="aboutdialog.ui">

     </QtUic>

     <QtUic Include="accountdetails.ui">

     </QtUic>

+    <QtUic Include="animatedoverlay.ui">

+      <SubType>Designer</SubType>

+    </QtUic>

     <QtUic Include="bannedcontactswidget.ui">

     </QtUic>

     <QtUic Include="callutilsdialog.ui">

@@ -392,8 +544,6 @@
     </QtUic>

     <QtUic Include="deleteaccountdialog.ui">

     </QtUic>

-    <QtUic Include="instantmessagingwidget.ui">

-    </QtUic>

     <QtUic Include="invitebuttonswidget.ui" />

     <QtUic Include="mainwindow.ui">

       <SubType>Designer</SubType>

@@ -462,6 +612,7 @@
     <None Include="images\logo-ring-standard-coul.png" />

     <None Include="images\qrcode.png" />

     <QtRcc Include="ressources.qrc">

+      <SubType>Designer</SubType>

     </QtRcc>

     <None Include="images\ring.png" />

     <None Include="images\spikeMask.png" />

diff --git a/ring-client-windows.vcxproj.filters b/ring-client-windows.vcxproj.filters
index 951dfa2..d2e77d3 100644
--- a/ring-client-windows.vcxproj.filters
+++ b/ring-client-windows.vcxproj.filters
@@ -55,6 +55,9 @@
       <Extensions>ts;xlf</Extensions>

       <ParseFiles>false</ParseFiles>

     </Filter>

+    <Filter Include="Resource Files\web">

+      <UniqueIdentifier>{faf8f8a4-eb67-49df-a082-2fd64189e62c}</UniqueIdentifier>

+    </Filter>

   </ItemGroup>

   <ItemGroup>

     <ClCompile Include="aboutdialog.cpp">

@@ -93,12 +96,6 @@
     <ClCompile Include="idlabel.cpp">

       <Filter>Source Files</Filter>

     </ClCompile>

-    <ClCompile Include="imdelegate.cpp">

-      <Filter>Source Files</Filter>

-    </ClCompile>

-    <ClCompile Include="instantmessagingwidget.cpp">

-      <Filter>Source Files</Filter>

-    </ClCompile>

     <ClCompile Include="main.cpp">

       <Filter>Source Files</Filter>

     </ClCompile>

@@ -156,9 +153,6 @@
     <ClCompile Include="smartlistmodel.cpp">

       <Filter>Source Files</Filter>

     </ClCompile>

-    <ClCompile Include="messagemodel.cpp">

-      <Filter>Source Files</Filter>

-    </ClCompile>

     <ClCompile Include="accountlistmodel.cpp">

       <Filter>Source Files</Filter>

     </ClCompile>

@@ -186,7 +180,16 @@
     <ClCompile Include="currentaccountcombobox.cpp">

       <Filter>Source Files</Filter>

     </ClCompile>

-    <ClCompile Include="conversationfilterbutton.cpp">

+    <ClCompile Include="messagewebview.cpp">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+    <ClCompile Include="messagewebpage.cpp">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+    <ClCompile Include="webchathelpers.cpp">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+    <ClCompile Include="animationhelpers.cpp">

       <Filter>Source Files</Filter>

     </ClCompile>

   </ItemGroup>

@@ -230,12 +233,6 @@
     <QtMoc Include="idlabel.h">

       <Filter>Header Files</Filter>

     </QtMoc>

-    <QtMoc Include="imdelegate.h">

-      <Filter>Header Files</Filter>

-    </QtMoc>

-    <QtMoc Include="instantmessagingwidget.h">

-      <Filter>Header Files</Filter>

-    </QtMoc>

     <QtMoc Include="mainwindow.h">

       <Filter>Header Files</Filter>

     </QtMoc>

@@ -296,9 +293,6 @@
     <QtMoc Include="smartlistview.h">

       <Filter>Header Files</Filter>

     </QtMoc>

-    <QtMoc Include="messagemodel.h">

-      <Filter>Header Files</Filter>

-    </QtMoc>

     <QtMoc Include="accountlistmodel.h">

       <Filter>Header Files</Filter>

     </QtMoc>

@@ -323,7 +317,13 @@
     <QtMoc Include="currentaccountcombobox.h">

       <Filter>Header Files</Filter>

     </QtMoc>

-    <QtMoc Include="conversationfilterbutton.h">

+    <QtMoc Include="messagewebview.h">

+      <Filter>Header Files</Filter>

+    </QtMoc>

+    <QtMoc Include="messagewebpage.h">

+      <Filter>Header Files</Filter>

+    </QtMoc>

+    <QtMoc Include="animationhelpers.h">

       <Filter>Header Files</Filter>

     </QtMoc>

   </ItemGroup>

@@ -361,9 +361,6 @@
     <ClInclude Include="ui_deleteaccountdialog.h">

       <Filter>Generated Files</Filter>

     </ClInclude>

-    <ClInclude Include="ui_instantmessagingwidget.h">

-      <Filter>Generated Files</Filter>

-    </ClInclude>

     <ClInclude Include="ui_mainwindow.h">

       <Filter>Generated Files</Filter>

     </ClInclude>

@@ -552,6 +549,27 @@
     <None Include="translations\ring_client_windows_zh_TW.ts">

       <Filter>Translation Files</Filter>

     </None>

+    <None Include="web\chatview.css">

+      <Filter>Resource Files\web</Filter>

+    </None>

+    <None Include="web\chatview.html">

+      <Filter>Resource Files\web</Filter>

+    </None>

+    <None Include="web\linkify.js">

+      <Filter>Resource Files\web</Filter>

+    </None>

+    <None Include="web\linkify-html.js">

+      <Filter>Resource Files\web</Filter>

+    </None>

+    <None Include="web\linkify-string.js">

+      <Filter>Resource Files\web</Filter>

+    </None>

+    <None Include="web\chatview.js">

+      <Filter>Resource Files\web</Filter>

+    </None>

+    <None Include="web\qwebchannel.js">

+      <Filter>Resource Files\web</Filter>

+    </None>

   </ItemGroup>

   <ItemGroup>

     <QtUic Include="aboutdialog.ui">

@@ -581,9 +599,6 @@
     <QtUic Include="deleteaccountdialog.ui">

       <Filter>Form Files</Filter>

     </QtUic>

-    <QtUic Include="instantmessagingwidget.ui">

-      <Filter>Form Files</Filter>

-    </QtUic>

     <QtUic Include="mainwindow.ui">

       <Filter>Form Files</Filter>

     </QtUic>

@@ -620,6 +635,9 @@
     <QtUic Include="wizardwidget.ui">

       <Filter>Form Files</Filter>

     </QtUic>

+    <QtUic Include="animatedoverlay.ui">

+      <Filter>Form Files</Filter>

+    </QtUic>

   </ItemGroup>

   <ItemGroup>

     <None Include="images\FontAwesome.otf">

@@ -765,5 +783,11 @@
     <ClInclude Include="lrcinstance.h">

       <Filter>Header Files</Filter>

     </ClInclude>

+    <ClInclude Include="webchathelpers.h">

+      <Filter>Header Files</Filter>

+    </ClInclude>

+    <ClInclude Include="version.h">

+      <Filter>Header Files</Filter>

+    </ClInclude>

   </ItemGroup>

 </Project>
\ No newline at end of file
diff --git a/ringthemeutils.h b/ringthemeutils.h
index 8b77e73..4536685 100644
--- a/ringthemeutils.h
+++ b/ringthemeutils.h
@@ -31,6 +31,7 @@
 static const QColor lightRed_ {252, 91, 90};
 static const QColor darkRed_ {219, 55, 54};
 static const QColor notificationRed_{ 255, 59, 48 };
+static const QColor urgentOrange_{ 255, 165, 0 };
 static const QColor green_ {127, 255, 0};
 static const QColor presenceGreen_{ 76, 217, 100 };
 static const QColor smartlistSelection_ { 237, 237, 237 };
diff --git a/settingskey.h b/settingskey.h
index 8a2453a..4290031 100644
--- a/settingskey.h
+++ b/settingskey.h
@@ -26,6 +26,7 @@
 constexpr static char windowState[] = "windowState";
 constexpr static char enableNotifications[] = "enableNotifications";
 constexpr static char selectedAccount[] = "selectedAccount";
+constexpr static char mainSplitterState[] = "mainSplitterState";
 }
 
 #define accountAutoAnswer(A) (A+SettingsKey::autoAnswer)
diff --git a/smartlistmodel.cpp b/smartlistmodel.cpp
index d767a03..be10454 100644
--- a/smartlistmodel.cpp
+++ b/smartlistmodel.cpp
@@ -29,7 +29,6 @@
 
 // Client
 #include "pixbufmanipulator.h"
-#include "messagemodel.h"
 #include "utils.h"
 
 SmartListModel::SmartListModel(const lrc::api::account::Info &acc, QObject *parent)
@@ -95,6 +94,8 @@
             }
             case Role::LastInteraction:
                 return QVariant(QString::fromStdString(item.interactions.at(item.lastMessageUid).body));
+            case Role::LastInteractionType:
+                return QVariant(Utils::toUnderlyingValue(item.interactions.at(item.lastMessageUid).type));
             case Role::ContactType:
             {
                 auto& contact = acc_.contactModel->getContact(item.participants[0]);
@@ -103,7 +104,7 @@
             case Role::UID:
                 return QVariant(QString::fromStdString(item.uid));
             case Role::ContextMenuOpen:
-                return QVariant(isContextMenuOpen_);
+                return QVariant(isContextMenuOpen);
             }
         } catch (...) {}
     }
diff --git a/smartlistmodel.h b/smartlistmodel.h
index 9071599..1da0a65 100644
--- a/smartlistmodel.h
+++ b/smartlistmodel.h
@@ -25,8 +25,6 @@
 #include "api/conversation.h"
 #include "api/contact.h"
 
-#include "messagemodel.h"
-
 namespace lrc { namespace api { class ConversationModel; } }
 
 class SmartListModel : public QAbstractItemModel
@@ -47,6 +45,7 @@
         UnreadMessagesCount,
         LastInteractionDate,
         LastInteraction,
+        LastInteractionType,
         ContactType,
         UID,
         ContextMenuOpen
@@ -62,7 +61,8 @@
     QModelIndex parent(const QModelIndex &child) const;
     Qt::ItemFlags flags(const QModelIndex &index) const;
 
-    bool isContextMenuOpen_{false};
+    // hack for context menu highlight retention
+    bool isContextMenuOpen{ false };
 
 private:
     const AccountInfo& acc_;
diff --git a/smartlistview.cpp b/smartlistview.cpp
index ae44423..4accf05 100644
--- a/smartlistview.cpp
+++ b/smartlistview.cpp
@@ -128,29 +128,17 @@
 }
 
 void
-SmartListView::hideButtonsWidgets()
-{
-    auto model = this->model();
-    if (!model) {
-        return;
-    }
-    for (int i = 0; i < model->rowCount(); ++i) {
-        auto index = model->index(i, 0);
-        if (index.isValid() && indexWidget(index)) {
-            qDebug() << "hide a ButtonsWidgets";
-            indexWidget(index)->setVisible(false);
-        }
-    }
-}
-
-void
 SmartListView::drawRow(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
 {
     if (index == hoveredRow_ && indexWidget(hoveredRow_)) {
         indexWidget(index)->setVisible(true);
-    }
-    else if (indexWidget(index)) {
-        indexWidget(index)->setVisible(false);
+    } else if (indexWidget(index)) {
+        auto type = Utils::toEnum<lrc::api::profile::Type>(
+            index.data(static_cast<int>(SmartListModel::Role::ContactType)).value<int>()
+            );
+        if (type == lrc::api::profile::Type::PENDING) {
+            indexWidget(index)->setVisible(false);
+        }
     }
     QTreeView::drawRow(painter, option, index);
 }
\ No newline at end of file
diff --git a/smartlistview.h b/smartlistview.h
index 480109c..126c1a2 100644
--- a/smartlistview.h
+++ b/smartlistview.h
@@ -21,8 +21,6 @@
 
 #include <QTreeView>
 
-class ConversationItemDelegate;
-
 class SmartListView : public QTreeView
 {
     Q_OBJECT
@@ -38,7 +36,6 @@
     void drawRow(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
 
 private:
-    void hideButtonsWidgets();
     QModelIndex hoveredRow_;
 
 signals:
diff --git a/stylesheet.css b/stylesheet.css
index a1ef20a..63150a8 100644
--- a/stylesheet.css
+++ b/stylesheet.css
@@ -26,7 +26,7 @@
     background-color: #db3c30;
 }
 
-QPushButton#buttonConversations, QPushButton#buttonInvites {
+QPushButton#btnConversations, QPushButton#btnInvites {
     background-color: rgb(242, 242, 242);
     border-style: solid;
     border-width: 0px;
@@ -35,14 +35,18 @@
 	color: rgb(32, 32, 32);
 }
 
-QPushButton#buttonConversations:hover, QPushButton#buttonInvites:hover {
+QPushButton#btnConversations:hover, QPushButton#btnInvites:hover {
     background-color: rgb(237, 237, 237);
 }
 
-QPushButton#buttonConversations:pressed, QPushButton#buttonInvites:pressed {
+QPushButton#btnConversations:pressed, QPushButton#btnInvites:pressed {
     background-color: rgb(212, 212, 212);
 }
 
+QPushButton#btnConversations:checked, QPushButton#btnInvites:checked {
+    background-color: rgb(237, 237, 237);
+}
+
 QPushButton#imBackButton, QPushButton#btnAcceptInvite, QPushButton#btnIgnoreInvite,
 QPushButton#btnBlockInvite, QPushButton#btnAudioCall, QPushButton#btnVideoCall,
 QPushButton#sendContactRequestButton, QPushButton#sendButton, QPushButton#sendIMButton {
@@ -79,12 +83,6 @@
     background-position: bottom;
 }
 
-IdLabel{
-    border-style: solid;
-    border-width: 1px;
-    border-color: rgb(0, 192, 212);
-}
-
 RingContactLineEdit{
     border-color: rgb(242, 242, 242);
     border-radius: 5px;
@@ -163,9 +161,8 @@
     background-color: rgba(242, 242, 242, 255);
 }
 
-QListView#listMessageView{
-    background: rgb(255, 255, 255);
-    border-bottom: 2px solid rgb(240, 240, 240);
+QWidget#sendIMWidget {
+	border-top: 2px solid rgb(240, 240, 240);
 }
 
 QWidget#messagingHeaderWidget{
@@ -175,6 +172,7 @@
 QLineEdit#messageEdit, QLineEdit#imMessageEdit{
     border: none;
     background-color: rgb(255, 255, 255);
+	padding: 0px;
 }
 
 QLineEdit#numberBar{
@@ -248,21 +246,24 @@
 }
 
 QToolButton#qrButton, QToolButton#shareButton{
-    background-color: #3AC0D2;
-    border-radius: 15px;
-    border:solid 1px;
+    background-color: rgb(242, 242, 242);
+    border-style: solid;
+    border-width: 0px;
+    border-radius: 5px;
+    padding: 8px;
+	color: rgb(32, 32, 32);
 }
 
 QToolButton#qrButton:hover, QToolButton#shareButton:hover{
-    background-color: #4dc6d6;
+    background-color: rgb(237, 237, 237);
 }
 
 QToolButton#qrButton:pressed, QToolButton#shareButton:pressed{
-    background-color: #34acbd;
+    background-color: rgb(212, 212, 212);
 }
 
 QToolButton#qrButton:checked {
-    background-color: #34acbd;
+    background-color: rgb(237, 237, 237);
 }
 
 QPushButton#deleteAccountBtn, QToolButton#addAccountButton{
@@ -286,10 +287,7 @@
 
 QDialog#WizardDialog, QWidget#welcomePage, QWidget#sendContactRequestPage,
 QDialog#DeleteAccountDialog, QDialog#DeleteContactDialog{
-    background: rgb(242, 242, 242);
-    background-image : url(:/images/background-light.png);
-    background-repeat : repeat-x;
-    background-position: bottom;
+    background: rgb(255, 255, 255);
 }
 
 QDialog#CallUtilsDialog, QDialog#QualityDialog{
diff --git a/touch_res.bat b/touch_res.bat
new file mode 100644
index 0000000..6b58825
--- /dev/null
+++ b/touch_res.bat
@@ -0,0 +1 @@
+copy ressources.qrc /B+ ,,/Y
\ No newline at end of file
diff --git a/utils.cpp b/utils.cpp
index 56ffb9c..870da11 100644
--- a/utils.cpp
+++ b/utils.cpp
@@ -36,6 +36,7 @@
 #include <QStackedWidget>
 #include <QPropertyAnimation>
 #include <QApplication>
+#include <QFile>
 
 #include "globalinstances.h"
 #include "pixbufmanipulator.h"
@@ -403,3 +404,15 @@
     QVariant var = GlobalInstances::pixmapManipulator().decorationRole(*conversation, accountInfo);
     return var.value<QImage>();
 }
+
+QByteArray
+Utils::QByteArrayFromFile(const QString& filename)
+{
+    QFile file(filename);
+    if (file.open(QIODevice::ReadOnly)) {
+        return file.readAll();
+    } else {
+        qDebug() << "can't open file";
+        return QByteArray();
+    }
+}
\ No newline at end of file
diff --git a/utils.h b/utils.h
index ffa2922..c13abde 100644
--- a/utils.h
+++ b/utils.h
@@ -37,6 +37,8 @@
 #include <QString>
 #include <QImage>
 #include <QStackedWidget>
+#include <QTextDocument>
+#include <QItemDelegate>
 
 #include "api/conversationmodel.h"
 #include "api/account.h"
@@ -74,8 +76,8 @@
     bool isInteractionGenerated(const lrc::api::interaction::Type& interaction);
     bool isContactValid(const std::string& contactUid, const lrc::api::ConversationModel& model);
     QImage conversationPhoto(const std::string& convUid, const lrc::api::account::Info& accountInfo);
+    QByteArray QByteArrayFromFile(const QString& filename);
 
-    // helpers
     template<typename E>
     constexpr inline typename std::enable_if<   std::is_enum<E>::value,
         typename std::underlying_type<E>::type
@@ -104,4 +106,4 @@
         }
         return std::distance(vec.begin(), it);
     }
-}
+}
\ No newline at end of file
diff --git a/version.h b/version.h
new file mode 100644
index 0000000..1ff8dd5
--- /dev/null
+++ b/version.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#define BUILD_YEAR_CH0 (__DATE__[ 7])
+#define BUILD_YEAR_CH1 (__DATE__[ 8])
+#define BUILD_YEAR_CH2 (__DATE__[ 9])
+#define BUILD_YEAR_CH3 (__DATE__[10])
+
+
+#define BUILD_MONTH_IS_JAN (__DATE__[0] == 'J' && __DATE__[1] == 'a' && __DATE__[2] == 'n')
+#define BUILD_MONTH_IS_FEB (__DATE__[0] == 'F')
+#define BUILD_MONTH_IS_MAR (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'r')
+#define BUILD_MONTH_IS_APR (__DATE__[0] == 'A' && __DATE__[1] == 'p')
+#define BUILD_MONTH_IS_MAY (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'y')
+#define BUILD_MONTH_IS_JUN (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'n')
+#define BUILD_MONTH_IS_JUL (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'l')
+#define BUILD_MONTH_IS_AUG (__DATE__[0] == 'A' && __DATE__[1] == 'u')
+#define BUILD_MONTH_IS_SEP (__DATE__[0] == 'S')
+#define BUILD_MONTH_IS_OCT (__DATE__[0] == 'O')
+#define BUILD_MONTH_IS_NOV (__DATE__[0] == 'N')
+#define BUILD_MONTH_IS_DEC (__DATE__[0] == 'D')
+
+
+#define BUILD_MONTH_CH0 \
+    ((BUILD_MONTH_IS_OCT || BUILD_MONTH_IS_NOV || BUILD_MONTH_IS_DEC) ? '1' : '0')
+
+#define BUILD_MONTH_CH1 \
+    ( \
+        (BUILD_MONTH_IS_JAN) ? '1' : \
+        (BUILD_MONTH_IS_FEB) ? '2' : \
+        (BUILD_MONTH_IS_MAR) ? '3' : \
+        (BUILD_MONTH_IS_APR) ? '4' : \
+        (BUILD_MONTH_IS_MAY) ? '5' : \
+        (BUILD_MONTH_IS_JUN) ? '6' : \
+        (BUILD_MONTH_IS_JUL) ? '7' : \
+        (BUILD_MONTH_IS_AUG) ? '8' : \
+        (BUILD_MONTH_IS_SEP) ? '9' : \
+        (BUILD_MONTH_IS_OCT) ? '0' : \
+        (BUILD_MONTH_IS_NOV) ? '1' : \
+        (BUILD_MONTH_IS_DEC) ? '2' : \
+        /* error default */    '?' \
+    )
+
+#define BUILD_DAY_CH0 ((__DATE__[4] >= '0') ? (__DATE__[4]) : '0')
+#define BUILD_DAY_CH1 (__DATE__[ 5])
+
+const char VERSION_STRING[] =
+{
+    BUILD_YEAR_CH0, BUILD_YEAR_CH1, BUILD_YEAR_CH2, BUILD_YEAR_CH3,
+    BUILD_MONTH_CH0, BUILD_MONTH_CH1,
+    BUILD_DAY_CH0, BUILD_DAY_CH1,
+    '\0'
+};
\ No newline at end of file
diff --git a/videoview.cpp b/videoview.cpp
index 5a9c70a..36c0a30 100644
--- a/videoview.cpp
+++ b/videoview.cpp
@@ -217,7 +217,8 @@
 void
 VideoView::toggleFullScreen()
 {
-    overlay_->toggleContextButtons(isFullScreen());
+    qDebug() << "toggle FS";
+    /*overlay_->toggleContextButtons(isFullScreen());
     if(isFullScreen()) {
         dynamic_cast<QSplitter*>(oldParent_)->insertWidget(0,this);
         this->resize(oldSize_.width(), oldSize_.height());
@@ -228,7 +229,7 @@
         this->setParent(0);
         this->showFullScreen();
     }
-    ui->videoWidget->setResetPreview(true);
+    ui->videoWidget->setResetPreview(true);*/
 }
 
 void
diff --git a/web/chatview.css b/web/chatview.css
new file mode 100644
index 0000000..5fa4156
--- /dev/null
+++ b/web/chatview.css
@@ -0,0 +1,921 @@
+/** Variable and font definitions */
+
+:root {
+    /* color definitions */
+    --ring-light-blue: rgba(59, 193, 211, 0.3);
+    /* main properties */
+    /* --bg-color: #f2f2f2; same as macOS client */
+    --bg-color: #ffffff; /* same as macOS client */
+    /* navbar properties */
+    --navbar-height: 40px;
+    --navbar-padding-top: 8px;
+    --navbar-padding-bottom: var(--navbar-padding-top);
+    /* message bar properties */
+    --textarea-max-height: 150px;
+    --placeholder-text-color: #d3d3d3;
+    /* button properties */
+    --action-icon-color: #00;
+    --deactivated-icon-color: #bebebe;
+    --action-icon-hover-color: #ededed;
+    --action-critical-icon-hover-color: rgba(211, 77, 59, 0.3); /* complementary color of ring light blue */
+    --action-critical-icon-press-color: rgba(211, 77, 59, 0.5);
+    --action-critical-icon-color: #4E1300;
+    --non-action-icon-color: #212121;
+    --action-icon-press-color: rgba(212, 212, 212, 1.0);
+    /* hairline properties */
+    --hairline-color: #f0f0f0;
+    --hairline-thickness: 2px;
+}
+
+@font-face {
+    font-family: emoji;
+    /* Fonts for text outside emoji blocks */
+    src: local('Open sans'), local('Helvetica'), local('Segoe UI'), local('sans-serif');
+}
+
+@font-face {
+    font-family: emoji;
+    src: local('Noto Color Emoji'), local('Android Emoji'), local('Twitter Color Emoji');
+    /* Emoji unicode blocks */
+    unicode-range: U+1F300-1F5FF, U+1F600-1F64F, U+1F680-1F6FF, U+2600-26FF;
+}
+
+/** Body */
+
+body {
+    --messagebar-size: 57px;
+    margin: 0;
+    overflow: hidden;
+    background-color: var(--bg-color);
+    padding-bottom: var(--messagebar-size);
+    /* disable selection highlight because it looks very bad */
+    -webkit-user-select: none;
+    opacity: 1;
+    transition: 0.5s opacity;
+}
+
+    body.fade {
+        opacity: 0;
+        transition: none;
+    }
+
+::-webkit-scrollbar-track {
+    background-color: var(--bg-color);
+}
+
+::-webkit-scrollbar {
+    width: 8px;
+    background-color: var(--bg-color);
+}
+
+::-webkit-scrollbar-thumb {
+    background-color: #f0f0f0;
+}
+
+
+/** Navbar */
+
+.navbar-wrapper {
+    /* on top, over everything and full width */
+    position: fixed;
+    left: 0;
+    right: 0;
+    z-index: 500;
+    top: 0;
+}
+
+#navbar {
+    background-color: var(--bg-color);
+    padding-right: 8px;
+    padding-left: 8px;
+    padding-top: var(--navbar-padding-top);
+    padding-bottom: var(--navbar-padding-bottom);
+    height: var(--navbar-height);
+    overflow: hidden;
+    align-items: center;
+    /* takes whole width */
+    left: 0;
+    right: 0;
+    /* hairline */
+    border-bottom: var(--hairline-thickness) solid var(--hairline-color);
+    display: flex;
+}
+
+.hiddenState {
+    /* Used to hide navbar and message bar */
+    display: none !important;
+}
+
+.svgicon {
+    display: block;
+    margin: auto;
+    height: 70%;
+}
+
+.nav-button {
+    width: 30px;
+    height: 30px;
+    margin: 8px;
+    padding: 2px;
+    display: flex;
+    cursor: pointer;
+    align-self: center;
+    border-radius: 9px;
+}
+
+    .nav-button.deactivated {
+        width: 30px;
+        height: 30px;
+        margin: 8px;
+        padding: 2px;
+        align-self: center;
+        display: flex;
+        border-radius: 9px;
+        cursor: auto;
+    }
+
+.action-button svg {
+    fill: var(--action-icon-color);
+}
+
+.action-button.deactivated svg {
+    fill: var(--deactivated-icon-color);
+}
+
+.non-action-button svg {
+    fill: var(--non-action-icon-color);
+}
+
+.non-action-button:hover, .action-button:hover {
+    background: var(--action-icon-hover-color);
+}
+
+.non-action-button:active, .action-button:active {
+    background: var(--action-icon-press-color);
+}
+
+.action-button.deactivated:hover, .action-button.deactivated:active {
+    background: none;
+}
+
+.action-critical-button svg {
+    fill: var(--action-critical-icon-color);
+}
+
+.action-critical-button:hover {
+    background: var(--action-critical-icon-hover-color);
+}
+
+.action-critical-button:active {
+    background: var(--action-critical-icon-press-color);
+}
+
+#callButtons {
+    display: flex;
+}
+
+#navbar #addBannedContactButton, #navbar #addToConversationsButton {
+    display: none;
+}
+
+#navbar.onBannedState #addToConvButton, #navbar.onBannedState #callButtons, #navbar.onBannedState #addToConversationsButton {
+    display: none;
+}
+
+#navbar.onBannedState #addBannedContactButton {
+    display: flex;
+}
+
+/** Invitation bar */
+
+#invitation {
+    visibility: hidden;
+    background: var(--ring-light-blue);
+    position: absolute;
+    width: 100%;
+    /* hairline */
+    border-bottom: var(--hairline-thickness) solid var(--hairline-color);
+}
+
+    #invitation #actions {
+        margin: 10px;
+        list-style: none;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        /* enable selection (it is globally disabled in the body) */
+        -webkit-user-select: auto;
+    }
+
+    #invitation #text h1 {
+        font-size: 1.5em;
+    }
+
+    #invitation #text {
+        text-align: center;
+        width: 90%;
+        margin: auto;
+        margin-top: 10px;
+        font-size: 0.8em;
+        /* enable selection (it is globally disabled in the body) */
+        -webkit-user-select: auto;
+    }
+
+.invitation-button,
+.flat-button {
+    margin: 5px;
+    border: 0;
+    border-radius: 5px;
+    transition: all 0.3s ease;
+    color: #f9f9f9;
+    padding: 10px 20px 10px 20px;
+    vertical-align: middle;
+    cursor: pointer;
+}
+
+.button-green {
+    background: #27ae60;
+}
+
+    .button-green:hover {
+        background: #1f8b4c;
+    }
+
+.button-red {
+    background: #dc3a37;
+}
+
+    .button-red:hover {
+        background: #b02e2c;
+    }
+
+/** Messaging bar */
+
+#sendMessage {
+    background-color: var(--bg-color);
+    display: flex;
+    overflow: hidden;
+    padding: 4px;
+    align-items: center;
+    position: fixed;
+    left: 0;
+    right: 0;
+    z-index: 500;
+    bottom: 0;
+    /* hairline */
+    border-top: var(--hairline-thickness) solid var(--hairline-color);
+}
+
+#message {
+    font-family: emoji;
+    flex: 1;
+    background-color: var(--bg-color);
+    border: 0;
+    overflow-y: scroll;
+    color: black;
+    max-height: var(--textarea-max-height);
+    margin-right: 10px;
+    resize: none;
+    /* enable selection (it is globally disabled in the body) */
+    -webkit-user-select: auto;
+}
+
+    #message:focus,
+    #message.focus {
+        outline: none;
+    }
+
+#container[disabled] {
+    background-color: #ccc;
+}
+
+input[placeholder], [placeholder], *[placeholder] {
+    color: var(--placeholder-text-color);
+}
+
+/** Main chat view */
+
+#lazyloading-icon {
+    margin: auto;
+    margin-bottom: 10px;
+    margin-top: 5px;
+    vertical-align: center;
+    width: 30px;
+    display: flex;
+}
+
+#container {
+    position: relative;
+    height: 100%;
+    width: 100%;
+    margin: 0;
+    padding: 0;
+    display: flex;
+    flex-direction: column;
+    /* When there are not enough messages to occupy full height of the
+       container, make sure they are displayed at the bottom, not at the
+       top. */
+    justify-content: flex-end;
+}
+
+a:link {
+    text-decoration: none;
+    color: #0e649b;
+    transition: all 0.2s ease-in-out;
+    border-bottom: dotted 1px;
+}
+
+a:hover {
+    border: 0;
+}
+
+#messages {
+    position: relative;
+    z-index: 0;
+    width: 100%;
+    overflow: hidden;
+    height: auto;
+    padding-top: 0.5em;
+}
+
+    #messages:hover {
+        overflow-y: overlay;
+    }
+
+.last_message {
+    /* The last message gets a bigger bottom padding so that it is not
+     "glued" to the message bar. */
+    padding-bottom: 1.5em !important;
+}
+
+/* General messages */
+
+.internal_mes_wrapper {
+    max-width: 70%;
+    margin: 8px 0 0 0;
+    display: flex;
+    flex-direction: column;
+    /* If a message is smaller (in width) than the timestamp, do not fill
+       full width and pack message at the left. */
+    align-items: flex-start;
+    align-content: flex-start;
+}
+
+.message_out > .internal_mes_wrapper {
+    /* If message is in the outgoing direction, pack at the right. */
+    align-items: flex-end;
+    align-content: flex-end;
+}
+
+.message_wrapper {
+    max-width: calc(100% - 2em);
+    border-radius: 10px;
+    padding: 0.5em 1em 0.5em 1em;
+    position: relative;
+    display: flex;
+    flex-direction: row;
+}
+
+.message_type_data_transfer .message_wrapper {
+    display: flex;
+    flex-direction: column;
+    padding: 0;
+    width: 450px;
+    max-width: none;
+}
+
+.transfer_info_wrapper {
+    display: flex;
+    flex-direction: row;
+}
+
+.message {
+    font: 0.875em emoji;
+    margin: 0;
+    display: flex;
+    justify-content: flex-start;
+    align-items: top;
+    overflow: hidden;
+    /* enable selection (it is globally disabled in the body) */
+    -webkit-user-select: auto;
+}
+
+.message_in {
+    padding-left: 25%;
+}
+
+.message_out {
+    padding-right: 25%;
+    /* Message sent by the user should be displayed at the right side of
+       the screen. */
+    flex-direction: row-reverse;
+}
+
+.message_delivery_status {
+    margin: 10px 10px;
+    color: #A0A0A0;
+}
+
+.message_sender {
+    display: none;
+}
+
+.sender_image {
+    border-radius: 50%;
+    margin: 8px 10px 0px 10px;
+}
+
+.message_out .message_wrapper {
+    border-top-right-radius: 0;
+    transform-origin: top right;
+}
+
+.message_in .message_wrapper {
+    border-top-left-radius: 0;
+    transform-origin: top left;
+}
+
+.message_out .message_wrapper {
+    background-color: #cfd8dc;
+}
+
+.message_in .message_wrapper {
+    background-color: #cfebf5;
+}
+
+.message_in .sender_image,
+.message_out .sender_image {
+    visibility: hidden;
+}
+
+.message_in.last_of_sequence .sender_image,
+.message_in.single_message .sender_image {
+    visibility: visible;
+}
+
+.message_in.last_of_sequence .sender_image {
+    margin-top: 2px;
+}
+
+.message_in.middle_of_sequence .sender_image {
+    margin-top: 0px;
+}
+
+.generated_message.message_in .message_wrapper,
+.generated_message.message_out .message_wrapper {
+    background-color: transparent !important;
+    border-radius: 0px !important;
+}
+
+.single_message.message_in .message_wrapper,
+.single_message.message_out .message_wrapper {
+    border-radius: 20px 20px 20px 20px !important;
+}
+
+.last_of_sequence.message_in .message_wrapper {
+    border-radius: 4px 20px 20px 20px;
+}
+
+.first_of_sequence.message_in .message_wrapper {
+    border-radius: 20px 20px 20px 4px;
+}
+
+.middle_of_sequence.message_in .message_wrapper {
+    border-radius: 4px 20px 20px 5px;
+}
+
+.last_of_sequence.message_out .message_wrapper {
+    border-radius: 20px 4px 20px 20px;
+}
+
+
+.first_of_sequence.message_out .message_wrapper {
+    border-radius: 20px 20px 4px 20px;
+}
+
+.middle_of_sequence.message_out .message_wrapper {
+    border-radius: 20px 5px 4px 20px;
+}
+
+.middle_of_sequence .internal_mes_wrapper,
+.last_of_sequence .internal_mes_wrapper,
+.last_message .internal_mes_wrapper {
+    margin: 3px 0 0 0 !important;
+}
+
+.message_out .sender_image {
+    margin: 8px;
+}
+
+.first_of_sequence.message_out .internal_mes_wrapper,
+.single_message.message_out .internal_mes_wrapper {
+    margin: 4px 0 0 0 !important;
+}
+
+@-webkit-keyframes fade-in {
+    from {
+        opacity: 0;
+    }
+
+    to {
+        opacity: 1;
+    }
+}
+
+.timestamp {
+    display: inline-flex;
+    justify-content: flex-start;
+    align-self: stretch;
+    color: #333;
+    font-size: 10px;
+    padding: 5px;
+}
+
+.timestamp_out {
+    flex-direction: row-reverse;
+}
+
+.timestamp_action {
+    margin: auto;
+    padding: 0;
+    vertical-align: center;
+    opacity: 0;
+    transition: visibility 0.3s linear, opacity 0.3s linear;
+}
+
+.message_type_contact .message_wrapper:hover .timestamp_action,
+.message_type_call .message_wrapper:hover .timestamp_action {
+    opacity: 1;
+}
+
+/* Ellipsis - dropdown menu */
+
+input[type=checkbox] {
+    display: none;
+}
+
+.menu_interaction {
+    margin: 5px;
+    padding: 10px;
+    padding-top: 0;
+    opacity: 0;
+    height: 20px;
+    transition: visibility 0.3s linear, opacity 0.3s linear;
+}
+
+.message_type_contact .menu_interaction {
+    display: none;
+    visibility: hidden;
+}
+
+.message_type_call .menu_interaction {
+    margin: auto;
+    padding: 0;
+    vertical-align: center;
+}
+
+    .message_type_call .menu_interaction .dropdown {
+        margin-top: -17px;
+    }
+
+.message:hover:not(.message_type_contact) .menu_interaction {
+    display: block;
+    opacity: 1;
+}
+
+.dropdown {
+    display: none;
+    z-index: 1;
+    position: absolute;
+    background-color: #fff;
+    padding-top: 3px;
+    padding-bottom: 3px;
+}
+
+    .dropdown div {
+        color: #111;
+        padding: 10px;
+    }
+
+        .dropdown div:hover {
+            background-color: #ddd;
+        }
+
+.showmenu:checked ~ .dropdown {
+    display: block;
+}
+
+.menuoption {
+    user-select: none;
+    cursor: pointer;
+}
+
+/* Buttons */
+
+.flat-button {
+    flex: 1;
+    padding: 0;
+}
+
+.left_buttons {
+    align-self: center;
+    max-width: 90px;
+    padding-left: 1em;
+}
+
+/* Status */
+
+.status_circle {
+    fill: #A0A0A0;
+    -webkit-animation: circle-dance;
+    -webkit-animation-duration: 0.8s;
+    -webkit-animation-iteration-count: infinite;
+    -webkit-animation-direction: alternate;
+    -webkit-animation-timing-function: ease-in-out;
+}
+
+.anim-first {
+    -webkit-animation-delay: 0.7s;
+}
+
+.anim-second {
+    -webkit-animation-delay: 0.9s;
+}
+
+.anim-third {
+    -webkit-animation-delay: 1.1s;
+}
+
+@-webkit-keyframes circle-dance {
+    0%,50% {
+        -webkit-transform: translateY(0);
+        fill: #A0A0A0;
+    }
+
+    100% {
+        -webkit-transform: translateY(-8px);
+        fill: #000;
+    }
+}
+
+.status-x {
+    stroke-dasharray: 12;
+}
+
+/* Contact + Call interactions */
+.message_type_contact .message_wrapper,
+.message_type_call .message_wrapper {
+    width: auto;
+    margin: auto;
+    display: flex;
+    flex-wrap: wrap;
+    background-color: var(--bg-color);
+    padding: 0;
+}
+
+    .message_type_contact .message_wrapper:before,
+    .message_type_call .message_wrapper:before {
+        display: none;
+    }
+
+.message_type_contact .text,
+.message_type_call .text {
+    align-self: center;
+    font-size: 1.2em;
+    padding: 1em;
+}
+
+/* file interactions */
+
+.message_type_data_transfer .internal_mes_wrapper {
+    padding: 0;
+    display: flex;
+    flex-wrap: wrap;
+}
+
+.accept, .refuse {
+    border-radius: 50%;
+    cursor: pointer;
+}
+
+    .accept svg,
+    .refuse svg {
+        padding: 8px;
+        width: 24px;
+        height: 24px;
+    }
+
+.accept {
+    fill: #219d55;
+}
+
+    .accept:hover {
+        fill: white;
+        background: #219d55;
+    }
+
+.refuse {
+    fill: #dc2719;
+}
+
+    .refuse:hover {
+        fill: white;
+        background: #dc2719;
+    }
+
+.message_type_data_transfer .text {
+    text-align: left;
+    align-self: left;
+    padding: 1em;
+}
+
+.truncate-ellipsis {
+    display: table;
+    table-layout: fixed;
+    width: 100%;
+    white-space: nowrap;
+}
+
+    .truncate-ellipsis > * {
+        display: table-cell;
+        overflow: hidden;
+        text-overflow: ellipsis;
+    }
+
+.message_type_data_transfer .filename {
+    cursor: pointer;
+    font-size: 1.1em;
+}
+
+.message_type_data_transfer .informations {
+    color: #555;
+    font-size: 0.8em;
+}
+
+.message_progress_bar {
+    width: 100%;
+    height: 1em;
+    position: relative;
+    overflow: hidden;
+    background-color: #eee;
+    border-radius: 0;
+    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25) inset;
+}
+
+    .message_progress_bar > span {
+        display: inline;
+        height: 100%;
+        background-color: #01a2b8;
+        position: absolute;
+        overflow: hidden;
+    }
+
+/* text interactions */
+
+.message_type_text .internal_mes_wrapper {
+    padding: 0px;
+}
+
+.message_text {
+    word-break: break-all;
+    word-wrap: hyphenate;
+    max-width: 100%;
+}
+
+    .message_text pre {
+        display: inline;
+    }
+
+pre {
+    font: inherit;
+    font-family: inherit;
+    font-size: inherit;
+    font-style: inherit;
+    font-variant: inherit;
+    font-weight: inherit;
+    margin: 0;
+    padding: 0;
+    white-space: pre-wrap;
+}
+
+/* Media interactions */
+.media_wrapper img {
+    max-width: 800px;
+    max-height: 700px;
+    margin: 5px 0 0 0;
+    border-radius: 10px;
+}
+
+.playVideo {
+    background-color: rgba(0, 0, 0, 0.6);
+    height: 50px;
+    width: 50px;
+    border-radius: 5px;
+    float: right;
+    position: absolute;
+    top: calc(50% - 25px);
+    left: calc(50% - 25px);
+    z-index: 3;
+}
+
+.containerVideo {
+    width: 100%;
+    position: relative;
+}
+
+.playVideo svg {
+    height: 40px;
+    width: 40px;
+    margin: 5px;
+}
+
+/* Text interaction */
+.failure,
+.sending {
+    margin: 10px 10px;
+    color: #A0A0A0;
+}
+
+/* classic screens */
+@media screen and (max-width: 1920px), screen and (max-height: 1080px) {
+    .message_in {
+        padding-left: 15%;
+    }
+
+    .message_out {
+        padding-right: 15%;
+    }
+
+    .internal_mes_wrapper {
+        max-width: 60%;
+    }
+
+    .media_wrapper img {
+        /* It is perfectly fine to specify max-widths in px when the
+         wrapper's max-width is in %, as long as the max-width in px
+         doesn't exceed the one in %. */
+        max-width: 450px;
+        max-height: 450px;
+    }
+
+    .menu_interaction {
+        margin: 5px;
+        padding: 2px;
+        height: 10px;
+        font-size: 0.7em;
+        transition: visibility 0.3s linear,opacity 0.3s linear;
+    }
+}
+
+/* lower resolutions */
+@media screen and (max-width: 1000px), screen and (max-height: 480px) {
+    .message_in {
+        padding-left: 0;
+    }
+
+    .message_out {
+        padding-right: 0;
+    }
+
+    .message_type_contact,
+    .message_type_call {
+        max-width: 100%;
+    }
+
+    .internal_mes_wrapper {
+        max-width: 90%;
+    }
+
+    /* Media interactions */
+    .media_wrapper img {
+        max-width: 200px;
+        max-height: 200px;
+    }
+}
+
+@media screen and (max-width: 550px) {
+    .message_type_data_transfer .message_wrapper {
+        width: 250px;
+    }
+}
+
+/* Special case */
+@media screen and (max-width: 350px) {
+    .sender_image {
+        display: none;
+    }
+
+    /* File interactions */
+    .message_type_data_transfer .left_buttons {
+        max-width: 100%;
+    }
+
+    .message_type_data_transfer .text {
+        max-width: 100%;
+        padding-left: 0;
+    }
+
+    .message_type_data_transfer .message_wrapper {
+        width: 200px;
+    }
+}
diff --git a/web/chatview.html b/web/chatview.html
new file mode 100644
index 0000000..43fbb18
--- /dev/null
+++ b/web/chatview.html
@@ -0,0 +1,43 @@
+<html>
+<!-- Empty head might be needed for setSenderImage -->
+<head>
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta charset="utf-8">
+    <!--<link rel="stylesheet" href="chatview.css">
+    <script type="text/javascript" src="linkify.js"></script>
+    <script type="text/javascript" src="linkify-html.js"></script>
+    <script type="text/javascript" src="linkify-string.js"></script>
+    <script type="text/javascript" src="qwebchannel.js"></script>-->
+</head>
+<body>
+    <div class="navbar-wrapper">
+        <div id="invitation">
+            <div id="text">
+            </div>
+            <div id="actions">
+                <div id="accept-btn" class="invitation-button button-green" onclick="acceptInvitation()">Accept</div>
+                <div id="refuse-btn" class="invitation-button button-red" onclick="refuseInvitation()">Refuse</div>
+                <div id="block-btn" class="invitation-button button-red" onclick="blockConversation()">Block</div>
+            </div>
+        </div>
+    </div>
+    <div id="container">
+        <div id="messages" onscroll="onScrolled()"></div>
+        <div id="sendMessage">
+            <div class="nav-button action-button" onclick="sendFile()" title="Send File">
+                <svg class="svgicon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+                    <path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" />
+                    <path d="M0 0h24v24H0z" fill="none" />
+                </svg>
+            </div>
+            <textarea id="message" autofocus placeholder="Type a message" onkeyup="grow_text_area()" onkeydown="process_messagebar_keydown()" rows="1"></textarea>
+            <div class="nav-button action-button" onclick="sendMessage(); grow_text_area()" title="Send">
+                <svg class="svgicon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+                    <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
+                    <path d="M0 0h24v24H0z" fill="none" />
+                </svg>
+            </div>
+        </div>
+    </div>
+</body>
+</html>
diff --git a/web/chatview.js b/web/chatview.js
new file mode 100644
index 0000000..54994fb
--- /dev/null
+++ b/web/chatview.js
@@ -0,0 +1,1504 @@
+"use strict"
+
+/* Constants used at several places*/
+const messageBarPlaceHolder = "Type a message"
+/* Constants used at several places*/
+// scrollDetectionThresh represents the number of pixels a user can scroll
+// without disabling the automatic go-back-to-bottom when a new message is
+// received
+const scrollDetectionThresh = 200
+// printHistoryPart loads blocks of messages. Each block contains
+// scrollBuffer messages
+const scrollBuffer = 20
+// The first time a conversation is loaded, the lazy loading system makes
+// sure at least initialScrollBufferFactor screens of messages are loaded
+const initialScrollBufferFactor = 3
+// Some signal like the onscrolled signals are debounced so that the their
+// assigned function isn't fired too often
+const debounceTime = 200
+
+/* Buffers */
+// current index in the history buffer
+var historyBufferIndex = 0
+// buffer containing the conversation's messages
+var historyBuffer = []
+
+/* We retrieve refs to the most used navbar and message bar elements for efficiency purposes */
+/* NOTE: always use getElementById when possible, way more efficient */
+const messageBar        = document.getElementById("sendMessage")
+const messageBarInput   = document.getElementById("message")
+const invitation        = document.getElementById("invitation")
+const invitationText    = document.getElementById("text")
+var   messages          = document.getElementById("messages")
+
+/* States: allows us to avoid re-doing something if it isn't meaningful */
+var displayLinksEnabled = true
+var hoverBackButtonAllowed = true
+var hasInvitation = false
+var isTemporary = false
+var isBanned = false
+var isAccountEnabled = true
+var isInitialLoading = false
+var imagesLoadingCounter = 0
+
+/* Set the default target to _self and handle with QWebEnginePage::acceptNavigationRequest */
+var linkifyOptions = {
+    attributes: null,
+    className: 'linkified',
+    defaultProtocol: 'http',
+    events: null,
+    format: function (value, type) {
+        return value;
+    },
+    formatHref: function (href, type) {
+        return href;
+    },
+    ignoreTags: [],
+    nl2br: false,
+    tagName: 'a',
+    target: {
+        url: '_self'
+    },
+    validate: true
+};
+
+new QWebChannel(qt.webChannelTransport, function(channel) {
+    window.jsbridge = channel.objects.jsbridge;
+});
+
+function onScrolled_() {
+    if (messages.scrollTop == 0 && historyBufferIndex != historyBuffer.length) {
+        /* At the top and there's something to print */
+        printHistoryPart(messages, messages.scrollHeight)
+    }
+}
+
+const debounce = (fn, time) => {
+    let timeout
+
+    return function() {
+        const functionCall = () => fn.apply(this, arguments)
+
+        clearTimeout(timeout)
+        timeout = setTimeout(functionCall, time)
+    }
+}
+
+/* exported onScrolled */
+var onScrolled = debounce(onScrolled_, debounceTime)
+
+/**
+ * Generic wrapper. Execute passed function keeping scroll position identical.
+ *
+ * @param func function to execute
+ * @param args parameters as array
+ */
+function exec_keeping_scroll_position(func, args) {
+    var atEnd = messages.scrollTop >= messages.scrollHeight - messages.clientHeight - scrollDetectionThresh
+    func(...args)
+    if (atEnd) {
+        messages.scrollTop = messages.scrollHeight
+    }
+}
+
+/**
+ * Reset scrollbar at a given position.
+ * @param scroll position at which the scrollbar should be set.
+ *               Here position means the number of pixels scrolled,
+ *               i.e. scroll = 0 resets the scrollbar at the bottom.
+ */
+function back_to_scroll(scroll) {
+    messages.scrollTop = messages.scrollHeight - scroll
+}
+
+/**
+ * Reset scrollbar at bottom.
+ */
+function back_to_bottom() {
+    back_to_scroll(0)
+}
+
+/**
+ * Hide or show invitation.
+ *
+ * Invitation is hidden if no contactAlias/invalid alias is passed.
+ * Otherwise, invitation div is updated.
+ *
+ * @param contactAlias
+ */
+/* exported showInvitation */
+function showInvitation(contactAlias) {
+    if (!contactAlias) {
+        if (hasInvitation) {
+            hasInvitation = false
+            invitation.style.visibility = ""
+        }
+    } else {
+        hasInvitation = true
+        invitationText.innerHTML = "<h1>" + contactAlias + " sends you an invitation</h1>"
+      + "Do you want to add them to the conversations list?<br>"
+      + "Note: you can automatically accept this invitation by sending a message."
+        invitation.style.visibility = "visible"
+    }
+}
+
+/* exported setDisplayLinks */
+function setDisplayLinks(display) {
+    displayLinksEnabled = display
+}
+
+/**
+ * This event handler dynamically resizes the message bar depending on the amount of
+ * text entered, while adjusting the body paddings so that that the message bar doesn't
+ * overlap messages when it grows.
+ */
+/* exported grow_text_area */
+function grow_text_area() {
+    exec_keeping_scroll_position(function(){
+        var old_height = window.getComputedStyle(messageBar).height
+        messageBarInput.style.height = "auto" /* <-- necessary, no clue why */
+        messageBarInput.style.height = messageBarInput.scrollHeight + "px"
+        var new_height = window.getComputedStyle(messageBar).height
+
+        var msgbar_size = window.getComputedStyle(document.body).getPropertyValue("--messagebar-size")
+        var total_size = parseInt(msgbar_size) + parseInt(new_height) - parseInt(old_height)
+
+        document.body.style.setProperty("--messagebar-size", total_size.toString() + "px")
+    }, [])
+}
+
+/**
+ * This event handler processes keydown events from the message bar. When pressed key is
+ * the enter key, send the message unless shift or control was pressed too.
+ *
+ * @param key the pressed key
+ */
+/* exported process_messagebar_keydown */
+function process_messagebar_keydown(key) {
+    key = key || event
+    var map = {}
+    map[key.keyCode] = key.type == "keydown"
+    if (key.ctrlKey && map[13]) {
+        messageBarInput.value += "\n"
+    }
+    if (key.ctrlKey || key.shiftKey) {
+        return true
+    }
+    if (map[13]) {
+        sendMessage()
+        key.preventDefault()
+    }
+    return true
+}
+
+
+/**
+ * Disable or enable textarea.
+ *
+ * @param isDisabled whether message bar should be enabled or disabled
+ */
+/* exported disableSendMessage */
+function disableSendMessage(isDisabled)
+{
+    messageBarInput.disabled = isDisabled
+}
+
+/*
+ * Update timestamps messages.
+ */
+function updateView() {
+    updateTimestamps(messages)
+}
+
+setInterval(updateView, 60000)
+
+/**
+ * Transform a date to a string group like "1 hour ago".
+ *
+ * @param date
+ */
+function formatDate(date) {
+    const seconds = Math.floor((new Date() - date) / 1000)
+    var interval = Math.floor(seconds / (3600 * 24))
+    if (interval > 5) {
+        return date.toLocaleDateString()
+    }
+    if (interval > 1) {
+        return interval + " days ago"
+    }
+    if (interval === 1) {
+        return interval + " day ago"
+    }
+    interval = Math.floor(seconds / 3600)
+    if (interval > 1) {
+        return interval + " hours ago"
+    }
+    if (interval === 1) {
+        return interval + " hour ago"
+    }
+    interval = Math.floor(seconds / 60)
+    if (interval > 1) {
+        return interval + " minutes ago"
+    }
+    return "just now"
+}
+
+/**
+ * Send content of message bar
+ */
+function sendMessage()
+{
+    var message = messageBarInput.value
+    if (message.length > 0) {
+        messageBarInput.value = ""
+        window.jsbridge.sendMessage(message)
+    }
+}
+
+/* exported sendFile */
+function sendFile()
+{
+    window.jsbridge.sendFile();
+}
+
+/**
+ * Clear all messages.
+ */
+/* exported clearMessages */
+function clearMessages()
+{
+    while (messages.firstChild) {
+        messages.removeChild(messages.firstChild)
+    }
+}
+
+/**
+ * Convert text to HTML.
+ */
+function escapeHtml(html)
+{
+    var text = document.createTextNode(html)
+    var div = document.createElement("div")
+    div.appendChild(text)
+    return div.innerHTML
+}
+
+
+/**
+ * Get the youtube video id from a URL.
+ * @param url
+ */
+function youtube_id(url) {
+    const regExp = /^.*(youtu\.be\/|v\/|\/u\/w|embed\/|watch\?v=|&v=)([^#&?]*).*/
+    const match = url.match(regExp)
+    return (match && match[2].length == 11) ? match[2] : null
+}
+
+/**
+ * Returns HTML message from the message text, cleaned and linkified.
+ * @param message_text
+ */
+function getMessageHtml(message_text)
+{
+    const escaped_message = escapeHtml(message_text)
+
+    var linkified_message = linkifyHtml(escaped_message, linkifyOptions)  // eslint-disable-line no-undef
+
+    const textPart = document.createElement("pre")
+    textPart.innerHTML = linkified_message
+
+    return textPart.outerHTML
+}
+
+/**
+ * Returns the message status, formatted for display
+ * @param message_delivery_status
+ */
+/* exported getMessageDeliveryStatusText */
+function getMessageDeliveryStatusText(message_delivery_status)
+{
+    var formatted_delivery_status = message_delivery_status
+
+    switch(message_delivery_status)
+    {
+    case "sending":
+    case "ongoing":
+        formatted_delivery_status = "Sending<svg overflow='visible' viewBox='0 -2 16 14' height='16px' width='16px'><circle class='status_circle anim-first' cx='4' cy='12' r='1'/><circle class='status_circle anim-second' cx='8' cy='12' r='1'/><circle class='status_circle anim-third' cx='12' cy='12' r='1'/></svg>"
+        break
+    case "failure":
+        formatted_delivery_status = "Failure <svg overflow='visible' viewBox='0 -2 16 14' height='16px' width='16px'><path class='status-x x-first' stroke='#AA0000' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' fill='none' d='M4,4 L12,12'/><path class='status-x x-second' stroke='#AA0000' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' fill='none' d='M12,4 L4,12'/></svg>"
+        break
+    case "sent":
+    case "finished":
+    case "unknown":
+    case "read":
+        formatted_delivery_status = ""
+        break
+    default:
+        break
+    }
+
+    return formatted_delivery_status
+}
+
+/**
+ * Returns the message date, formatted for display
+ */
+function getMessageTimestampText(message_timestamp, custom_format)
+{
+    const date = new Date(1000 * message_timestamp)
+    if(custom_format) {
+        return formatDate(date)
+    } else {
+        return date.toLocaleString()
+    }
+}
+
+/**
+ * Update timestamps.
+ * @param message_div
+ */
+function updateTimestamps(messages_div) {
+    const timestamps = messages_div.getElementsByClassName("timestamp")
+    for (var c = timestamps.length - 1; c >= 0 ; --c) {
+        var timestamp = timestamps.item(c)
+        timestamp.innerHTML = getMessageTimestampText(timestamp.getAttribute("message_timestamp"), true)
+    }
+}
+
+/**
+ * Convert a value in filesize
+ */
+function humanFileSize(bytes) {
+    var thresh = 1024
+    if(Math.abs(bytes) < thresh) {
+        return bytes + " B"
+    }
+    var units = ["kB","MB","GB","TB","PB","EB","ZB","YB"]
+    var u = -1
+    do {
+        bytes /= thresh
+        ++u
+    } while(Math.abs(bytes) >= thresh && u < units.length - 1)
+    return bytes.toFixed(1)+" "+units[u]
+}
+
+/**
+ * Hide or show add to conversations/calls whether the account is enabled
+ * @param accountEnabled true if account is enabled
+ */
+function hideControls(accountEnabled) {
+    if (!accountEnabled) {
+        callButtons.display = "none"
+    } else {
+        callButtons.display = ""
+    }
+}
+
+/**
+ * Change the value of the progress bar.
+ *
+ * @param progress_bar
+ * @param message_object
+ */
+function updateProgressBar(progress_bar, message_object) {
+    var delivery_status = message_object["delivery_status"]
+    if ("progress" in message_object && !isErrorStatus(delivery_status) && message_object["progress"] !== 100) {
+        var progress_percent = (100 * message_object["progress"] / message_object["totalSize"])
+        if (progress_percent !== 100)
+            progress_bar.childNodes[0].setAttribute("style", "width: " + progress_percent + "%")
+        else
+            progress_bar.setAttribute("style", "display: none")
+    } else
+        progress_bar.setAttribute("style", "display: none")
+}
+
+/**
+ * Check if a status is an error status
+ * @param
+ */
+function isErrorStatus(status) {
+    return (status === "failure"
+         || status === "awaiting peer timeout"
+         || status === "canceled"
+         || status === "unjoinable peer")
+}
+
+/**
+ * Build a new file interaction
+ * @param message_id
+ */
+function fileInteraction(message_id) {
+    var message_wrapper = document.createElement("div")
+    message_wrapper.setAttribute("class", "message_wrapper")
+
+    var transfer_info_wrapper = document.createElement("div")
+    transfer_info_wrapper.setAttribute("class", "transfer_info_wrapper")
+    message_wrapper.appendChild(transfer_info_wrapper)
+
+    /* Buttons at the left for status information or accept/refuse actions.
+       The text is bold and clickable. */
+    var left_buttons = document.createElement("div")
+    left_buttons.setAttribute("class", "left_buttons")
+    transfer_info_wrapper.appendChild(left_buttons)
+
+    var full_div = document.createElement("div")
+    full_div.setAttribute("class", "full")
+    full_div.style.visibility = "hidden"
+    full_div.style.display = "none"
+
+    var filename_wrapper = document.createElement("div")
+    filename_wrapper.setAttribute("class", "truncate-ellipsis")
+
+    var message_text = document.createElement("span")
+    message_text.setAttribute("class", "filename")
+    filename_wrapper.appendChild(message_text)
+
+    // And information like size or error message.
+    var informations_div = document.createElement("div")
+    informations_div.setAttribute("class", "informations")
+
+    var text_div = document.createElement("div")
+    text_div.setAttribute("class", "text")
+    text_div.addEventListener("click", function () {
+        // ask ring to open the file
+        const filename = document.querySelector("#message_" + message_id + " .full").innerText
+        window.jsbridge.openFile(filename);
+    })
+
+    text_div.appendChild(filename_wrapper)
+    text_div.appendChild(full_div)
+    text_div.appendChild(informations_div)
+    transfer_info_wrapper.appendChild(text_div)
+
+    // And finally, a progress bar
+    var message_transfer_progress_bar = document.createElement("span")
+    message_transfer_progress_bar.setAttribute("class", "message_progress_bar")
+
+    var message_transfer_progress_completion = document.createElement("span")
+    message_transfer_progress_bar.appendChild(message_transfer_progress_completion)
+    message_wrapper.appendChild(message_transfer_progress_bar)
+
+    const internal_mes_wrapper = document.createElement("div")
+    internal_mes_wrapper.setAttribute("class", "internal_mes_wrapper")
+    internal_mes_wrapper.appendChild(message_wrapper)
+
+    return internal_mes_wrapper
+}
+
+/**
+ * Build information text for passed file interaction message object
+ *
+ * @param message_object message object containing file interaction info
+ */
+function buildFileInformationText(message_object) {
+    var informations_txt = getMessageTimestampText(message_object["timestamp"], true)
+    if (message_object["totalSize"] && message_object["progress"]) {
+        if (message_object["delivery_status"] === "finished") {
+            informations_txt += " - " + humanFileSize(message_object["totalSize"])
+        } else {
+            informations_txt += " - " + humanFileSize(message_object["progress"])
+                         + " / " + humanFileSize(message_object["totalSize"])
+        }
+    }
+
+    return informations_txt + " - " + message_object["delivery_status"]
+}
+
+/**
+ * Update a file interaction (icons + filename + status + progress bar)
+ *
+ * @param message_div the message to update
+ * @param message_object new informations
+ * @param forceTypeToFile
+ */
+function updateFileInteraction(message_div, message_object, forceTypeToFile = false) {
+    if (!message_div.querySelector(".informations")) return // media
+
+    var acceptSvg = "<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z\"/></svg>",
+        refuseSvg = "<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"/><path d=\"M0 0h24v24H0z\" fill=\"none\"/></svg>",
+        fileSvg = "<svg fill=\"#000000\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z\"/><path d=\"M0 0h24v24H0z\" fill=\"none\"/></svg>",
+        warningSvg = "<svg fill=\"#000000\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>"
+    var message_delivery_status = message_object["delivery_status"]
+    var message_direction = message_object["direction"]
+    var message_id = message_object["id"]
+    var message_text = message_object["text"]
+
+
+    if (isImage(message_text) && message_delivery_status === "finished" && displayLinksEnabled && !forceTypeToFile) {
+        // Replace the old wrapper by the downloaded image
+        var old_wrapper = message_div.querySelector(".internal_mes_wrapper")
+        if (old_wrapper) {
+            old_wrapper.parentNode.removeChild(old_wrapper)
+        }
+
+        var errorHandler = function() {
+            var wrapper = message_div.querySelector(".internal_mes_wrapper")
+            var message_wrapper = message_div.querySelector(".message_wrapper")
+            if (message_wrapper) {
+                message_wrapper.parentNode.removeChild(message_wrapper)
+            }
+
+            var media_wrapper = message_div.querySelector(".media_wrapper")
+            if (media_wrapper) {
+                media_wrapper.parentNode.removeChild(media_wrapper)
+            }
+
+            var new_interaction = fileInteraction(message_id)
+            var new_message_wrapper = new_interaction.querySelector(".message_wrapper")
+            wrapper.prepend(new_message_wrapper)
+            updateFileInteraction(message_div, message_object, true)
+        }
+
+        var new_wrapper = mediaInteraction(message_id, message_text, null, errorHandler)
+        message_div.insertBefore(new_wrapper, message_div.querySelector(".menu_interaction"))
+        message_div.querySelector("img").id = message_id
+        message_div.querySelector("img").msg_obj = message_object
+        return
+    }
+
+    // Set informations text
+    var informations_div = message_div.querySelector(".informations")
+    informations_div.innerText = buildFileInformationText(message_object)
+
+    // Update flat buttons
+    var left_buttons = message_div.querySelector(".left_buttons")
+    left_buttons.innerHTML = ""
+    if (message_delivery_status === "awaiting peer" ||
+        message_delivery_status === "awaiting host" ||
+        message_delivery_status.indexOf("ongoing") === 0) {
+
+        if (message_direction === "in" && message_delivery_status.indexOf("ongoing") !== 0) {
+            // add buttons to accept or refuse a call.
+            var accept_button = document.createElement("div")
+            accept_button.innerHTML = acceptSvg
+            accept_button.setAttribute("title", "Accept")
+            accept_button.setAttribute("class", "flat-button accept")
+            accept_button.onclick = function() {
+                window.jsbridge.acceptFile(message_id);
+            }
+            left_buttons.appendChild(accept_button)
+        }
+
+        var refuse_button = document.createElement("div")
+        refuse_button.innerHTML = refuseSvg
+        refuse_button.setAttribute("title", "Refuse")
+        refuse_button.setAttribute("class", "flat-button refuse")
+        refuse_button.onclick = function() {
+            window.jsbridge.refuseFile(message_id);
+        }
+        left_buttons.appendChild(refuse_button)
+    } else {
+        var status_button = document.createElement("div")
+        var statusFile = fileSvg
+        if (isErrorStatus(message_delivery_status))
+            statusFile = warningSvg
+        status_button.innerHTML = statusFile
+        status_button.setAttribute("class", "flat-button")
+        left_buttons.appendChild(status_button)
+    }
+
+    message_div.querySelector(".full").innerText = message_text
+    message_div.querySelector(".filename").innerText = message_text.split("/").pop()
+    updateProgressBar(message_div.querySelector(".message_progress_bar"), message_object)
+}
+
+/**
+ * Return if a file is an image
+ * @param file
+ */
+function isImage(file) {
+    return file.toLowerCase().match(/\.(jpeg|jpg|gif|png)$/) !== null
+}
+
+/**
+ * Return if a file is a youtube video
+ * @param file
+ */
+function isVideo(file) {
+    const availableProtocols = ["http:", "https:"]
+    const videoHostname = ["youtube.com", "www.youtube.com", "youtu.be"]
+    const urlParser = document.createElement("a")
+    urlParser.href = file
+    return (availableProtocols.includes(urlParser.protocol) && videoHostname.includes(urlParser.hostname))
+}
+
+/**
+ * Build a container for passed video thumbnail
+ * @param linkElt video thumbnail div
+ */
+function buildVideoContainer(linkElt) {
+    const containerElt = document.createElement("div")
+    containerElt.setAttribute("class", "containerVideo")
+    const playDiv = document.createElement("div")
+    playDiv.setAttribute("class", "playVideo")
+    playDiv.innerHTML = "<svg fill=\"#ffffff\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\
+        <path d=\"M8 5v14l11-7z\"/>\
+        <path d=\"M0 0h24v24H0z\" fill=\"none\"/>\
+    </svg>"
+    linkElt.appendChild(playDiv)
+    containerElt.appendChild(linkElt)
+
+    return containerElt
+}
+
+/**
+ * Try to show an image or a video link (youtube for now)
+ * @param message_id
+ * @param link to show
+ * @param ytid if it's a youtube video
+ * @param errorHandler the new media's onerror field will be set to this function
+ */
+function mediaInteraction(message_id, link, ytid, errorHandler) {
+    /* TODO promise?
+     Try to display images. */
+    const media_wrapper = document.createElement("div")
+    media_wrapper.setAttribute("class", "media_wrapper")
+    const linkElt = document.createElement("a")
+    linkElt.href = link
+    linkElt.style.textDecoration = "none"
+    linkElt.style.border = "none"
+    const imageElt = document.createElement("img")
+
+    imageElt.src = ytid ? `http://img.youtube.com/vi/${ytid}/0.jpg` : link
+
+    /* Note, here, we don't check the size of the image.
+     in the future, we can check the content-type and content-length with a request
+     and maybe disable svg */
+
+    if (isInitialLoading) {
+        /* During initial load, make sure the scrollbar stays at the bottom.
+           Also, the final scrollHeight is only known after the last image was
+           loaded. We want to display a specific number of messages screens so
+           we have to set up a callback (on_image_load_finished) which will
+           check on that and reschedule a new display batch if not enough
+           messages have been loaded in the DOM. */
+        imagesLoadingCounter++
+        imageElt.onload = function() {
+            back_to_bottom()
+            on_image_load_finished()
+        }
+
+        if (errorHandler) {
+            imageElt.onerror = function() {
+                errorHandler()
+                back_to_bottom()
+                on_image_load_finished()
+            }
+        }
+    } else if (messages.scrollTop >= messages.scrollHeight - messages.clientHeight - scrollDetectionThresh) {
+        /* Keep the scrollbar at the bottom. Images are loaded asynchronously and
+           the scrollbar position is changed each time an image is loaded and displayed.
+           In order to make sure the scrollbar stays at the bottom, reset scrollbar
+           position each time an image was loaded. */
+        imageElt.onload = back_to_bottom
+
+        if (errorHandler) {
+            imageElt.onerror = function() {
+                errorHandler()
+                back_to_bottom()
+            }
+        }
+    } else if (errorHandler) {
+        imageElt.onerror = errorHandler
+    }
+
+    linkElt.appendChild(imageElt)
+
+    if (ytid) {
+        media_wrapper.appendChild(buildVideoContainer(linkElt))
+    } else {
+        media_wrapper.appendChild(linkElt)
+    }
+
+    const internal_mes_wrapper = document.createElement("div")
+    internal_mes_wrapper.setAttribute("class", "internal_mes_wrapper")
+    internal_mes_wrapper.appendChild(media_wrapper)
+
+    return internal_mes_wrapper
+}
+
+/**
+ * Build a new text interaction
+ * @param message_id
+ * @param htmlText the DOM to show
+ */
+function textInteraction(message_id, htmlText) {
+    const message_wrapper = document.createElement("div")
+    message_wrapper.setAttribute("class", "message_wrapper")
+    var message_text = document.createElement("div")
+    message_text.setAttribute("class", "message_text")
+    message_text.innerHTML = htmlText
+    message_wrapper.appendChild(message_text)
+    // TODO STATUS
+
+    const internal_mes_wrapper = document.createElement("div")
+    internal_mes_wrapper.setAttribute("class", "internal_mes_wrapper")
+    internal_mes_wrapper.appendChild(message_wrapper)
+
+    return internal_mes_wrapper
+}
+
+/**
+ * Update a text interaction (text)
+ * @param message_div the message to update
+ * @param delivery_status the status of the message
+ */
+function updateTextInteraction(message_div, delivery_status) {
+    if (!message_div.querySelector(".message_text")) return // media
+    var sending = message_div.querySelector(".sending")
+    switch(delivery_status)
+    {
+    case "ongoing":
+    case "sending":
+        if (!sending) {
+            sending = document.createElement("div")
+            sending.setAttribute("class", "sending")
+            sending.innerHTML = "<svg overflow=\"hidden\" viewBox=\"0 -2 16 14\" height=\"16px\" width=\"16px\"><circle class=\"status_circle anim-first\" cx=\"4\" cy=\"12\" r=\"1\"/><circle class=\"status_circle anim-second\" cx=\"8\" cy=\"12\" r=\"1\"/><circle class=\"status_circle anim-third\" cx=\"12\" cy=\"12\" r=\"1\"/></svg>"
+            // add sending animation to message;
+            message_div.insertBefore(sending, message_div.querySelector(".menu_interaction"))
+        }
+        message_div.querySelector(".message_text").style.color = "#888"
+        break
+    case "failure":
+        // change text color to red
+        message_div.querySelector(".message_text").color = "#000"
+        var failure_div = message_div.querySelector(".failure")
+        if (!failure_div) {
+            failure_div = document.createElement("div")
+            failure_div.setAttribute("class", "failure")
+            failure_div.innerHTML = "<svg overflow=\"visible\" viewBox=\"0 -2 16 14\" height=\"16px\" width=\"16px\"><path class=\"status-x x-first\" stroke=\"#AA0000\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"3\" fill=\"none\" d=\"M4,4 L12,12\"/><path class=\"status-x x-second\" stroke=\"#AA0000\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"3\" fill=\"none\" d=\"M12,4 L4,12\"/></svg>"
+            // add failure animation to message
+            message_div.insertBefore(failure_div, message_div.querySelector(".menu_interaction"))
+        }
+        message_div.querySelector(".message_text").style.color = "#000"
+        if (sending) sending.style.display = "none"
+        break
+    case "sent":
+    case "finished":
+    case "unknown":
+    case "read":
+        // change text color to black
+        message_div.querySelector(".message_text").style.color = "#000"
+        if (sending) sending.style.display = "none"
+        break
+    default:
+        break
+    }
+}
+
+/**
+ * Build a new interaction (call or contact)
+ */
+function actionInteraction() {
+    var message_wrapper = document.createElement("div")
+    message_wrapper.setAttribute("class", "message_wrapper")
+
+    // A file interaction contains buttons at the left of the interaction
+    // for the status or accept/refuse buttons
+    var left_buttons = document.createElement("div")
+    left_buttons.setAttribute("class", "left_buttons")
+    message_wrapper.appendChild(left_buttons)
+    // Also contains a bold clickable text
+    var text_div = document.createElement("div")
+    text_div.setAttribute("class", "text")
+    message_wrapper.appendChild(text_div)
+    return message_wrapper
+}
+
+/**
+ * Update a call interaction (icon + text)
+ * @param message_div the message to update
+ * @param message_object new informations
+ */
+function updateCallInteraction(message_div, message_object) {
+    const outgoingCall = "<svg fill=\"#219d55\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5z\"/></svg>"
+    const callMissed = "<svg fill=\"#dc2719\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M19.59 7L12 14.59 6.41 9H11V7H3v8h2v-4.59l7 7 9-9z\"/></svg>"
+    const outgoingMissed = "<svg fill=\"#dc2719\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"><defs><path d=\"M24 24H0V0h24v24z\" id=\"a\"/></defs><clipPath id=\"b\"><use overflow=\"visible\" xlink:href=\"#a\"/></clipPath><path clip-path=\"url(#b)\" d=\"M3 8.41l9 9 7-7V15h2V7h-8v2h4.59L12 14.59 4.41 7 3 8.41z\"/></svg>"
+    const callReceived = "<svg fill=\"#219d55\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M20 5.41L18.59 4 7 15.59V9H5v10h10v-2H8.41z\"/></svg>"
+
+    const message_text = message_object["text"]
+    const message_direction = (message_text.toLowerCase().indexOf("incoming") !== -1) ? "in" : "out"
+    const missed = message_text.indexOf("Missed") !== -1
+
+    message_div.querySelector(".text").innerText = message_text.substring(2)
+
+    var left_buttons = message_div.querySelector(".left_buttons")
+    left_buttons.innerHTML = ""
+    var status_button = document.createElement("div")
+    var statusFile = ""
+    if (missed)
+        statusFile = (message_direction === "in") ? callMissed : outgoingMissed
+    else
+        statusFile = (message_direction === "in") ? callReceived : outgoingCall
+    status_button.innerHTML = statusFile
+    status_button.setAttribute("class", "flat-button")
+    left_buttons.appendChild(status_button)
+}
+
+/**
+ * Update a contact interaction (icon + text)
+ * @param message_div the message to update
+ * @param message_object new informations
+ */
+function updateContactInteraction(message_div, message_object) {
+    const message_text = message_object["text"]
+
+    message_div.querySelector(".text").innerText = message_text
+
+    var left_buttons = message_div.querySelector(".left_buttons")
+    left_buttons.innerHTML = ""
+    var status_button = document.createElement("div")
+    status_button.innerHTML = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\
+<path d=\"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z\"/>\
+<path d=\"M0 0h24v24H0z\" fill=\"none\"/></svg>"
+    status_button.setAttribute("class", "flat-button")
+    left_buttons.appendChild(status_button)
+}
+
+/**
+ * Remove an interaction from the conversation
+ * @param interaction_id
+ */
+/* exported removeInteraction */
+function removeInteraction(interaction_id) {
+    var interaction = document.getElementById(`message_${interaction_id}`)
+    if (!interaction) {
+        return
+    }
+
+    if (interaction.previousSibling) {
+        /* if element was the most recently received message, make sure the
+           last_message property is given away to the previous sibling */
+        if (interaction.classList.contains("last_message")) {
+            interaction.previousSibling.classList.add("last_message")
+        }
+
+        /* same for timestamp */
+        var timestamp = interaction.querySelector(".timestamp")
+        var previousTimeStamp = interaction.previousSibling.querySelector(".timestamp")
+        if (timestamp && !previousTimeStamp) {
+            interaction.previousSibling.querySelector(".internal_mes_wrapper").appendChild(timestamp)
+        }
+    }
+
+    var firstMessage = getPreviousInteraction(interaction)
+    var secondMessage = getNextInteraction(interaction)
+
+    updateSequencing(firstMessage, secondMessage)
+
+    interaction.parentNode.removeChild(interaction)
+}
+
+/**
+ * Build message dropdown
+ * @return a message dropdown for passed message id
+ */
+function buildMessageDropdown(message_id) {
+    const menu_element = document.createElement("div")
+    menu_element.setAttribute("class", "menu_interaction")
+    menu_element.innerHTML =
+    `<input type="checkbox" id="showmenu${message_id}" class="showmenu">
+     <label for="showmenu${message_id}">
+       <svg fill="#888888" height="12" viewBox="0 0 24 24" width="12" xmlns="http://www.w3.org/2000/svg">
+         <path d="M0 0h24v24H0z" fill="none"/>
+         <path d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>
+       </svg>
+     </label>`
+    menu_element.onclick = function() {
+        const button = this.querySelector(".showmenu")
+        button.checked = !button.checked
+    }
+    menu_element.onmouseleave = function() {
+        const button = this.querySelector(".showmenu")
+        button.checked = false
+    }
+    const dropdown = document.createElement("div")
+    const dropdown_classes = [
+        "dropdown",
+        `dropdown_${message_id}`
+    ]
+    dropdown.setAttribute("class", dropdown_classes.join(" "))
+
+    const remove = document.createElement("div")
+    remove.setAttribute("class", "menuoption")
+    remove.innerHTML = "Delete"
+    remove.msg_id = message_id
+    remove.onclick = function() {
+        window.jsbridge.deleteInteraction(`${this.msg_id}`);
+    }
+    dropdown.appendChild(remove)
+    menu_element.appendChild(dropdown)
+
+    return menu_element
+}
+
+/**
+ * Build a message div for passed message object
+ * @param message_object to treat
+ */
+function buildNewMessage(message_object) {
+    const message_id = message_object["id"]
+    const message_type = message_object["type"]
+    const message_text = message_object["text"]
+    const message_direction = message_object["direction"]
+    const delivery_status = message_object["delivery_status"]
+    const message_sender_contact_method = message_object["sender_contact_method"]
+
+    var classes = [
+        "message",
+        `message_${message_direction}`,
+        `message_type_${message_type}`
+    ]
+
+    var type = ""
+    var message_div = document.createElement("div")
+    message_div.setAttribute("id", `message_${message_id}`)
+    message_div.setAttribute("class", classes.join(" "))
+
+    // Build message for each types.
+    // Add sender images if necessary (like if the interaction doesn't take the whole width)
+    const need_sender = (message_type === "data_transfer" || message_type === "text")
+    if (need_sender) {
+        var message_sender_image = document.createElement("span")
+        message_sender_image.setAttribute("class", `sender_image sender_image_${message_sender_contact_method}`)
+        message_div.appendChild(message_sender_image)
+    }
+
+    // Build main content
+    if (message_type === "data_transfer") {
+        if (isImage(message_text) && delivery_status === "finished" && displayLinksEnabled) {
+            var errorHandler = function() {
+                var wrapper = message_div.querySelector(".internal_mes_wrapper")
+                var message_wrapper = message_div.querySelector(".message_wrapper")
+                if (message_wrapper) {
+                    message_wrapper.parentNode.removeChild(message_wrapper)
+                }
+
+                var media_wrapper = message_div.querySelector(".media_wrapper")
+                if (media_wrapper) {
+                    media_wrapper.parentNode.removeChild(media_wrapper)
+                }
+
+                var new_interaction = fileInteraction(message_id)
+                var new_message_wrapper = new_interaction.querySelector(".message_wrapper")
+                wrapper.prepend(new_message_wrapper)
+                updateFileInteraction(message_div, message_object, true)
+            }
+            message_div.append(mediaInteraction(message_id, message_text, null, errorHandler))
+            message_div.querySelector("img").id = message_id
+            message_div.querySelector("img").msg_obj = message_object
+        } else {
+            message_div.append(fileInteraction(message_id))
+        }
+    } else if (message_type === "text") {
+        // TODO add the possibility to update messages (remove and rebuild)
+        const htmlText = getMessageHtml(message_text)
+        if (displayLinksEnabled) {
+            const parser = new DOMParser()
+            const DOMMsg = parser.parseFromString(htmlText, "text/xml")
+            const links = DOMMsg.querySelectorAll("a")
+            if (DOMMsg.childNodes.length && links.length) {
+                var isTextToShow = (DOMMsg.childNodes[0].childNodes.length > 1)
+                const ytid = (isVideo(message_text))? youtube_id(message_text) : ""
+                if (!isTextToShow && (ytid || isImage(message_text))) {
+                    type = "media"
+                    message_div.append(mediaInteraction(message_id, message_text, ytid))
+                }
+            }
+        }
+        if (type !== "media") {
+            type = "text"
+            message_div.append(textInteraction(message_id, htmlText))
+        }
+    } else if (message_type === "call" || message_type === "contact") {
+        message_div.append(actionInteraction())
+    } else {
+        const temp = document.createElement("div")
+        temp.innerText = message_type
+        message_div.appendChild(temp)
+    }
+
+    var message_dropdown = buildMessageDropdown(message_id)
+    if (message_type !== "call") {
+        message_div.appendChild(message_dropdown)
+    } else {
+        var wrapper = message_div.querySelector(".message_wrapper")
+        wrapper.insertBefore(message_dropdown, wrapper.firstChild)
+    }
+
+    return message_div
+}
+
+/**
+ * Build a timestamp for passed message object
+ * @param message_object to treat
+ */
+function buildNewTimestamp(message_object) {
+    const message_type = message_object["type"]
+    const message_direction = message_object["direction"]
+    const message_timestamp = message_object["timestamp"]
+
+    const formattedTimestamp = getMessageTimestampText(message_timestamp, true)
+    const date_elt = document.createElement("div")
+
+    date_elt.innerText = formattedTimestamp
+    var typeIsCallOrContact = (message_type === "call" || message_type === "contact")
+    var timestamp_div_classes = ["timestamp", typeIsCallOrContact ? "timestamp_action" : `timestamp_${message_direction}`]
+    date_elt.setAttribute("class", timestamp_div_classes.join(" "))
+    date_elt.setAttribute("message_timestamp", message_timestamp)
+
+    return date_elt
+}
+
+/**
+ * Add a message to the conversation.
+ * @param message_object to treat
+ * @param new_message if it's a new message or if we need to update
+ * @param insert_after if we want the message at the end or the top of the conversation
+ * @param messages_div
+ */
+function addOrUpdateMessage(message_object, new_message, insert_after = true, messages_div) {
+    const message_id = message_object["id"]
+    const message_type = message_object["type"]
+    const message_direction = message_object["direction"]
+    const delivery_status = message_object["delivery_status"]
+
+    var message_div = messages_div.querySelector("#message_" + message_id)
+    if (new_message) {
+        message_div = buildNewMessage(message_object)
+
+        /* Show timestamp if either:
+           - message has type call or contact
+           - or most recently added timestamp in this set is different
+           - or message is the first message in this set */
+
+        var date_elt = buildNewTimestamp(message_object)
+        var timestamp = messages_div.querySelector(".timestamp")
+
+        if (message_type === "call" || message_type === "contact") {
+            message_div.querySelector(".message_wrapper").appendChild(date_elt)
+        } else if (insert_after || !timestamp || timestamp.className !== date_elt.className
+                                || timestamp.innerHTML !== date_elt.innerHTML) {
+            message_div.querySelector(".internal_mes_wrapper").appendChild(date_elt)
+        }
+
+        var isGenerated = message_type === "call" || message_type === "contact"
+        if (isGenerated) {
+            message_div.classList.add("generated_message")
+        }
+
+        if (insert_after) {
+            var previousMessage = messages_div.lastChild
+            messages_div.appendChild(message_div)
+            computeSequencing(previousMessage, message_div, null, insert_after)
+            if (previousMessage) {
+                previousMessage.classList.remove("last_message")
+            }
+            message_div.classList.add("last_message")
+
+            /* When inserting at the bottom we should also check that the
+               previously sent message does not have the same timestamp.
+               If it's the case, remove it.*/
+            var previous_timestamp = message_div.previousSibling.querySelector(".timestamp")
+            if (previous_timestamp &&
+                previous_timestamp.className === date_elt.className &&
+                previous_timestamp.innerHTML === date_elt.innerHTML &&
+                !message_div.previousSibling.classList.contains("last_of_sequence")) {
+                previous_timestamp.parentNode.removeChild(previous_timestamp)
+            }
+        } else {
+            var nextMessage = messages_div.firstChild
+            messages_div.prepend(message_div)
+            computeSequencing(message_div, nextMessage, null, insert_after)
+        }
+    }
+
+    if (isErrorStatus(delivery_status) && message_direction === "out") {
+        const dropdown = messages_div.querySelector(`.dropdown_${message_id}`)
+        if (!dropdown.querySelector(".retry")) {
+            const retry = document.createElement("div")
+            retry.setAttribute("class", "retry")
+            retry.innerHTML = "Retry"
+            retry.msg_id = message_id
+            retry.onclick = function() {
+                window.jsbridge.retryInteraction(`${this.msg_id}`);
+            }
+            dropdown.insertBefore(retry, message_div.querySelector(".delete"))
+        }
+    }
+
+    // Update informations if needed
+    if (message_type === "data_transfer")
+        updateFileInteraction(message_div, message_object)
+    if (message_type === "text" && message_direction === "out")
+    // Modify sent status if necessary
+        updateTextInteraction(message_div, delivery_status)
+    if (message_type === "call")
+        updateCallInteraction(message_div, message_object)
+    if (message_type === "contact")
+        updateContactInteraction(message_div, message_object)
+
+    // Clean timestamps
+    updateTimestamps(messages_div)
+}
+
+function getNextInteraction(interaction, includeLazyLoadedBlock = true)
+{
+    var nextInteraction = interaction.nextSibling
+    if (!nextInteraction && includeLazyLoadedBlock) {
+        var nextBlock = interaction.parentNode.nextElementSibling
+        if (nextBlock) {
+            nextInteraction = nextBlock.firstElementChild
+        }
+    }
+    return nextInteraction
+}
+
+function getPreviousInteraction(interaction, includeLazyLoadedBlock = true)
+{
+    var previousInteraction = interaction.previousSibling
+    if (!previousInteraction && includeLazyLoadedBlock) {
+        var previousBlock = interaction.parentNode.previousElementSibling
+        if (previousBlock) {
+            previousInteraction = previousBlock.lastElementChild
+        }
+    }
+    return previousInteraction
+}
+
+function isSequenceBreak(firstMessage, secondMessage, insert_after = true)
+{
+    if (!firstMessage || !secondMessage) {
+        return false
+    }
+    var first_message_direction = firstMessage.classList.contains("message_out") ? "out" : "in"
+    var second_message_direction = secondMessage.classList.contains("message_out") ? "out" : "in"
+    if (second_message_direction != first_message_direction) {
+        return true
+    }
+    var firstMessageIsGenerated = firstMessage.classList.contains("generated_message")
+    var secondMessageIsGenerated = secondMessage.classList.contains("generated_message")
+    if (firstMessageIsGenerated != secondMessageIsGenerated) {
+        return true
+    }
+    if (insert_after) {
+        const internal_message_wrapper = firstMessage.querySelector(".internal_mes_wrapper")
+        if (internal_message_wrapper) {
+            const firstTimestamp = internal_message_wrapper.querySelector(".timestamp")
+            return !!(firstTimestamp) && firstTimestamp.innerHTML !== "just now"
+        }
+        return false
+    } else {
+        const internal_message_wrapper = firstMessage.querySelector(".internal_mes_wrapper")
+        if (internal_message_wrapper) {
+            return !!(internal_message_wrapper.querySelector(".timestamp"))
+        }
+        return false
+    }
+}
+
+function updateSequencing(firstMessage, secondMessage)
+{
+    if (firstMessage) {
+        if (secondMessage) {
+            var sequence_break = isSequenceBreak(firstMessage, secondMessage, false)
+            if (sequence_break) {
+                if (firstMessage.classList.contains("middle_of_sequence")) {
+                    firstMessage.classList.remove("middle_of_sequence")
+                    firstMessage.classList.add("last_of_sequence")
+                } else if (firstMessage.classList.contains("first_of_sequence")) {
+                    firstMessage.classList.remove("first_of_sequence")
+                    firstMessage.classList.add("single_message")
+                }
+                if (secondMessage.classList.contains("middle_of_sequence")) {
+                    secondMessage.classList.remove("middle_of_sequence")
+                    secondMessage.classList.add("first_of_sequence")
+                } else if (secondMessage.classList.contains("last_of_sequence")) {
+                    secondMessage.classList.remove("last_of_sequence")
+                    secondMessage.classList.add("single_message")
+                }
+            } else {
+                if (firstMessage.classList.contains("last_of_sequence")) {
+                    firstMessage.classList.remove("last_of_sequence")
+                    firstMessage.classList.add("middle_of_sequence")
+                } else if (firstMessage.classList.contains("single_message")) {
+                    firstMessage.classList.remove("single_message")
+                    firstMessage.classList.add("first_of_sequence")
+                }
+                if (secondMessage.classList.contains("first_of_sequence")) {
+                    secondMessage.classList.remove("first_of_sequence")
+                    secondMessage.classList.add("middle_of_sequence")
+                } else if (secondMessage.classList.contains("single_message")) {
+                    secondMessage.classList.remove("single_message")
+                    secondMessage.classList.add("last_of_sequence")
+                }
+            }
+        } else {
+            // this is the last interaction of the conversation
+            if (firstMessage.classList.contains("first_of_sequence")) {
+                firstMessage.classList.remove("first_of_sequence")
+                firstMessage.classList.add("single_message")
+            } else if (firstMessage.classList.contains("middle_of_sequence")) {
+                firstMessage.classList.remove("middle_of_sequence")
+                firstMessage.classList.add("last_of_sequence")
+            }
+        }
+    } else if (secondMessage) {
+        // this is the first interaction of the conversation
+        if (secondMessage.classList.contains("middle_of_sequence")) {
+            secondMessage.classList.remove("middle_of_sequence")
+            secondMessage.classList.add("first_of_sequence")
+        } else if (secondMessage.classList.contains("last_of_sequence")) {
+            secondMessage.classList.remove("last_of_sequence")
+            secondMessage.classList.add("single_message")
+        }
+    }
+}
+
+function computeSequencing(firstMessage, secondMessage, lazyLoadingBlock, insert_after = true)
+{
+    if (insert_after) {
+        if (secondMessage) {
+            var secondMessageIsGenerated = secondMessage.classList.contains("generated_message")
+            if (firstMessage && !secondMessageIsGenerated) {
+                var firstMessageIsGenerated = firstMessage.classList.contains("generated_message")
+                var sequence_break = isSequenceBreak(firstMessage, secondMessage)
+                if (sequence_break) {
+                    secondMessage.classList.add("single_message")
+                } else {
+                    if (firstMessage.classList.contains("single_message")) {
+                        firstMessage.classList.remove("single_message")
+                        firstMessage.classList.add("first_of_sequence")
+                    } else if (firstMessage.classList.contains("last_of_sequence")) {
+                        firstMessage.classList.remove("last_of_sequence")
+                        firstMessage.classList.add("middle_of_sequence")
+                    }
+                    if (firstMessageIsGenerated) {
+                        secondMessage.classList.add("single_message")
+                    } else {
+                        secondMessage.classList.add("last_of_sequence")
+                    }
+                }
+            } else if (!secondMessageIsGenerated) {
+                secondMessage.classList.add("single_message")
+            }
+        }
+    } else if (firstMessage) {
+        var firstMessageIsGenerated = firstMessage.classList.contains("generated_message")
+        if (secondMessage && !firstMessageIsGenerated) {
+            var secondMessageIsGenerated = secondMessage.classList.contains("generated_message")
+            var sequence_break = isSequenceBreak(firstMessage, secondMessage, false)
+            if (sequence_break) {
+                firstMessage.classList.add("single_message")
+            } else {
+                if (secondMessage.classList.contains("single_message")) {
+                    secondMessage.classList.remove("single_message")
+                    secondMessage.classList.add("last_of_sequence")
+                } else if (secondMessage.classList.contains("first_of_sequence")) {
+                    secondMessage.classList.remove("first_of_sequence")
+                    secondMessage.classList.add("middle_of_sequence")
+                }
+                if (secondMessageIsGenerated) {
+                    firstMessage.classList.add("single_message")
+                } else {
+                    firstMessage.classList.add("first_of_sequence")
+                }
+            }
+        } else if (!firstMessageIsGenerated) {
+            firstMessage.classList.add("single_message")
+        }
+    }
+}
+
+/**
+ * Wrapper for addOrUpdateMessage.
+ *
+ * Add or update a message and make sure the scrollbar position
+ * is refreshed correctly
+ *
+ * @param message_object message to be added
+ */
+/* exported addMessage */
+function addMessage(message_object)
+{
+    if (!messages.lastChild) {
+        var block_wrapper = document.createElement("div")
+        messages.append(block_wrapper)
+    }
+
+    exec_keeping_scroll_position(addOrUpdateMessage, [message_object, true, undefined, messages.lastChild])
+}
+
+/**
+ * Update a message that was previously added with addMessage and
+ * make sure the scrollbar position is refreshed correctly
+ *
+ * @param message_object message to be updated
+ */
+/* exported updateMessage */
+function updateMessage(message_object)
+{
+    var message_div = messages.querySelector("#message_" + message_object["id"])
+    exec_keeping_scroll_position(addOrUpdateMessage, [message_object, false, undefined, message_div.parentNode])
+}
+
+/**
+ * Called whenever an image has finished loading. Check lazy loading status
+ * once all images have finished loading.
+ */
+function on_image_load_finished() {
+    imagesLoadingCounter--
+
+    if (!imagesLoadingCounter) {
+        /* This code is executed once all images have been loaded. */
+        check_lazy_loading()
+    }
+}
+
+/**
+ * Make sure at least initialScrollBufferFactor screens of messages are
+ * available in the DOM.
+ */
+function check_lazy_loading() {
+    if (messages.scrollHeight < initialScrollBufferFactor * messages.clientHeight
+        && historyBufferIndex !== historyBuffer.length) {
+        /* Not enough messages loaded, print a new batch. Enable isInitialLoading
+           as reloading a single batch might not be sufficient to fulfill our
+           criteria (we want to be called back again to check on that) */
+        isInitialLoading = true
+        printHistoryPart(messages, 0)
+        isInitialLoading = false
+    }
+}
+
+/**
+ * Display 'scrollBuffer' messages from history in passed div (reverse order).
+ *
+ * @param messages_div that should be modified
+ * @param setMessages if enabled, #messages will be set to the resulting messages
+ *                    div after being modified. If #messages already exists it will
+ *                    be removed and replaced by the new div.
+ * @param fixedAt if setMessages is enabled, maintain scrollbar at the specified
+ *                position (otherwise modifying #messages would result in
+ *                changing the position of the scrollbar)
+ */
+function printHistoryPart(messages_div, fixedAt)
+{
+    if (historyBufferIndex === historyBuffer.length) {
+        return
+    }
+
+    /* If first element is a spinner, remove it */
+    if (messages_div.firstChild && messages_div.firstChild.id === "lazyloading-icon") {
+        messages_div.removeChild(messages_div.firstChild)
+    }
+
+    /* Elements are appended to a wrapper div. This div has no style
+       properties, it allows us to add all messages at once to the main
+       messages div. */
+    var block_wrapper = document.createElement("div")
+
+    messages_div.prepend(block_wrapper)
+
+    for (var i = 0; i < scrollBuffer && historyBufferIndex < historyBuffer.length; ++historyBufferIndex && ++i) {
+        // TODO on-screen messages should be removed from the buffer
+        addOrUpdateMessage(historyBuffer[historyBuffer.length - 1 - historyBufferIndex], true, false, block_wrapper)
+    }
+
+    var lastMessage = block_wrapper.lastChild
+
+    updateSequencing(lastMessage, getNextInteraction(lastMessage))
+
+    var absoluteLastMessage = messages_div.lastChild.lastChild
+    if (absoluteLastMessage) {
+        absoluteLastMessage.classList.add("last_message")
+    }
+
+    /* Add ellipsis (...) at the top if there are still messages to load */
+    if (historyBufferIndex < historyBuffer.length) {
+        var llicon = document.createElement("span")
+        llicon.id = "lazyloading-icon"
+        llicon.innerHTML = "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"#888888\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"/></svg>"
+        messages_div.prepend(llicon)
+    }
+
+    if (fixedAt !== undefined) {
+        /* update scrollbar position to take text-message -related
+           scrollHeight changes in account (not necessary to wait
+           for DOM redisplay in this case). Changes due to image
+           messages are handled in their onLoad callbacks. */
+        back_to_scroll(fixedAt)
+        /* schedule a scrollbar position update for changes which
+           are neither handled by the previous call nor by onLoad
+           callbacks. This call is necessary but not sufficient,
+           dropping the previous call would result in visual
+           glitches during initial load. */
+        setTimeout(function() {back_to_scroll(fixedAt)}, 0)
+    }
+
+    if (!imagesLoadingCounter) {
+        setTimeout(check_lazy_loading, 0)
+    }
+}
+
+function hideBody()
+{
+    if (!document.body.classList.contains('fade')) {
+        document.body.classList.add('fade');
+    }
+}
+
+/**
+ * Set history buffer, initialize messages div and display a first batch
+ * of messages.
+ *
+ * Make sure that enough messages are displayed to fill initialScrollBufferFactor
+ * screens of messages (if enough messages are present in the conversation)
+ *
+ * @param messages_array should contain history to be printed
+ */
+/* exported printHistory */
+function printHistory(messages_array)
+{
+    hideBody()
+
+    historyBuffer = messages_array
+    historyBufferIndex = 0
+
+    isInitialLoading = true
+    printHistoryPart(messages, 0)
+    isInitialLoading = false
+
+    document.body.classList.remove('fade');
+}
+
+/**
+ * Set the image for a given sender
+ * set_sender_image object should contain the following keys:
+ * - sender: the name of the sender
+ * - sender_image: base64 png encoding of the sender image
+ *
+ * @param set_sender_image_object sender image object as previously described
+ */
+/* exported setSenderImage */
+function setSenderImage(set_sender_image_object)
+{
+    var sender_contact_method = set_sender_image_object["sender_contact_method"],
+        sender_image = set_sender_image_object["sender_image"],
+        sender_image_id = "sender_image_" + sender_contact_method,
+        currentSenderImage = document.getElementById(sender_image_id), // Remove the currently set sender image
+        style
+
+    if (currentSenderImage) {
+        currentSenderImage.parentNode.removeChild(currentSenderImage)
+    }
+
+    // Create a new style element
+    style = document.createElement("style")
+
+    style.type = "text/css"
+    style.id = sender_image_id
+    style.innerHTML = "." + sender_image_id + " {content: url(data:image/png;base64," + sender_image + ");height: 32px;width: 32px;}"
+    document.head.appendChild(style)
+}
diff --git a/web/linkify-html.js b/web/linkify-html.js
new file mode 100644
index 0000000..1e5090c
--- /dev/null
+++ b/web/linkify-html.js
@@ -0,0 +1,827 @@
+/*
+ * Copyright (c) 2016 SoapBox Innovations Inc.
+ *
+ * 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.
+*/
+
+'use strict';
+
+;(function (window, linkify) {
+  var linkifyHtml = function (linkify) {
+    'use strict';
+
+    var HTML5NamedCharRefs = {};
+
+    function EntityParser(named) {
+      this.named = named;
+    }
+
+    var HEXCHARCODE = /^#[xX]([A-Fa-f0-9]+)$/;
+    var CHARCODE = /^#([0-9]+)$/;
+    var NAMED = /^([A-Za-z0-9]+)$/;
+
+    EntityParser.prototype.parse = function (entity) {
+      if (!entity) {
+        return;
+      }
+      var matches = entity.match(HEXCHARCODE);
+      if (matches) {
+        return '&#x' + matches[1] + ';';
+      }
+      matches = entity.match(CHARCODE);
+      if (matches) {
+        return '&#' + matches[1] + ';';
+      }
+      matches = entity.match(NAMED);
+      if (matches) {
+        return '&' + matches[1] + ';';
+      }
+    };
+
+    var WSP = /[\t\n\f ]/;
+    var ALPHA = /[A-Za-z]/;
+    var CRLF = /\r\n?/g;
+
+    function isSpace(char) {
+      return WSP.test(char);
+    }
+
+    function isAlpha(char) {
+      return ALPHA.test(char);
+    }
+
+    function preprocessInput(input) {
+      return input.replace(CRLF, "\n");
+    }
+
+    function EventedTokenizer(delegate, entityParser) {
+      this.delegate = delegate;
+      this.entityParser = entityParser;
+
+      this.state = null;
+      this.input = null;
+
+      this.index = -1;
+      this.line = -1;
+      this.column = -1;
+      this.tagLine = -1;
+      this.tagColumn = -1;
+
+      this.reset();
+    }
+
+    EventedTokenizer.prototype = {
+      reset: function reset() {
+        this.state = 'beforeData';
+        this.input = '';
+
+        this.index = 0;
+        this.line = 1;
+        this.column = 0;
+
+        this.tagLine = -1;
+        this.tagColumn = -1;
+
+        this.delegate.reset();
+      },
+
+      tokenize: function tokenize(input) {
+        this.reset();
+        this.tokenizePart(input);
+        this.tokenizeEOF();
+      },
+
+      tokenizePart: function tokenizePart(input) {
+        this.input += preprocessInput(input);
+
+        while (this.index < this.input.length) {
+          this.states[this.state].call(this);
+        }
+      },
+
+      tokenizeEOF: function tokenizeEOF() {
+        this.flushData();
+      },
+
+      flushData: function flushData() {
+        if (this.state === 'data') {
+          this.delegate.finishData();
+          this.state = 'beforeData';
+        }
+      },
+
+      peek: function peek() {
+        return this.input.charAt(this.index);
+      },
+
+      consume: function consume() {
+        var char = this.peek();
+
+        this.index++;
+
+        if (char === "\n") {
+          this.line++;
+          this.column = 0;
+        } else {
+          this.column++;
+        }
+
+        return char;
+      },
+
+      consumeCharRef: function consumeCharRef() {
+        var endIndex = this.input.indexOf(';', this.index);
+        if (endIndex === -1) {
+          return;
+        }
+        var entity = this.input.slice(this.index, endIndex);
+        var chars = this.entityParser.parse(entity);
+        if (chars) {
+          this.index = endIndex + 1;
+          return chars;
+        }
+      },
+
+      markTagStart: function markTagStart() {
+        this.tagLine = this.line;
+        this.tagColumn = this.column;
+      },
+
+      states: {
+        beforeData: function beforeData() {
+          var char = this.peek();
+
+          if (char === "<") {
+            this.state = 'tagOpen';
+            this.markTagStart();
+            this.consume();
+          } else {
+            this.state = 'data';
+            this.delegate.beginData();
+          }
+        },
+
+        data: function data() {
+          var char = this.peek();
+
+          if (char === "<") {
+            this.delegate.finishData();
+            this.state = 'tagOpen';
+            this.markTagStart();
+            this.consume();
+          } else if (char === "&") {
+            this.consume();
+            this.delegate.appendToData(this.consumeCharRef() || "&");
+          } else {
+            this.consume();
+            this.delegate.appendToData(char);
+          }
+        },
+
+        tagOpen: function tagOpen() {
+          var char = this.consume();
+
+          if (char === "!") {
+            this.state = 'markupDeclaration';
+          } else if (char === "/") {
+            this.state = 'endTagOpen';
+          } else if (isAlpha(char)) {
+            this.state = 'tagName';
+            this.delegate.beginStartTag();
+            this.delegate.appendToTagName(char.toLowerCase());
+          }
+        },
+
+        markupDeclaration: function markupDeclaration() {
+          var char = this.consume();
+
+          if (char === "-" && this.input.charAt(this.index) === "-") {
+            this.index++;
+            this.state = 'commentStart';
+            this.delegate.beginComment();
+          }
+        },
+
+        commentStart: function commentStart() {
+          var char = this.consume();
+
+          if (char === "-") {
+            this.state = 'commentStartDash';
+          } else if (char === ">") {
+            this.delegate.finishComment();
+            this.state = 'beforeData';
+          } else {
+            this.delegate.appendToCommentData(char);
+            this.state = 'comment';
+          }
+        },
+
+        commentStartDash: function commentStartDash() {
+          var char = this.consume();
+
+          if (char === "-") {
+            this.state = 'commentEnd';
+          } else if (char === ">") {
+            this.delegate.finishComment();
+            this.state = 'beforeData';
+          } else {
+            this.delegate.appendToCommentData("-");
+            this.state = 'comment';
+          }
+        },
+
+        comment: function comment() {
+          var char = this.consume();
+
+          if (char === "-") {
+            this.state = 'commentEndDash';
+          } else {
+            this.delegate.appendToCommentData(char);
+          }
+        },
+
+        commentEndDash: function commentEndDash() {
+          var char = this.consume();
+
+          if (char === "-") {
+            this.state = 'commentEnd';
+          } else {
+            this.delegate.appendToCommentData("-" + char);
+            this.state = 'comment';
+          }
+        },
+
+        commentEnd: function commentEnd() {
+          var char = this.consume();
+
+          if (char === ">") {
+            this.delegate.finishComment();
+            this.state = 'beforeData';
+          } else {
+            this.delegate.appendToCommentData("--" + char);
+            this.state = 'comment';
+          }
+        },
+
+        tagName: function tagName() {
+          var char = this.consume();
+
+          if (isSpace(char)) {
+            this.state = 'beforeAttributeName';
+          } else if (char === "/") {
+            this.state = 'selfClosingStartTag';
+          } else if (char === ">") {
+            this.delegate.finishTag();
+            this.state = 'beforeData';
+          } else {
+            this.delegate.appendToTagName(char);
+          }
+        },
+
+        beforeAttributeName: function beforeAttributeName() {
+          var char = this.consume();
+
+          if (isSpace(char)) {
+            return;
+          } else if (char === "/") {
+            this.state = 'selfClosingStartTag';
+          } else if (char === ">") {
+            this.delegate.finishTag();
+            this.state = 'beforeData';
+          } else {
+            this.state = 'attributeName';
+            this.delegate.beginAttribute();
+            this.delegate.appendToAttributeName(char);
+          }
+        },
+
+        attributeName: function attributeName() {
+          var char = this.consume();
+
+          if (isSpace(char)) {
+            this.state = 'afterAttributeName';
+          } else if (char === "/") {
+            this.delegate.beginAttributeValue(false);
+            this.delegate.finishAttributeValue();
+            this.state = 'selfClosingStartTag';
+          } else if (char === "=") {
+            this.state = 'beforeAttributeValue';
+          } else if (char === ">") {
+            this.delegate.beginAttributeValue(false);
+            this.delegate.finishAttributeValue();
+            this.delegate.finishTag();
+            this.state = 'beforeData';
+          } else {
+            this.delegate.appendToAttributeName(char);
+          }
+        },
+
+        afterAttributeName: function afterAttributeName() {
+          var char = this.consume();
+
+          if (isSpace(char)) {
+            return;
+          } else if (char === "/") {
+            this.delegate.beginAttributeValue(false);
+            this.delegate.finishAttributeValue();
+            this.state = 'selfClosingStartTag';
+          } else if (char === "=") {
+            this.state = 'beforeAttributeValue';
+          } else if (char === ">") {
+            this.delegate.beginAttributeValue(false);
+            this.delegate.finishAttributeValue();
+            this.delegate.finishTag();
+            this.state = 'beforeData';
+          } else {
+            this.delegate.beginAttributeValue(false);
+            this.delegate.finishAttributeValue();
+            this.state = 'attributeName';
+            this.delegate.beginAttribute();
+            this.delegate.appendToAttributeName(char);
+          }
+        },
+
+        beforeAttributeValue: function beforeAttributeValue() {
+          var char = this.consume();
+
+          if (isSpace(char)) {} else if (char === '"') {
+            this.state = 'attributeValueDoubleQuoted';
+            this.delegate.beginAttributeValue(true);
+          } else if (char === "'") {
+            this.state = 'attributeValueSingleQuoted';
+            this.delegate.beginAttributeValue(true);
+          } else if (char === ">") {
+            this.delegate.beginAttributeValue(false);
+            this.delegate.finishAttributeValue();
+            this.delegate.finishTag();
+            this.state = 'beforeData';
+          } else {
+            this.state = 'attributeValueUnquoted';
+            this.delegate.beginAttributeValue(false);
+            this.delegate.appendToAttributeValue(char);
+          }
+        },
+
+        attributeValueDoubleQuoted: function attributeValueDoubleQuoted() {
+          var char = this.consume();
+
+          if (char === '"') {
+            this.delegate.finishAttributeValue();
+            this.state = 'afterAttributeValueQuoted';
+          } else if (char === "&") {
+            this.delegate.appendToAttributeValue(this.consumeCharRef('"') || "&");
+          } else {
+            this.delegate.appendToAttributeValue(char);
+          }
+        },
+
+        attributeValueSingleQuoted: function attributeValueSingleQuoted() {
+          var char = this.consume();
+
+          if (char === "'") {
+            this.delegate.finishAttributeValue();
+            this.state = 'afterAttributeValueQuoted';
+          } else if (char === "&") {
+            this.delegate.appendToAttributeValue(this.consumeCharRef("'") || "&");
+          } else {
+            this.delegate.appendToAttributeValue(char);
+          }
+        },
+
+        attributeValueUnquoted: function attributeValueUnquoted() {
+          var char = this.consume();
+
+          if (isSpace(char)) {
+            this.delegate.finishAttributeValue();
+            this.state = 'beforeAttributeName';
+          } else if (char === "&") {
+            this.delegate.appendToAttributeValue(this.consumeCharRef(">") || "&");
+          } else if (char === ">") {
+            this.delegate.finishAttributeValue();
+            this.delegate.finishTag();
+            this.state = 'beforeData';
+          } else {
+            this.delegate.appendToAttributeValue(char);
+          }
+        },
+
+        afterAttributeValueQuoted: function afterAttributeValueQuoted() {
+          var char = this.peek();
+
+          if (isSpace(char)) {
+            this.consume();
+            this.state = 'beforeAttributeName';
+          } else if (char === "/") {
+            this.consume();
+            this.state = 'selfClosingStartTag';
+          } else if (char === ">") {
+            this.consume();
+            this.delegate.finishTag();
+            this.state = 'beforeData';
+          } else {
+            this.state = 'beforeAttributeName';
+          }
+        },
+
+        selfClosingStartTag: function selfClosingStartTag() {
+          var char = this.peek();
+
+          if (char === ">") {
+            this.consume();
+            this.delegate.markTagAsSelfClosing();
+            this.delegate.finishTag();
+            this.state = 'beforeData';
+          } else {
+            this.state = 'beforeAttributeName';
+          }
+        },
+
+        endTagOpen: function endTagOpen() {
+          var char = this.consume();
+
+          if (isAlpha(char)) {
+            this.state = 'tagName';
+            this.delegate.beginEndTag();
+            this.delegate.appendToTagName(char.toLowerCase());
+          }
+        }
+      }
+    };
+
+    function Tokenizer(entityParser, options) {
+      this.token = null;
+      this.startLine = 1;
+      this.startColumn = 0;
+      this.options = options || {};
+      this.tokenizer = new EventedTokenizer(this, entityParser);
+    }
+
+    Tokenizer.prototype = {
+      tokenize: function tokenize(input) {
+        this.tokens = [];
+        this.tokenizer.tokenize(input);
+        return this.tokens;
+      },
+
+      tokenizePart: function tokenizePart(input) {
+        this.tokens = [];
+        this.tokenizer.tokenizePart(input);
+        return this.tokens;
+      },
+
+      tokenizeEOF: function tokenizeEOF() {
+        this.tokens = [];
+        this.tokenizer.tokenizeEOF();
+        return this.tokens[0];
+      },
+
+      reset: function reset() {
+        this.token = null;
+        this.startLine = 1;
+        this.startColumn = 0;
+      },
+
+      addLocInfo: function addLocInfo() {
+        if (this.options.loc) {
+          this.token.loc = {
+            start: {
+              line: this.startLine,
+              column: this.startColumn
+            },
+            end: {
+              line: this.tokenizer.line,
+              column: this.tokenizer.column
+            }
+          };
+        }
+        this.startLine = this.tokenizer.line;
+        this.startColumn = this.tokenizer.column;
+      },
+
+      // Data
+
+      beginData: function beginData() {
+        this.token = {
+          type: 'Chars',
+          chars: ''
+        };
+        this.tokens.push(this.token);
+      },
+
+      appendToData: function appendToData(char) {
+        this.token.chars += char;
+      },
+
+      finishData: function finishData() {
+        this.addLocInfo();
+      },
+
+      // Comment
+
+      beginComment: function beginComment() {
+        this.token = {
+          type: 'Comment',
+          chars: ''
+        };
+        this.tokens.push(this.token);
+      },
+
+      appendToCommentData: function appendToCommentData(char) {
+        this.token.chars += char;
+      },
+
+      finishComment: function finishComment() {
+        this.addLocInfo();
+      },
+
+      // Tags - basic
+
+      beginStartTag: function beginStartTag() {
+        this.token = {
+          type: 'StartTag',
+          tagName: '',
+          attributes: [],
+          selfClosing: false
+        };
+        this.tokens.push(this.token);
+      },
+
+      beginEndTag: function beginEndTag() {
+        this.token = {
+          type: 'EndTag',
+          tagName: ''
+        };
+        this.tokens.push(this.token);
+      },
+
+      finishTag: function finishTag() {
+        this.addLocInfo();
+      },
+
+      markTagAsSelfClosing: function markTagAsSelfClosing() {
+        this.token.selfClosing = true;
+      },
+
+      // Tags - name
+
+      appendToTagName: function appendToTagName(char) {
+        this.token.tagName += char;
+      },
+
+      // Tags - attributes
+
+      beginAttribute: function beginAttribute() {
+        this._currentAttribute = ["", "", null];
+        this.token.attributes.push(this._currentAttribute);
+      },
+
+      appendToAttributeName: function appendToAttributeName(char) {
+        this._currentAttribute[0] += char;
+      },
+
+      beginAttributeValue: function beginAttributeValue(isQuoted) {
+        this._currentAttribute[2] = isQuoted;
+      },
+
+      appendToAttributeValue: function appendToAttributeValue(char) {
+        this._currentAttribute[1] = this._currentAttribute[1] || "";
+        this._currentAttribute[1] += char;
+      },
+
+      finishAttributeValue: function finishAttributeValue() {}
+    };
+
+    function tokenize(input, options) {
+      var tokenizer = new Tokenizer(new EntityParser(HTML5NamedCharRefs), options);
+      return tokenizer.tokenize(input);
+    }
+
+    var HTML5Tokenizer = {
+      HTML5NamedCharRefs: HTML5NamedCharRefs,
+      EntityParser: EntityParser,
+      EventedTokenizer: EventedTokenizer,
+      Tokenizer: Tokenizer,
+      tokenize: tokenize
+    };
+
+    var options = linkify.options;
+    var Options = options.Options;
+
+
+    var StartTag = 'StartTag';
+    var EndTag = 'EndTag';
+    var Chars = 'Chars';
+    var Comment = 'Comment';
+
+    /**
+    	`tokens` and `token` in this section refer to tokens generated by the HTML
+    	parser.
+    */
+    function linkifyHtml(str) {
+      var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+      var tokens = HTML5Tokenizer.tokenize(str);
+      var linkifiedTokens = [];
+      var linkified = [];
+      var i;
+
+      opts = new Options(opts);
+
+      // Linkify the tokens given by the parser
+      for (i = 0; i < tokens.length; i++) {
+        var token = tokens[i];
+
+        if (token.type === StartTag) {
+          linkifiedTokens.push(token);
+
+          // Ignore all the contents of ignored tags
+          var tagName = token.tagName.toUpperCase();
+          var isIgnored = tagName === 'A' || options.contains(opts.ignoreTags, tagName);
+          if (!isIgnored) {
+            continue;
+          }
+
+          var preskipLen = linkifiedTokens.length;
+          skipTagTokens(tagName, tokens, ++i, linkifiedTokens);
+          i += linkifiedTokens.length - preskipLen - 1;
+          continue;
+        } else if (token.type !== Chars) {
+          // Skip this token, it's not important
+          linkifiedTokens.push(token);
+          continue;
+        }
+
+        // Valid text token, linkify it!
+        var linkifedChars = linkifyChars(token.chars, opts);
+        linkifiedTokens.push.apply(linkifiedTokens, linkifedChars);
+      }
+
+      // Convert the tokens back into a string
+      for (i = 0; i < linkifiedTokens.length; i++) {
+        var _token = linkifiedTokens[i];
+        switch (_token.type) {
+          case StartTag:
+            var link = '<' + _token.tagName;
+            if (_token.attributes.length > 0) {
+              var attrs = attrsToStrings(_token.attributes);
+              link += ' ' + attrs.join(' ');
+            }
+            link += '>';
+            linkified.push(link);
+            break;
+          case EndTag:
+            linkified.push('</' + _token.tagName + '>');
+            break;
+          case Chars:
+            linkified.push(escapeText(_token.chars));
+            break;
+          case Comment:
+            linkified.push('<!--' + escapeText(_token.chars) + '-->');
+            break;
+        }
+      }
+
+      return linkified.join('');
+    }
+
+    /**
+    	`tokens` and `token` in this section referes to tokens returned by
+    	`linkify.tokenize`. `linkified` will contain HTML Parser-style tokens
+    */
+    function linkifyChars(str, opts) {
+      var tokens = linkify.tokenize(str);
+      var result = [];
+
+      for (var i = 0; i < tokens.length; i++) {
+        var token = tokens[i];
+
+        if (token.type === 'nl' && opts.nl2br) {
+          result.push({
+            type: StartTag,
+            tagName: 'br',
+            attributes: [],
+            selfClosing: true
+          });
+          continue;
+        } else if (!token.isLink || !opts.check(token)) {
+          result.push({ type: Chars, chars: token.toString() });
+          continue;
+        }
+
+        var _opts$resolve = opts.resolve(token);
+
+        var href = _opts$resolve.href;
+        var formatted = _opts$resolve.formatted;
+        var formattedHref = _opts$resolve.formattedHref;
+        var tagName = _opts$resolve.tagName;
+        var className = _opts$resolve.className;
+        var target = _opts$resolve.target;
+        var attributes = _opts$resolve.attributes;
+
+        // Build up attributes
+
+        var attributeArray = [['href', formattedHref]];
+
+        if (className) {
+          attributeArray.push(['class', className]);
+        }
+
+        if (target) {
+          attributeArray.push(['target', target]);
+        }
+
+        for (var attr in attributes) {
+          attributeArray.push([attr, attributes[attr]]);
+        }
+
+        // Add the required tokens
+        result.push({
+          type: StartTag,
+          tagName: tagName,
+          attributes: attributeArray,
+          selfClosing: false
+        });
+        result.push({ type: Chars, chars: formatted });
+        result.push({ type: EndTag, tagName: tagName });
+      }
+
+      return result;
+    }
+
+    /**
+    	Returns a list of tokens skipped until the closing tag of tagName.
+
+    	* `tagName` is the closing tag which will prompt us to stop skipping
+    	* `tokens` is the array of tokens generated by HTML5Tokenizer which
+    	* `i` is the index immediately after the opening tag to skip
+    	* `skippedTokens` is an array which skipped tokens are being pushed into
+
+    	Caveats
+
+    	* Assumes that i is the first token after the given opening tagName
+    	* The closing tag will be skipped, but nothing after it
+    	* Will track whether there is a nested tag of the same type
+    */
+    function skipTagTokens(tagName, tokens, i, skippedTokens) {
+
+      // number of tokens of this type on the [fictional] stack
+      var stackCount = 1;
+
+      while (i < tokens.length && stackCount > 0) {
+        var token = tokens[i];
+        if (token.type === StartTag && token.tagName.toUpperCase() === tagName) {
+          // Nested tag of the same type, "add to stack"
+          stackCount++;
+        } else if (token.type === EndTag && token.tagName.toUpperCase() === tagName) {
+          // Closing tag
+          stackCount--;
+        }
+        skippedTokens.push(token);
+        i++;
+      }
+
+      // Note that if stackCount > 0 here, the HTML is probably invalid
+      return skippedTokens;
+    }
+
+    function escapeText(text) {
+      // Not required, HTML tokenizer ensures this occurs properly
+      return text;
+    }
+
+    function escapeAttr(attr) {
+      return attr.replace(/"/g, '&quot;');
+    }
+
+    function attrsToStrings(attrs) {
+      var attrStrs = [];
+      for (var i = 0; i < attrs.length; i++) {
+        var _attrs$i = attrs[i];
+        var name = _attrs$i[0];
+        var value = _attrs$i[1];
+
+        attrStrs.push(name + '="' + escapeAttr(value) + '"');
+      }
+      return attrStrs;
+    }
+
+    return linkifyHtml;
+  }(linkify);
+  window.linkifyHtml = linkifyHtml;
+})(window, linkify);
diff --git a/web/linkify-string.js b/web/linkify-string.js
new file mode 100644
index 0000000..699a94d
--- /dev/null
+++ b/web/linkify-string.js
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2016 SoapBox Innovations Inc.
+ *
+ * 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.
+*/
+
+'use strict';
+
+;(function (window, linkify) {
+	var linkifyString = function (linkify) {
+		'use strict';
+
+		/**
+  	Convert strings of text into linkable HTML text
+  */
+
+		var tokenize = linkify.tokenize;
+		var options = linkify.options;
+		var Options = options.Options;
+
+
+		function escapeText(text) {
+			return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+		}
+
+		function escapeAttr(href) {
+			return href.replace(/"/g, '&quot;');
+		}
+
+		function attributesToString(attributes) {
+			if (!attributes) {
+				return '';
+			}
+			var result = [];
+
+			for (var attr in attributes) {
+				var val = attributes[attr] + '';
+				result.push(attr + '="' + escapeAttr(val) + '"');
+			}
+			return result.join(' ');
+		}
+
+		function linkifyStr(str) {
+			var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+			opts = new Options(opts);
+
+			var tokens = tokenize(str);
+			var result = [];
+
+			for (var i = 0; i < tokens.length; i++) {
+				var token = tokens[i];
+
+				if (token.type === 'nl' && opts.nl2br) {
+					result.push('<br>\n');
+					continue;
+				} else if (!token.isLink || !opts.check(token)) {
+					result.push(escapeText(token.toString()));
+					continue;
+				}
+
+				var _opts$resolve = opts.resolve(token);
+
+				var formatted = _opts$resolve.formatted;
+				var formattedHref = _opts$resolve.formattedHref;
+				var tagName = _opts$resolve.tagName;
+				var className = _opts$resolve.className;
+				var target = _opts$resolve.target;
+				var attributes = _opts$resolve.attributes;
+
+
+				var link = '<' + tagName + ' href="' + escapeAttr(formattedHref) + '"';
+
+				if (className) {
+					link += ' class="' + escapeAttr(className) + '"';
+				}
+
+				if (target) {
+					link += ' target="' + escapeAttr(target) + '"';
+				}
+
+				if (attributes) {
+					link += ' ' + attributesToString(attributes);
+				}
+
+				link += '>' + escapeText(formatted) + '</' + tagName + '>';
+				result.push(link);
+			}
+
+			return result.join('');
+		}
+
+		if (!String.prototype.linkify) {
+			String.prototype.linkify = function (opts) {
+				return linkifyStr(this, opts);
+			};
+		}
+
+		return linkifyStr;
+	}(linkify);
+	window.linkifyStr = linkifyString;
+})(window, linkify);
diff --git a/web/linkify.js b/web/linkify.js
new file mode 100644
index 0000000..15dc03b
--- /dev/null
+++ b/web/linkify.js
@@ -0,0 +1,1271 @@
+/*
+ * Copyright (c) 2016 SoapBox Innovations Inc.
+ *
+ * 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.
+*/
+
+;(function () {
+'use strict';
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
+
+(function (exports) {
+	'use strict';
+
+	function inherits(parent, child) {
+		var props = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+
+		var extended = Object.create(parent.prototype);
+		for (var p in props) {
+			extended[p] = props[p];
+		}
+		extended.constructor = child;
+		child.prototype = extended;
+		return child;
+	}
+
+	var defaults = {
+		defaultProtocol: 'http',
+		events: null,
+		format: noop,
+		formatHref: noop,
+		nl2br: false,
+		tagName: 'a',
+		target: typeToTarget,
+		validate: true,
+		ignoreTags: [],
+		attributes: null,
+		className: 'linkified' };
+
+	function Options(opts) {
+		opts = opts || {};
+
+		this.defaultProtocol = opts.defaultProtocol || defaults.defaultProtocol;
+		this.events = opts.events || defaults.events;
+		this.format = opts.format || defaults.format;
+		this.formatHref = opts.formatHref || defaults.formatHref;
+		this.nl2br = opts.nl2br || defaults.nl2br;
+		this.tagName = opts.tagName || defaults.tagName;
+		this.target = opts.target || defaults.target;
+		this.validate = opts.validate || defaults.validate;
+		this.ignoreTags = [];
+
+		// linkAttributes and linkClass is deprecated
+		this.attributes = opts.attributes || opts.linkAttributes || defaults.attributes;
+		this.className = opts.className || opts.linkClass || defaults.className;
+
+		// Make all tags names upper case
+
+		var ignoredTags = opts.ignoreTags || defaults.ignoreTags;
+		for (var i = 0; i < ignoredTags.length; i++) {
+			this.ignoreTags.push(ignoredTags[i].toUpperCase());
+		}
+	}
+
+	Options.prototype = {
+		/**
+   * Given the token, return all options for how it should be displayed
+   */
+		resolve: function resolve(token) {
+			var href = token.toHref(this.defaultProtocol);
+			return {
+				formatted: this.get('format', token.toString(), token),
+				formattedHref: this.get('formatHref', href, token),
+				tagName: this.get('tagName', href, token),
+				className: this.get('className', href, token),
+				target: this.get('target', href, token),
+				events: this.getObject('events', href, token),
+				attributes: this.getObject('attributes', href, token)
+			};
+		},
+
+
+		/**
+   * Returns true or false based on whether a token should be displayed as a
+   * link based on the user options. By default,
+   */
+		check: function check(token) {
+			return this.get('validate', token.toString(), token);
+		},
+
+
+		// Private methods
+
+		/**
+   * Resolve an option's value based on the value of the option and the given
+   * params.
+   * @param [String] key Name of option to use
+   * @param operator will be passed to the target option if it's method
+   * @param [MultiToken] token The token from linkify.tokenize
+   */
+		get: function get(key, operator, token) {
+			var option = this[key];
+
+			if (!option) {
+				return option;
+			}
+
+			switch (typeof option === 'undefined' ? 'undefined' : _typeof(option)) {
+				case 'function':
+					return option(operator, token.type);
+				case 'object':
+					var optionValue = option[token.type] || defaults[key];
+					return typeof optionValue === 'function' ? optionValue(operator, token.type) : optionValue;
+			}
+
+			return option;
+		},
+		getObject: function getObject(key, operator, token) {
+			var option = this[key];
+			return typeof option === 'function' ? option(operator, token.type) : option;
+		}
+	};
+
+	/**
+  * Quick indexOf replacement for checking the ignoreTags option
+  */
+	function contains(arr, value) {
+		for (var i = 0; i < arr.length; i++) {
+			if (arr[i] === value) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	function noop(val) {
+		return val;
+	}
+
+	function typeToTarget(href, type) {
+		return type === 'url' ? '_blank' : null;
+	}
+
+	var options = Object.freeze({
+		defaults: defaults,
+		Options: Options,
+		contains: contains
+	});
+
+	function createStateClass() {
+		return function (tClass) {
+			this.j = [];
+			this.T = tClass || null;
+		};
+	}
+
+	/**
+ 	A simple state machine that can emit token classes
+
+ 	The `j` property in this class refers to state jumps. It's a
+ 	multidimensional array where for each element:
+
+ 	* index [0] is a symbol or class of symbols to transition to.
+ 	* index [1] is a State instance which matches
+
+ 	The type of symbol will depend on the target implementation for this class.
+ 	In Linkify, we have a two-stage scanner. Each stage uses this state machine
+ 	but with a slighly different (polymorphic) implementation.
+
+ 	The `T` property refers to the token class.
+
+ 	TODO: Can the `on` and `next` methods be combined?
+
+ 	@class BaseState
+ */
+	var BaseState = createStateClass();
+	BaseState.prototype = {
+		defaultTransition: false,
+
+		/**
+  	@method constructor
+  	@param {Class} tClass Pass in the kind of token to emit if there are
+  		no jumps after this state and the state is accepting.
+  */
+
+		/**
+  	On the given symbol(s), this machine should go to the given state
+  		@method on
+  	@param {Array|Mixed} symbol
+  	@param {BaseState} state Note that the type of this state should be the
+  		same as the current instance (i.e., don't pass in a different
+  		subclass)
+  */
+		on: function on(symbol, state) {
+			if (symbol instanceof Array) {
+				for (var i = 0; i < symbol.length; i++) {
+					this.j.push([symbol[i], state]);
+				}
+				return this;
+			}
+			this.j.push([symbol, state]);
+			return this;
+		},
+
+
+		/**
+  	Given the next item, returns next state for that item
+  	@method next
+  	@param {Mixed} item Should be an instance of the symbols handled by
+  		this particular machine.
+  	@return {State} state Returns false if no jumps are available
+  */
+		next: function next(item) {
+			for (var i = 0; i < this.j.length; i++) {
+				var jump = this.j[i];
+				var symbol = jump[0]; // Next item to check for
+				var state = jump[1]; // State to jump to if items match
+
+				// compare item with symbol
+				if (this.test(item, symbol)) {
+					return state;
+				}
+			}
+
+			// Nowhere left to jump!
+			return this.defaultTransition;
+		},
+
+
+		/**
+  	Does this state accept?
+  	`true` only of `this.T` exists
+  		@method accepts
+  	@return {Boolean}
+  */
+		accepts: function accepts() {
+			return !!this.T;
+		},
+
+
+		/**
+  	Determine whether a given item "symbolizes" the symbol, where symbol is
+  	a class of items handled by this state machine.
+  		This method should be overriden in extended classes.
+  		@method test
+  	@param {Mixed} item Does this item match the given symbol?
+  	@param {Mixed} symbol
+  	@return {Boolean}
+  */
+		test: function test(item, symbol) {
+			return item === symbol;
+		},
+
+
+		/**
+  	Emit the token for this State (just return it in this case)
+  	If this emits a token, this instance is an accepting state
+  	@method emit
+  	@return {Class} T
+  */
+		emit: function emit() {
+			return this.T;
+		}
+	};
+
+	/**
+ 	State machine for string-based input
+
+ 	@class CharacterState
+ 	@extends BaseState
+ */
+	var CharacterState = inherits(BaseState, createStateClass(), {
+		/**
+  	Does the given character match the given character or regular
+  	expression?
+  		@method test
+  	@param {String} char
+  	@param {String|RegExp} charOrRegExp
+  	@return {Boolean}
+  */
+		test: function test(character, charOrRegExp) {
+			return character === charOrRegExp || charOrRegExp instanceof RegExp && charOrRegExp.test(character);
+		}
+	});
+
+	/**
+ 	State machine for input in the form of TextTokens
+
+ 	@class TokenState
+ 	@extends BaseState
+ */
+	var State = inherits(BaseState, createStateClass(), {
+
+		/**
+   * Similar to `on`, but returns the state the results in the transition from
+   * the given item
+   * @method jump
+   * @param {Mixed} item
+   * @param {Token} [token]
+   * @return state
+   */
+		jump: function jump(token) {
+			var tClass = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+			var state = this.next(new token('')); // dummy temp token
+			if (state === this.defaultTransition) {
+				// Make a new state!
+				state = new this.constructor(tClass);
+				this.on(token, state);
+			} else if (tClass) {
+				state.T = tClass;
+			}
+			return state;
+		},
+
+
+		/**
+  	Is the given token an instance of the given token class?
+  		@method test
+  	@param {TextToken} token
+  	@param {Class} tokenClass
+  	@return {Boolean}
+  */
+		test: function test(token, tokenClass) {
+			return token instanceof tokenClass;
+		}
+	});
+
+	/**
+ 	Given a non-empty target string, generates states (if required) for each
+ 	consecutive substring of characters in str starting from the beginning of
+ 	the string. The final state will have a special value, as specified in
+ 	options. All other "in between" substrings will have a default end state.
+
+ 	This turns the state machine into a Trie-like data structure (rather than a
+ 	intelligently-designed DFA).
+
+ 	Note that I haven't really tried these with any strings other than
+ 	DOMAIN.
+
+ 	@param {String} str
+ 	@param {CharacterState} start State to jump from the first character
+ 	@param {Class} endToken Token class to emit when the given string has been
+ 		matched and no more jumps exist.
+ 	@param {Class} defaultToken "Filler token", or which token type to emit when
+ 		we don't have a full match
+ 	@return {Array} list of newly-created states
+ */
+	function stateify(str, start, endToken, defaultToken) {
+		var i = 0,
+		    len = str.length,
+		    state = start,
+		    newStates = [],
+		    nextState = void 0;
+
+		// Find the next state without a jump to the next character
+		while (i < len && (nextState = state.next(str[i]))) {
+			state = nextState;
+			i++;
+		}
+
+		if (i >= len) {
+			return [];
+		} // no new tokens were added
+
+		while (i < len - 1) {
+			nextState = new CharacterState(defaultToken);
+			newStates.push(nextState);
+			state.on(str[i], nextState);
+			state = nextState;
+			i++;
+		}
+
+		nextState = new CharacterState(endToken);
+		newStates.push(nextState);
+		state.on(str[len - 1], nextState);
+
+		return newStates;
+	}
+
+	function createTokenClass() {
+		return function (value) {
+			if (value) {
+				this.v = value;
+			}
+		};
+	}
+
+	/******************************************************************************
+ 	Text Tokens
+ 	Tokens composed of strings
+ ******************************************************************************/
+
+	/**
+ 	Abstract class used for manufacturing text tokens.
+ 	Pass in the value this token represents
+
+ 	@class TextToken
+ 	@abstract
+ */
+	var TextToken = createTokenClass();
+	TextToken.prototype = {
+		toString: function toString() {
+			return this.v + '';
+		}
+	};
+
+	function inheritsToken(value) {
+		var props = value ? { v: value } : {};
+		return inherits(TextToken, createTokenClass(), props);
+	}
+
+	/**
+ 	A valid domain token
+ 	@class DOMAIN
+ 	@extends TextToken
+ */
+	var DOMAIN = inheritsToken();
+
+	/**
+ 	@class AT
+ 	@extends TextToken
+ */
+	var AT = inheritsToken('@');
+
+	/**
+ 	Represents a single colon `:` character
+
+ 	@class COLON
+ 	@extends TextToken
+ */
+	var COLON = inheritsToken(':');
+
+	/**
+ 	@class DOT
+ 	@extends TextToken
+ */
+	var DOT = inheritsToken('.');
+
+	/**
+ 	A character class that can surround the URL, but which the URL cannot begin
+ 	or end with. Does not include certain English punctuation like parentheses.
+
+ 	@class PUNCTUATION
+ 	@extends TextToken
+ */
+	var PUNCTUATION = inheritsToken();
+
+	/**
+ 	The word localhost (by itself)
+ 	@class LOCALHOST
+ 	@extends TextToken
+ */
+	var LOCALHOST = inheritsToken();
+
+	/**
+ 	Newline token
+ 	@class NL
+ 	@extends TextToken
+ */
+	var TNL = inheritsToken('\n');
+
+	/**
+ 	@class NUM
+ 	@extends TextToken
+ */
+	var NUM = inheritsToken();
+
+	/**
+ 	@class PLUS
+ 	@extends TextToken
+ */
+	var PLUS = inheritsToken('+');
+
+	/**
+ 	@class POUND
+ 	@extends TextToken
+ */
+	var POUND = inheritsToken('#');
+
+	/**
+ 	Represents a web URL protocol. Supported types include
+
+ 	* `http:`
+ 	* `https:`
+ 	* `ftp:`
+ 	* `ftps:`
+ 	* There's Another super weird one
+
+ 	@class PROTOCOL
+ 	@extends TextToken
+ */
+	var PROTOCOL = inheritsToken();
+
+	/**
+ 	@class QUERY
+ 	@extends TextToken
+ */
+	var QUERY = inheritsToken('?');
+
+	/**
+ 	@class SLASH
+ 	@extends TextToken
+ */
+	var SLASH = inheritsToken('/');
+
+	/**
+ 	@class UNDERSCORE
+ 	@extends TextToken
+ */
+	var UNDERSCORE = inheritsToken('_');
+
+	/**
+ 	One ore more non-whitespace symbol.
+ 	@class SYM
+ 	@extends TextToken
+ */
+	var SYM = inheritsToken();
+
+	/**
+ 	@class TLD
+ 	@extends TextToken
+ */
+	var TLD = inheritsToken();
+
+	/**
+ 	Represents a string of consecutive whitespace characters
+
+ 	@class WS
+ 	@extends TextToken
+ */
+	var WS = inheritsToken();
+
+	/**
+ 	Opening/closing bracket classes
+ */
+
+	var OPENBRACE = inheritsToken('{');
+	var OPENBRACKET = inheritsToken('[');
+	var OPENANGLEBRACKET = inheritsToken('<');
+	var OPENPAREN = inheritsToken('(');
+	var CLOSEBRACE = inheritsToken('}');
+	var CLOSEBRACKET = inheritsToken(']');
+	var CLOSEANGLEBRACKET = inheritsToken('>');
+	var CLOSEPAREN = inheritsToken(')');
+
+	var TOKENS = Object.freeze({
+		Base: TextToken,
+		DOMAIN: DOMAIN,
+		AT: AT,
+		COLON: COLON,
+		DOT: DOT,
+		PUNCTUATION: PUNCTUATION,
+		LOCALHOST: LOCALHOST,
+		NL: TNL,
+		NUM: NUM,
+		PLUS: PLUS,
+		POUND: POUND,
+		QUERY: QUERY,
+		PROTOCOL: PROTOCOL,
+		SLASH: SLASH,
+		UNDERSCORE: UNDERSCORE,
+		SYM: SYM,
+		TLD: TLD,
+		WS: WS,
+		OPENBRACE: OPENBRACE,
+		OPENBRACKET: OPENBRACKET,
+		OPENANGLEBRACKET: OPENANGLEBRACKET,
+		OPENPAREN: OPENPAREN,
+		CLOSEBRACE: CLOSEBRACE,
+		CLOSEBRACKET: CLOSEBRACKET,
+		CLOSEANGLEBRACKET: CLOSEANGLEBRACKET,
+		CLOSEPAREN: CLOSEPAREN
+	});
+
+	/**
+ 	The scanner provides an interface that takes a string of text as input, and
+ 	outputs an array of tokens instances that can be used for easy URL parsing.
+
+ 	@module linkify
+ 	@submodule scanner
+ 	@main scanner
+ */
+
+	var tlds = 'aaa|aarp|abb|abbott|abogado|ac|academy|accenture|accountant|accountants|aco|active|actor|ad|adac|ads|adult|ae|aeg|aero|af|afl|ag|agency|ai|aig|airforce|airtel|al|alibaba|alipay|allfinanz|alsace|am|amica|amsterdam|an|analytics|android|ao|apartments|app|apple|aq|aquarelle|ar|aramco|archi|army|arpa|arte|as|asia|associates|at|attorney|au|auction|audi|audio|author|auto|autos|avianca|aw|ax|axa|az|azure|ba|baidu|band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bb|bbc|bbva|bcg|bcn|bd|be|beats|beer|bentley|berlin|best|bet|bf|bg|bh|bharti|bi|bible|bid|bike|bing|bingo|bio|biz|bj|black|blackfriday|bloomberg|blue|bm|bms|bmw|bn|bnl|bnpparibas|bo|boats|boehringer|bom|bond|boo|book|boots|bosch|bostik|bot|boutique|br|bradesco|bridgestone|broadway|broker|brother|brussels|bs|bt|budapest|bugatti|build|builders|business|buy|buzz|bv|bw|by|bz|bzh|ca|cab|cafe|cal|call|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|cc|cd|ceb|center|ceo|cern|cf|cfa|cfd|cg|ch|chanel|channel|chase|chat|cheap|chloe|christmas|chrome|church|ci|cipriani|circle|cisco|citic|city|cityeats|ck|cl|claims|cleaning|click|clinic|clinique|clothing|cloud|club|clubmed|cm|cn|co|coach|codes|coffee|college|cologne|com|commbank|community|company|compare|computer|comsec|condos|construction|consulting|contact|contractors|cooking|cool|coop|corsica|country|coupon|coupons|courses|cr|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc|cu|cuisinella|cv|cw|cx|cy|cymru|cyou|cz|dabur|dad|dance|date|dating|datsun|day|dclk|de|dealer|deals|degree|delivery|dell|deloitte|delta|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory|discount|dj|dk|dm|dnp|do|docs|dog|doha|domains|download|drive|dubai|durban|dvag|dz|earth|eat|ec|edeka|edu|education|ee|eg|email|emerck|energy|engineer|engineering|enterprises|epson|equipment|er|erni|es|esq|estate|et|eu|eurovision|eus|events|everbank|exchange|expert|exposed|express|fage|fail|fairwinds|faith|family|fan|fans|farm|fashion|fast|feedback|ferrero|fi|film|final|finance|financial|firestone|firmdale|fish|fishing|fit|fitness|fj|fk|flickr|flights|florist|flowers|flsmidth|fly|fm|fo|foo|football|ford|forex|forsale|forum|foundation|fox|fr|fresenius|frl|frogans|frontier|fund|furniture|futbol|fyi|ga|gal|gallery|gallup|game|garden|gb|gbiz|gd|gdn|ge|gea|gent|genting|gf|gg|ggee|gh|gi|gift|gifts|gives|giving|gl|glass|gle|global|globo|gm|gmail|gmbh|gmo|gmx|gn|gold|goldpoint|golf|goo|goog|google|gop|got|gov|gp|gq|gr|grainger|graphics|gratis|green|gripe|group|gs|gt|gu|gucci|guge|guide|guitars|guru|gw|gy|hamburg|hangout|haus|hdfcbank|health|healthcare|help|helsinki|here|hermes|hiphop|hitachi|hiv|hk|hm|hn|hockey|holdings|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house|how|hr|hsbc|ht|hu|hyundai|ibm|icbc|ice|icu|id|ie|ifm|iinet|il|im|immo|immobilien|in|industries|infiniti|info|ing|ink|institute|insurance|insure|int|international|investments|io|ipiranga|iq|ir|irish|is|iselect|ist|istanbul|it|itau|iwc|jaguar|java|jcb|je|jetzt|jewelry|jlc|jll|jm|jmp|jo|jobs|joburg|jot|joy|jp|jpmorgan|jprs|juegos|kaufen|kddi|ke|kerryhotels|kerrylogistics|kerryproperties|kfh|kg|kh|ki|kia|kim|kinder|kitchen|kiwi|km|kn|koeln|komatsu|kp|kpn|kr|krd|kred|kuokgroup|kw|ky|kyoto|kz|la|lacaixa|lamborghini|lamer|lancaster|land|landrover|lanxess|lasalle|lat|latrobe|law|lawyer|lb|lc|lds|lease|leclerc|legal|lexus|lgbt|li|liaison|lidl|life|lifeinsurance|lifestyle|lighting|like|limited|limo|lincoln|linde|link|live|living|lixil|lk|loan|loans|local|locus|lol|london|lotte|lotto|love|lr|ls|lt|ltd|ltda|lu|lupin|luxe|luxury|lv|ly|ma|madrid|maif|maison|makeup|man|management|mango|market|marketing|markets|marriott|mba|mc|md|me|med|media|meet|melbourne|meme|memorial|men|menu|meo|mg|mh|miami|microsoft|mil|mini|mk|ml|mm|mma|mn|mo|mobi|mobily|moda|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov|movie|movistar|mp|mq|mr|ms|mt|mtn|mtpc|mtr|mu|museum|mutuelle|mv|mw|mx|my|mz|na|nadex|nagoya|name|natura|navy|nc|ne|nec|net|netbank|network|neustar|new|news|nexus|nf|ng|ngo|nhk|ni|nico|nikon|ninja|nissan|nl|no|nokia|norton|nowruz|np|nr|nra|nrw|ntt|nu|nyc|nz|obi|office|okinawa|om|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|origins|osaka|otsuka|ovh|pa|page|pamperedchef|panerai|paris|pars|partners|parts|party|passagens|pe|pet|pf|pg|ph|pharmacy|philips|photo|photography|photos|physio|piaget|pics|pictet|pictures|pid|pin|ping|pink|pizza|pk|pl|place|play|playstation|plumbing|plus|pm|pn|pohl|poker|porn|post|pr|praxi|press|pro|prod|productions|prof|promo|properties|property|protection|ps|pt|pub|pw|pwc|py|qa|qpon|quebec|quest|racing|re|read|realtor|realty|recipes|red|redstone|redumbrella|rehab|reise|reisen|reit|ren|rent|rentals|repair|report|republican|rest|restaurant|review|reviews|rexroth|rich|ricoh|rio|rip|ro|rocher|rocks|rodeo|room|rs|rsvp|ru|ruhr|run|rw|rwe|ryukyu|sa|saarland|safe|safety|sakura|sale|salon|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|sas|saxo|sb|sbs|sc|sca|scb|schaeffler|schmidt|scholarships|school|schule|schwarz|science|scor|scot|sd|se|seat|security|seek|select|sener|services|seven|sew|sex|sexy|sfr|sg|sh|sharp|shell|shia|shiksha|shoes|show|shriram|si|singles|site|sj|sk|ski|skin|sky|skype|sl|sm|smile|sn|sncf|so|soccer|social|softbank|software|sohu|solar|solutions|song|sony|soy|space|spiegel|spot|spreadbetting|sr|srl|st|stada|star|starhub|statefarm|statoil|stc|stcgroup|stockholm|storage|store|studio|study|style|su|sucks|supplies|supply|support|surf|surgery|suzuki|sv|swatch|swiss|sx|sy|sydney|symantec|systems|sz|tab|taipei|taobao|tatamotors|tatar|tattoo|tax|taxi|tc|tci|td|team|tech|technology|tel|telecity|telefonica|temasek|tennis|tf|tg|th|thd|theater|theatre|tickets|tienda|tiffany|tips|tires|tirol|tj|tk|tl|tm|tmall|tn|to|today|tokyo|tools|top|toray|toshiba|total|tours|town|toyota|toys|tp|tr|trade|trading|training|travel|travelers|travelersinsurance|trust|trv|tt|tube|tui|tunes|tushu|tv|tvs|tw|tz|ua|ubs|ug|uk|unicom|university|uno|uol|us|uy|uz|va|vacations|vana|vc|ve|vegas|ventures|verisign|versicherung|vet|vg|vi|viajes|video|viking|villas|vin|vip|virgin|vision|vista|vistaprint|viva|vlaanderen|vn|vodka|volkswagen|vote|voting|voto|voyage|vu|vuelos|wales|walter|wang|wanggou|watch|watches|weather|weatherchannel|webcam|weber|website|wed|wedding|weir|wf|whoswho|wien|wiki|williamhill|win|windows|wine|wme|wolterskluwer|work|works|world|ws|wtc|wtf|xbox|xerox|xin|xperia|xxx|xyz|yachts|yahoo|yamaxun|yandex|ye|yodobashi|yoga|yokohama|youtube|yt|za|zara|zero|zip|zm|zone|zuerich|zw'.split('|'); // macro, see gulpfile.js
+
+	var NUMBERS = '0123456789'.split('');
+	var ALPHANUM = '0123456789abcdefghijklmnopqrstuvwxyz'.split('');
+	var WHITESPACE = [' ', '\f', '\r', '\t', '\v', ' ', ' ', 'á Ž']; // excluding line breaks
+
+	var domainStates = []; // states that jump to DOMAIN on /[a-z0-9]/
+	var makeState = function makeState(tokenClass) {
+		return new CharacterState(tokenClass);
+	};
+
+	// Frequently used states
+	var S_START = makeState();
+	var S_NUM = makeState(NUM);
+	var S_DOMAIN = makeState(DOMAIN);
+	var S_DOMAIN_HYPHEN = makeState(); // domain followed by 1 or more hyphen characters
+	var S_WS = makeState(WS);
+
+	// States for special URL symbols
+	S_START.on('@', makeState(AT)).on('.', makeState(DOT)).on('+', makeState(PLUS)).on('#', makeState(POUND)).on('?', makeState(QUERY)).on('/', makeState(SLASH)).on('_', makeState(UNDERSCORE)).on(':', makeState(COLON)).on('{', makeState(OPENBRACE)).on('[', makeState(OPENBRACKET)).on('<', makeState(OPENANGLEBRACKET)).on('(', makeState(OPENPAREN)).on('}', makeState(CLOSEBRACE)).on(']', makeState(CLOSEBRACKET)).on('>', makeState(CLOSEANGLEBRACKET)).on(')', makeState(CLOSEPAREN)).on([',', ';', '!', '"', '\''], makeState(PUNCTUATION));
+
+	// Whitespace jumps
+	// Tokens of only non-newline whitespace are arbitrarily long
+	S_START.on('\n', makeState(TNL)).on(WHITESPACE, S_WS);
+
+	// If any whitespace except newline, more whitespace!
+	S_WS.on(WHITESPACE, S_WS);
+
+	// Generates states for top-level domains
+	// Note that this is most accurate when tlds are in alphabetical order
+	for (var i = 0; i < tlds.length; i++) {
+		var newStates = stateify(tlds[i], S_START, TLD, DOMAIN);
+		domainStates.push.apply(domainStates, newStates);
+	}
+
+	// Collect the states generated by different protocls
+	var partialProtocolFileStates = stateify('file', S_START, DOMAIN, DOMAIN);
+	var partialProtocolFtpStates = stateify('ftp', S_START, DOMAIN, DOMAIN);
+	var partialProtocolHttpStates = stateify('http', S_START, DOMAIN, DOMAIN);
+
+	// Add the states to the array of DOMAINeric states
+	domainStates.push.apply(domainStates, partialProtocolFileStates);
+	domainStates.push.apply(domainStates, partialProtocolFtpStates);
+	domainStates.push.apply(domainStates, partialProtocolHttpStates);
+
+	// Protocol states
+	var S_PROTOCOL_FILE = partialProtocolFileStates.pop();
+	var S_PROTOCOL_FTP = partialProtocolFtpStates.pop();
+	var S_PROTOCOL_HTTP = partialProtocolHttpStates.pop();
+	var S_PROTOCOL_SECURE = makeState(DOMAIN);
+	var S_FULL_PROTOCOL = makeState(PROTOCOL); // Full protocol ends with COLON
+
+	// Secure protocols (end with 's')
+	S_PROTOCOL_FTP.on('s', S_PROTOCOL_SECURE).on(':', S_FULL_PROTOCOL);
+
+	S_PROTOCOL_HTTP.on('s', S_PROTOCOL_SECURE).on(':', S_FULL_PROTOCOL);
+
+	domainStates.push(S_PROTOCOL_SECURE);
+
+	// Become protocol tokens after a COLON
+	S_PROTOCOL_FILE.on(':', S_FULL_PROTOCOL);
+	S_PROTOCOL_SECURE.on(':', S_FULL_PROTOCOL);
+
+	// Localhost
+	var partialLocalhostStates = stateify('localhost', S_START, LOCALHOST, DOMAIN);
+	domainStates.push.apply(domainStates, partialLocalhostStates);
+
+	// Everything else
+	// DOMAINs make more DOMAINs
+	// Number and character transitions
+	S_START.on(NUMBERS, S_NUM);
+	S_NUM.on('-', S_DOMAIN_HYPHEN).on(NUMBERS, S_NUM).on(ALPHANUM, S_DOMAIN); // number becomes DOMAIN
+
+	S_DOMAIN.on('-', S_DOMAIN_HYPHEN).on(ALPHANUM, S_DOMAIN);
+
+	// All the generated states should have a jump to DOMAIN
+	for (var _i = 0; _i < domainStates.length; _i++) {
+		domainStates[_i].on('-', S_DOMAIN_HYPHEN).on(ALPHANUM, S_DOMAIN);
+	}
+
+	S_DOMAIN_HYPHEN.on('-', S_DOMAIN_HYPHEN).on(NUMBERS, S_DOMAIN).on(ALPHANUM, S_DOMAIN);
+
+	// Set default transition
+	S_START.defaultTransition = makeState(SYM);
+
+	/**
+ 	Given a string, returns an array of TOKEN instances representing the
+ 	composition of that string.
+
+ 	@method run
+ 	@param {String} str Input string to scan
+ 	@return {Array} Array of TOKEN instances
+ */
+	var run = function run(str) {
+
+		// The state machine only looks at lowercase strings.
+		// This selective `toLowerCase` is used because lowercasing the entire
+		// string causes the length and character position to vary in some in some
+		// non-English strings. This happens only on V8-based runtimes.
+		var lowerStr = str.replace(/[A-Z]/g, function (c) {
+			return c.toLowerCase();
+		});
+		var len = str.length;
+		var tokens = []; // return value
+
+		var cursor = 0;
+
+		// Tokenize the string
+		while (cursor < len) {
+			var state = S_START;
+			var secondState = null;
+			var nextState = null;
+			var tokenLength = 0;
+			var latestAccepting = null;
+			var sinceAccepts = -1;
+
+			while (cursor < len && (nextState = state.next(lowerStr[cursor]))) {
+				secondState = null;
+				state = nextState;
+
+				// Keep track of the latest accepting state
+				if (state.accepts()) {
+					sinceAccepts = 0;
+					latestAccepting = state;
+				} else if (sinceAccepts >= 0) {
+					sinceAccepts++;
+				}
+
+				tokenLength++;
+				cursor++;
+			}
+
+			if (sinceAccepts < 0) {
+				continue;
+			} // Should never happen
+
+			// Roll back to the latest accepting state
+			cursor -= sinceAccepts;
+			tokenLength -= sinceAccepts;
+
+			// Get the class for the new token
+			var TOKEN = latestAccepting.emit(); // Current token class
+
+			// No more jumps, just make a new token
+			tokens.push(new TOKEN(str.substr(cursor - tokenLength, tokenLength)));
+		}
+
+		return tokens;
+	};
+
+	var start = S_START;
+
+	var scanner = Object.freeze({
+		State: CharacterState,
+		TOKENS: TOKENS,
+		run: run,
+		start: start
+	});
+
+	/******************************************************************************
+ 	Multi-Tokens
+ 	Tokens composed of arrays of TextTokens
+ ******************************************************************************/
+
+	// Is the given token a valid domain token?
+	// Should nums be included here?
+	function isDomainToken(token) {
+		return token instanceof DOMAIN || token instanceof TLD;
+	}
+
+	/**
+ 	Abstract class used for manufacturing tokens of text tokens. That is rather
+ 	than the value for a token being a small string of text, it's value an array
+ 	of text tokens.
+
+ 	Used for grouping together URLs, emails, hashtags, and other potential
+ 	creations.
+
+ 	@class MultiToken
+ 	@abstract
+ */
+	var MultiToken = createTokenClass();
+
+	MultiToken.prototype = {
+		/**
+  	String representing the type for this token
+  	@property type
+  	@default 'TOKEN'
+  */
+		type: 'token',
+
+		/**
+  	Is this multitoken a link?
+  	@property isLink
+  	@default false
+  */
+		isLink: false,
+
+		/**
+  	Return the string this token represents.
+  	@method toString
+  	@return {String}
+  */
+		toString: function toString() {
+			var result = [];
+			for (var _i2 = 0; _i2 < this.v.length; _i2++) {
+				result.push(this.v[_i2].toString());
+			}
+			return result.join('');
+		},
+
+
+		/**
+  	What should the value for this token be in the `href` HTML attribute?
+  	Returns the `.toString` value by default.
+  		@method toHref
+  	@return {String}
+  */
+		toHref: function toHref() {
+			return this.toString();
+		},
+
+
+		/**
+  	Returns a hash of relevant values for this token, which includes keys
+  	* type - Kind of token ('url', 'email', etc.)
+  	* value - Original text
+  	* href - The value that should be added to the anchor tag's href
+  		attribute
+  		@method toObject
+  	@param {String} [protocol] `'http'` by default
+  	@return {Object}
+  */
+		toObject: function toObject() {
+			var protocol = arguments.length <= 0 || arguments[0] === undefined ? 'http' : arguments[0];
+
+			return {
+				type: this.type,
+				value: this.toString(),
+				href: this.toHref(protocol)
+			};
+		}
+	};
+
+	/**
+ 	Represents a list of tokens making up a valid email address
+ 	@class EMAIL
+ 	@extends MultiToken
+ */
+	var EMAIL = inherits(MultiToken, createTokenClass(), {
+		type: 'email',
+		isLink: true,
+		toHref: function toHref() {
+			return 'mailto:' + this.toString();
+		}
+	});
+
+	/**
+ 	Represents some plain text
+ 	@class TEXT
+ 	@extends MultiToken
+ */
+	var TEXT = inherits(MultiToken, createTokenClass(), { type: 'text' });
+
+	/**
+ 	Multi-linebreak token - represents a line break
+ 	@class NL
+ 	@extends MultiToken
+ */
+	var NL = inherits(MultiToken, createTokenClass(), { type: 'nl' });
+
+	/**
+ 	Represents a list of tokens making up a valid URL
+ 	@class URL
+ 	@extends MultiToken
+ */
+	var URL = inherits(MultiToken, createTokenClass(), {
+		type: 'url',
+		isLink: true,
+
+		/**
+  	Lowercases relevant parts of the domain and adds the protocol if
+  	required. Note that this will not escape unsafe HTML characters in the
+  	URL.
+  		@method href
+  	@param {String} protocol
+  	@return {String}
+  */
+		toHref: function toHref() {
+			var protocol = arguments.length <= 0 || arguments[0] === undefined ? 'http' : arguments[0];
+
+			var hasProtocol = false;
+			var hasSlashSlash = false;
+			var tokens = this.v;
+			var result = [];
+			var i = 0;
+
+			// Make the first part of the domain lowercase
+			// Lowercase protocol
+			while (tokens[i] instanceof PROTOCOL) {
+				hasProtocol = true;
+				result.push(tokens[i].toString().toLowerCase());
+				i++;
+			}
+
+			// Skip slash-slash
+			while (tokens[i] instanceof SLASH) {
+				hasSlashSlash = true;
+				result.push(tokens[i].toString());
+				i++;
+			}
+
+			// Lowercase all other characters in the domain
+			while (isDomainToken(tokens[i])) {
+				result.push(tokens[i].toString().toLowerCase());
+				i++;
+			}
+
+			// Leave all other characters as they were written
+			for (; i < tokens.length; i++) {
+				result.push(tokens[i].toString());
+			}
+
+			result = result.join('');
+
+			if (!(hasProtocol || hasSlashSlash)) {
+				result = protocol + '://' + result;
+			}
+
+			return result;
+		},
+		hasProtocol: function hasProtocol() {
+			return this.v[0] instanceof PROTOCOL;
+		}
+	});
+
+	var TOKENS$1 = Object.freeze({
+		Base: MultiToken,
+		EMAIL: EMAIL,
+		NL: NL,
+		TEXT: TEXT,
+		URL: URL
+	});
+
+	/**
+ 	Not exactly parser, more like the second-stage scanner (although we can
+ 	theoretically hotswap the code here with a real parser in the future... but
+ 	for a little URL-finding utility abstract syntax trees may be a little
+ 	overkill).
+
+ 	URL format: http://en.wikipedia.org/wiki/URI_scheme
+ 	Email format: http://en.wikipedia.org/wiki/Email_address (links to RFC in
+ 	reference)
+
+ 	@module linkify
+ 	@submodule parser
+ 	@main parser
+ */
+
+	var makeState$1 = function makeState$1(tokenClass) {
+		return new State(tokenClass);
+	};
+
+	// The universal starting state.
+	var S_START$1 = makeState$1();
+
+	// Intermediate states for URLs. Note that domains that begin with a protocol
+	// are treated slighly differently from those that don't.
+	var S_PROTOCOL = makeState$1(); // e.g., 'http:'
+	var S_PROTOCOL_SLASH = makeState$1(); // e.g., '/', 'http:/''
+	var S_PROTOCOL_SLASH_SLASH = makeState$1(); // e.g., '//', 'http://'
+	var S_DOMAIN$1 = makeState$1(); // parsed string ends with a potential domain name (A)
+	var S_DOMAIN_DOT = makeState$1(); // (A) domain followed by DOT
+	var S_TLD = makeState$1(URL); // (A) Simplest possible URL with no query string
+	var S_TLD_COLON = makeState$1(); // (A) URL followed by colon (potential port number here)
+	var S_TLD_PORT = makeState$1(URL); // TLD followed by a port number
+	var S_URL = makeState$1(URL); // Long URL with optional port and maybe query string
+	var S_URL_NON_ACCEPTING = makeState$1(); // URL followed by some symbols (will not be part of the final URL)
+	var S_URL_OPENBRACE = makeState$1(); // URL followed by {
+	var S_URL_OPENBRACKET = makeState$1(); // URL followed by [
+	var S_URL_OPENANGLEBRACKET = makeState$1(); // URL followed by <
+	var S_URL_OPENPAREN = makeState$1(); // URL followed by (
+	var S_URL_OPENBRACE_Q = makeState$1(URL); // URL followed by { and some symbols that the URL can end it
+	var S_URL_OPENBRACKET_Q = makeState$1(URL); // URL followed by [ and some symbols that the URL can end it
+	var S_URL_OPENANGLEBRACKET_Q = makeState$1(URL); // URL followed by < and some symbols that the URL can end it
+	var S_URL_OPENPAREN_Q = makeState$1(URL); // URL followed by ( and some symbols that the URL can end it
+	var S_URL_OPENBRACE_SYMS = makeState$1(); // S_URL_OPENBRACE_Q followed by some symbols it cannot end it
+	var S_URL_OPENBRACKET_SYMS = makeState$1(); // S_URL_OPENBRACKET_Q followed by some symbols it cannot end it
+	var S_URL_OPENANGLEBRACKET_SYMS = makeState$1(); // S_URL_OPENANGLEBRACKET_Q followed by some symbols it cannot end it
+	var S_URL_OPENPAREN_SYMS = makeState$1(); // S_URL_OPENPAREN_Q followed by some symbols it cannot end it
+	var S_EMAIL_DOMAIN = makeState$1(); // parsed string starts with local email info + @ with a potential domain name (C)
+	var S_EMAIL_DOMAIN_DOT = makeState$1(); // (C) domain followed by DOT
+	var S_EMAIL = makeState$1(EMAIL); // (C) Possible email address (could have more tlds)
+	var S_EMAIL_COLON = makeState$1(); // (C) URL followed by colon (potential port number here)
+	var S_EMAIL_PORT = makeState$1(EMAIL); // (C) Email address with a port
+	var S_LOCALPART = makeState$1(); // Local part of the email address
+	var S_LOCALPART_AT = makeState$1(); // Local part of the email address plus @
+	var S_LOCALPART_DOT = makeState$1(); // Local part of the email address plus '.' (localpart cannot end in .)
+	var S_NL = makeState$1(NL); // single new line
+
+	// Make path from start to protocol (with '//')
+	S_START$1.on(TNL, S_NL).on(PROTOCOL, S_PROTOCOL).on(SLASH, S_PROTOCOL_SLASH);
+
+	S_PROTOCOL.on(SLASH, S_PROTOCOL_SLASH);
+	S_PROTOCOL_SLASH.on(SLASH, S_PROTOCOL_SLASH_SLASH);
+
+	// The very first potential domain name
+	S_START$1.on(TLD, S_DOMAIN$1).on(DOMAIN, S_DOMAIN$1).on(LOCALHOST, S_TLD).on(NUM, S_DOMAIN$1);
+
+	// Force URL for anything sane followed by protocol
+	S_PROTOCOL_SLASH_SLASH.on(TLD, S_URL).on(DOMAIN, S_URL).on(NUM, S_URL).on(LOCALHOST, S_URL);
+
+	// Account for dots and hyphens
+	// hyphens are usually parts of domain names
+	S_DOMAIN$1.on(DOT, S_DOMAIN_DOT);
+	S_EMAIL_DOMAIN.on(DOT, S_EMAIL_DOMAIN_DOT);
+
+	// Hyphen can jump back to a domain name
+
+	// After the first domain and a dot, we can find either a URL or another domain
+	S_DOMAIN_DOT.on(TLD, S_TLD).on(DOMAIN, S_DOMAIN$1).on(NUM, S_DOMAIN$1).on(LOCALHOST, S_DOMAIN$1);
+
+	S_EMAIL_DOMAIN_DOT.on(TLD, S_EMAIL).on(DOMAIN, S_EMAIL_DOMAIN).on(NUM, S_EMAIL_DOMAIN).on(LOCALHOST, S_EMAIL_DOMAIN);
+
+	// S_TLD accepts! But the URL could be longer, try to find a match greedily
+	// The `run` function should be able to "rollback" to the accepting state
+	S_TLD.on(DOT, S_DOMAIN_DOT);
+	S_EMAIL.on(DOT, S_EMAIL_DOMAIN_DOT);
+
+	// Become real URLs after `SLASH` or `COLON NUM SLASH`
+	// Here PSS and non-PSS converge
+	S_TLD.on(COLON, S_TLD_COLON).on(SLASH, S_URL);
+	S_TLD_COLON.on(NUM, S_TLD_PORT);
+	S_TLD_PORT.on(SLASH, S_URL);
+	S_EMAIL.on(COLON, S_EMAIL_COLON);
+	S_EMAIL_COLON.on(NUM, S_EMAIL_PORT);
+
+	// Types of characters the URL can definitely end in
+	var qsAccepting = [DOMAIN, AT, LOCALHOST, NUM, PLUS, POUND, PROTOCOL, SLASH, TLD, UNDERSCORE, SYM];
+
+	// Types of tokens that can follow a URL and be part of the query string
+	// but cannot be the very last characters
+	// Characters that cannot appear in the URL at all should be excluded
+	var qsNonAccepting = [COLON, DOT, QUERY, PUNCTUATION, CLOSEBRACE, CLOSEBRACKET, CLOSEANGLEBRACKET, CLOSEPAREN, OPENBRACE, OPENBRACKET, OPENANGLEBRACKET, OPENPAREN];
+
+	// These states are responsible primarily for determining whether or not to
+	// include the final round bracket.
+
+	// URL, followed by an opening bracket
+	S_URL.on(OPENBRACE, S_URL_OPENBRACE).on(OPENBRACKET, S_URL_OPENBRACKET).on(OPENANGLEBRACKET, S_URL_OPENANGLEBRACKET).on(OPENPAREN, S_URL_OPENPAREN);
+
+	// URL with extra symbols at the end, followed by an opening bracket
+	S_URL_NON_ACCEPTING.on(OPENBRACE, S_URL_OPENBRACE).on(OPENBRACKET, S_URL_OPENBRACKET).on(OPENANGLEBRACKET, S_URL_OPENANGLEBRACKET).on(OPENPAREN, S_URL_OPENPAREN);
+
+	// Closing bracket component. This character WILL be included in the URL
+	S_URL_OPENBRACE.on(CLOSEBRACE, S_URL);
+	S_URL_OPENBRACKET.on(CLOSEBRACKET, S_URL);
+	S_URL_OPENANGLEBRACKET.on(CLOSEANGLEBRACKET, S_URL);
+	S_URL_OPENPAREN.on(CLOSEPAREN, S_URL);
+	S_URL_OPENBRACE_Q.on(CLOSEBRACE, S_URL);
+	S_URL_OPENBRACKET_Q.on(CLOSEBRACKET, S_URL);
+	S_URL_OPENANGLEBRACKET_Q.on(CLOSEANGLEBRACKET, S_URL);
+	S_URL_OPENPAREN_Q.on(CLOSEPAREN, S_URL);
+	S_URL_OPENBRACE_SYMS.on(CLOSEBRACE, S_URL);
+	S_URL_OPENBRACKET_SYMS.on(CLOSEBRACKET, S_URL);
+	S_URL_OPENANGLEBRACKET_SYMS.on(CLOSEANGLEBRACKET, S_URL);
+	S_URL_OPENPAREN_SYMS.on(CLOSEPAREN, S_URL);
+
+	// URL that beings with an opening bracket, followed by a symbols.
+	// Note that the final state can still be `S_URL_OPENBRACE_Q` (if the URL only
+	// has a single opening bracket for some reason).
+	S_URL_OPENBRACE.on(qsAccepting, S_URL_OPENBRACE_Q);
+	S_URL_OPENBRACKET.on(qsAccepting, S_URL_OPENBRACKET_Q);
+	S_URL_OPENANGLEBRACKET.on(qsAccepting, S_URL_OPENANGLEBRACKET_Q);
+	S_URL_OPENPAREN.on(qsAccepting, S_URL_OPENPAREN_Q);
+	S_URL_OPENBRACE.on(qsNonAccepting, S_URL_OPENBRACE_SYMS);
+	S_URL_OPENBRACKET.on(qsNonAccepting, S_URL_OPENBRACKET_SYMS);
+	S_URL_OPENANGLEBRACKET.on(qsNonAccepting, S_URL_OPENANGLEBRACKET_SYMS);
+	S_URL_OPENPAREN.on(qsNonAccepting, S_URL_OPENPAREN_SYMS);
+
+	// URL that begins with an opening bracket, followed by some symbols
+	S_URL_OPENBRACE_Q.on(qsAccepting, S_URL_OPENBRACE_Q);
+	S_URL_OPENBRACKET_Q.on(qsAccepting, S_URL_OPENBRACKET_Q);
+	S_URL_OPENANGLEBRACKET_Q.on(qsAccepting, S_URL_OPENANGLEBRACKET_Q);
+	S_URL_OPENPAREN_Q.on(qsAccepting, S_URL_OPENPAREN_Q);
+	S_URL_OPENBRACE_Q.on(qsNonAccepting, S_URL_OPENBRACE_Q);
+	S_URL_OPENBRACKET_Q.on(qsNonAccepting, S_URL_OPENBRACKET_Q);
+	S_URL_OPENANGLEBRACKET_Q.on(qsNonAccepting, S_URL_OPENANGLEBRACKET_Q);
+	S_URL_OPENPAREN_Q.on(qsNonAccepting, S_URL_OPENPAREN_Q);
+
+	S_URL_OPENBRACE_SYMS.on(qsAccepting, S_URL_OPENBRACE_Q);
+	S_URL_OPENBRACKET_SYMS.on(qsAccepting, S_URL_OPENBRACKET_Q);
+	S_URL_OPENANGLEBRACKET_SYMS.on(qsAccepting, S_URL_OPENANGLEBRACKET_Q);
+	S_URL_OPENPAREN_SYMS.on(qsAccepting, S_URL_OPENPAREN_Q);
+	S_URL_OPENBRACE_SYMS.on(qsNonAccepting, S_URL_OPENBRACE_SYMS);
+	S_URL_OPENBRACKET_SYMS.on(qsNonAccepting, S_URL_OPENBRACKET_SYMS);
+	S_URL_OPENANGLEBRACKET_SYMS.on(qsNonAccepting, S_URL_OPENANGLEBRACKET_SYMS);
+	S_URL_OPENPAREN_SYMS.on(qsNonAccepting, S_URL_OPENPAREN_SYMS);
+
+	// Account for the query string
+	S_URL.on(qsAccepting, S_URL);
+	S_URL_NON_ACCEPTING.on(qsAccepting, S_URL);
+
+	S_URL.on(qsNonAccepting, S_URL_NON_ACCEPTING);
+	S_URL_NON_ACCEPTING.on(qsNonAccepting, S_URL_NON_ACCEPTING);
+
+	// Email address-specific state definitions
+	// Note: We are not allowing '/' in email addresses since this would interfere
+	// with real URLs
+
+	// Tokens allowed in the localpart of the email
+	var localpartAccepting = [DOMAIN, NUM, PLUS, POUND, QUERY, UNDERSCORE, SYM, TLD];
+
+	// Some of the tokens in `localpartAccepting` are already accounted for here and
+	// will not be overwritten (don't worry)
+	S_DOMAIN$1.on(localpartAccepting, S_LOCALPART).on(AT, S_LOCALPART_AT);
+	S_TLD.on(localpartAccepting, S_LOCALPART).on(AT, S_LOCALPART_AT);
+	S_DOMAIN_DOT.on(localpartAccepting, S_LOCALPART);
+
+	// Okay we're on a localpart. Now what?
+	// TODO: IP addresses and what if the email starts with numbers?
+	S_LOCALPART.on(localpartAccepting, S_LOCALPART).on(AT, S_LOCALPART_AT) // close to an email address now
+	.on(DOT, S_LOCALPART_DOT);
+	S_LOCALPART_DOT.on(localpartAccepting, S_LOCALPART);
+	S_LOCALPART_AT.on(TLD, S_EMAIL_DOMAIN).on(DOMAIN, S_EMAIL_DOMAIN).on(LOCALHOST, S_EMAIL);
+	// States following `@` defined above
+
+	var run$1 = function run$1(tokens) {
+		var len = tokens.length;
+		var cursor = 0;
+		var multis = [];
+		var textTokens = [];
+
+		while (cursor < len) {
+			var state = S_START$1;
+			var secondState = null;
+			var nextState = null;
+			var multiLength = 0;
+			var latestAccepting = null;
+			var sinceAccepts = -1;
+
+			while (cursor < len && !(secondState = state.next(tokens[cursor]))) {
+				// Starting tokens with nowhere to jump to.
+				// Consider these to be just plain text
+				textTokens.push(tokens[cursor++]);
+			}
+
+			while (cursor < len && (nextState = secondState || state.next(tokens[cursor]))) {
+
+				// Get the next state
+				secondState = null;
+				state = nextState;
+
+				// Keep track of the latest accepting state
+				if (state.accepts()) {
+					sinceAccepts = 0;
+					latestAccepting = state;
+				} else if (sinceAccepts >= 0) {
+					sinceAccepts++;
+				}
+
+				cursor++;
+				multiLength++;
+			}
+
+			if (sinceAccepts < 0) {
+
+				// No accepting state was found, part of a regular text token
+				// Add all the tokens we looked at to the text tokens array
+				for (var _i3 = cursor - multiLength; _i3 < cursor; _i3++) {
+					textTokens.push(tokens[_i3]);
+				}
+			} else {
+
+				// Accepting state!
+
+				// First close off the textTokens (if available)
+				if (textTokens.length > 0) {
+					multis.push(new TEXT(textTokens));
+					textTokens = [];
+				}
+
+				// Roll back to the latest accepting state
+				cursor -= sinceAccepts;
+				multiLength -= sinceAccepts;
+
+				// Create a new multitoken
+				var MULTI = latestAccepting.emit();
+				multis.push(new MULTI(tokens.slice(cursor - multiLength, cursor)));
+			}
+		}
+
+		// Finally close off the textTokens (if available)
+		if (textTokens.length > 0) {
+			multis.push(new TEXT(textTokens));
+		}
+
+		return multis;
+	};
+
+	var parser = Object.freeze({
+		State: State,
+		TOKENS: TOKENS$1,
+		run: run$1,
+		start: S_START$1
+	});
+
+	if (!Array.isArray) {
+		Array.isArray = function (arg) {
+			return Object.prototype.toString.call(arg) === '[object Array]';
+		};
+	}
+
+	/**
+ 	Converts a string into tokens that represent linkable and non-linkable bits
+ 	@method tokenize
+ 	@param {String} str
+ 	@return {Array} tokens
+ */
+	var tokenize = function tokenize(str) {
+		return run$1(run(str));
+	};
+
+	/**
+ 	Returns a list of linkable items in the given string.
+ */
+	var find = function find(str) {
+		var type = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+		var tokens = tokenize(str);
+		var filtered = [];
+
+		for (var i = 0; i < tokens.length; i++) {
+			var token = tokens[i];
+			if (token.isLink && (!type || token.type === type)) {
+				filtered.push(token.toObject());
+			}
+		}
+
+		return filtered;
+	};
+
+	/**
+ 	Is the given string valid linkable text of some sort
+ 	Note that this does not trim the text for you.
+
+ 	Optionally pass in a second `type` param, which is the type of link to test
+ 	for.
+
+ 	For example,
+
+ 		test(str, 'email');
+
+ 	Will return `true` if str is a valid email.
+ */
+	var test = function test(str) {
+		var type = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+		var tokens = tokenize(str);
+		return tokens.length === 1 && tokens[0].isLink && (!type || tokens[0].type === type);
+	};
+
+	exports.find = find;
+	exports.inherits = inherits;
+	exports.options = options;
+	exports.parser = parser;
+	exports.scanner = scanner;
+	exports.test = test;
+	exports.tokenize = tokenize;
+})(window.linkify = window.linkify || {});
+})();
diff --git a/web/qwebchannel.js b/web/qwebchannel.js
new file mode 100644
index 0000000..abf8461
--- /dev/null
+++ b/web/qwebchannel.js
@@ -0,0 +1,427 @@
+/****************************************************************************

+**

+** Copyright (C) 2016 The Qt Company Ltd.

+** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>

+** Contact: https://www.qt.io/licensing/

+**

+** This file is part of the QtWebChannel module of the Qt Toolkit.

+**

+** $QT_BEGIN_LICENSE:LGPL$

+** Commercial License Usage

+** Licensees holding valid commercial Qt licenses may use this file in

+** accordance with the commercial license agreement provided with the

+** Software or, alternatively, in accordance with the terms contained in

+** a written agreement between you and The Qt Company. For licensing terms

+** and conditions see https://www.qt.io/terms-conditions. For further

+** information use the contact form at https://www.qt.io/contact-us.

+**

+** GNU Lesser General Public License Usage

+** Alternatively, this file may be used under the terms of the GNU Lesser

+** General Public License version 3 as published by the Free Software

+** Foundation and appearing in the file LICENSE.LGPL3 included in the

+** packaging of this file. Please review the following information to

+** ensure the GNU Lesser General Public License version 3 requirements

+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.

+**

+** GNU General Public License Usage

+** Alternatively, this file may be used under the terms of the GNU

+** General Public License version 2.0 or (at your option) the GNU General

+** Public license version 3 or any later version approved by the KDE Free

+** Qt Foundation. The licenses are as published by the Free Software

+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3

+** included in the packaging of this file. Please review the following

+** information to ensure the GNU General Public License requirements will

+** be met: https://www.gnu.org/licenses/gpl-2.0.html and

+** https://www.gnu.org/licenses/gpl-3.0.html.

+**

+** $QT_END_LICENSE$

+**

+****************************************************************************/

+

+"use strict";

+

+var QWebChannelMessageTypes = {

+    signal: 1,

+    propertyUpdate: 2,

+    init: 3,

+    idle: 4,

+    debug: 5,

+    invokeMethod: 6,

+    connectToSignal: 7,

+    disconnectFromSignal: 8,

+    setProperty: 9,

+    response: 10,

+};

+

+var QWebChannel = function(transport, initCallback)

+{

+    if (typeof transport !== "object" || typeof transport.send !== "function") {

+        console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." +

+                      " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send));

+        return;

+    }

+

+    var channel = this;

+    this.transport = transport;

+

+    this.send = function(data)

+    {

+        if (typeof(data) !== "string") {

+            data = JSON.stringify(data);

+        }

+        channel.transport.send(data);

+    }

+

+    this.transport.onmessage = function(message)

+    {

+        var data = message.data;

+        if (typeof data === "string") {

+            data = JSON.parse(data);

+        }

+        switch (data.type) {

+            case QWebChannelMessageTypes.signal:

+                channel.handleSignal(data);

+                break;

+            case QWebChannelMessageTypes.response:

+                channel.handleResponse(data);

+                break;

+            case QWebChannelMessageTypes.propertyUpdate:

+                channel.handlePropertyUpdate(data);

+                break;

+            default:

+                console.error("invalid message received:", message.data);

+                break;

+        }

+    }

+

+    this.execCallbacks = {};

+    this.execId = 0;

+    this.exec = function(data, callback)

+    {

+        if (!callback) {

+            // if no callback is given, send directly

+            channel.send(data);

+            return;

+        }

+        if (channel.execId === Number.MAX_VALUE) {

+            // wrap

+            channel.execId = Number.MIN_VALUE;

+        }

+        if (data.hasOwnProperty("id")) {

+            console.error("Cannot exec message with property id: " + JSON.stringify(data));

+            return;

+        }

+        data.id = channel.execId++;

+        channel.execCallbacks[data.id] = callback;

+        channel.send(data);

+    };

+

+    this.objects = {};

+

+    this.handleSignal = function(message)

+    {

+        var object = channel.objects[message.object];

+        if (object) {

+            object.signalEmitted(message.signal, message.args);

+        } else {

+            console.warn("Unhandled signal: " + message.object + "::" + message.signal);

+        }

+    }

+

+    this.handleResponse = function(message)

+    {

+        if (!message.hasOwnProperty("id")) {

+            console.error("Invalid response message received: ", JSON.stringify(message));

+            return;

+        }

+        channel.execCallbacks[message.id](message.data);

+        delete channel.execCallbacks[message.id];

+    }

+

+    this.handlePropertyUpdate = function(message)

+    {

+        for (var i in message.data) {

+            var data = message.data[i];

+            var object = channel.objects[data.object];

+            if (object) {

+                object.propertyUpdate(data.signals, data.properties);

+            } else {

+                console.warn("Unhandled property update: " + data.object + "::" + data.signal);

+            }

+        }

+        channel.exec({type: QWebChannelMessageTypes.idle});

+    }

+

+    this.debug = function(message)

+    {

+        channel.send({type: QWebChannelMessageTypes.debug, data: message});

+    };

+

+    channel.exec({type: QWebChannelMessageTypes.init}, function(data) {

+        for (var objectName in data) {

+            var object = new QObject(objectName, data[objectName], channel);

+        }

+        // now unwrap properties, which might reference other registered objects

+        for (var objectName in channel.objects) {

+            channel.objects[objectName].unwrapProperties();

+        }

+        if (initCallback) {

+            initCallback(channel);

+        }

+        channel.exec({type: QWebChannelMessageTypes.idle});

+    });

+};

+

+function QObject(name, data, webChannel)

+{

+    this.__id__ = name;

+    webChannel.objects[name] = this;

+

+    // List of callbacks that get invoked upon signal emission

+    this.__objectSignals__ = {};

+

+    // Cache of all properties, updated when a notify signal is emitted

+    this.__propertyCache__ = {};

+

+    var object = this;

+

+    // ----------------------------------------------------------------------

+

+    this.unwrapQObject = function(response)

+    {

+        if (response instanceof Array) {

+            // support list of objects

+            var ret = new Array(response.length);

+            for (var i = 0; i < response.length; ++i) {

+                ret[i] = object.unwrapQObject(response[i]);

+            }

+            return ret;

+        }

+        if (!response

+            || !response["__QObject*__"]

+            || response.id === undefined) {

+            return response;

+        }

+

+        var objectId = response.id;

+        if (webChannel.objects[objectId])

+            return webChannel.objects[objectId];

+

+        if (!response.data) {

+            console.error("Cannot unwrap unknown QObject " + objectId + " without data.");

+            return;

+        }

+

+        var qObject = new QObject( objectId, response.data, webChannel );

+        qObject.destroyed.connect(function() {

+            if (webChannel.objects[objectId] === qObject) {

+                delete webChannel.objects[objectId];

+                // reset the now deleted QObject to an empty {} object

+                // just assigning {} though would not have the desired effect, but the

+                // below also ensures all external references will see the empty map

+                // NOTE: this detour is necessary to workaround QTBUG-40021

+                var propertyNames = [];

+                for (var propertyName in qObject) {

+                    propertyNames.push(propertyName);

+                }

+                for (var idx in propertyNames) {

+                    delete qObject[propertyNames[idx]];

+                }

+            }

+        });

+        // here we are already initialized, and thus must directly unwrap the properties

+        qObject.unwrapProperties();

+        return qObject;

+    }

+

+    this.unwrapProperties = function()

+    {

+        for (var propertyIdx in object.__propertyCache__) {

+            object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]);

+        }

+    }

+

+    function addSignal(signalData, isPropertyNotifySignal)

+    {

+        var signalName = signalData[0];

+        var signalIndex = signalData[1];

+        object[signalName] = {

+            connect: function(callback) {

+                if (typeof(callback) !== "function") {

+                    console.error("Bad callback given to connect to signal " + signalName);

+                    return;

+                }

+

+                object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];

+                object.__objectSignals__[signalIndex].push(callback);

+

+                if (!isPropertyNotifySignal && signalName !== "destroyed") {

+                    // only required for "pure" signals, handled separately for properties in propertyUpdate

+                    // also note that we always get notified about the destroyed signal

+                    webChannel.exec({

+                        type: QWebChannelMessageTypes.connectToSignal,

+                        object: object.__id__,

+                        signal: signalIndex

+                    });

+                }

+            },

+            disconnect: function(callback) {

+                if (typeof(callback) !== "function") {

+                    console.error("Bad callback given to disconnect from signal " + signalName);

+                    return;

+                }

+                object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];

+                var idx = object.__objectSignals__[signalIndex].indexOf(callback);

+                if (idx === -1) {

+                    console.error("Cannot find connection of signal " + signalName + " to " + callback.name);

+                    return;

+                }

+                object.__objectSignals__[signalIndex].splice(idx, 1);

+                if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) {

+                    // only required for "pure" signals, handled separately for properties in propertyUpdate

+                    webChannel.exec({

+                        type: QWebChannelMessageTypes.disconnectFromSignal,

+                        object: object.__id__,

+                        signal: signalIndex

+                    });

+                }

+            }

+        };

+    }

+

+    /**

+     * Invokes all callbacks for the given signalname. Also works for property notify callbacks.

+     */

+    function invokeSignalCallbacks(signalName, signalArgs)

+    {

+        var connections = object.__objectSignals__[signalName];

+        if (connections) {

+            connections.forEach(function(callback) {

+                callback.apply(callback, signalArgs);

+            });

+        }

+    }

+

+    this.propertyUpdate = function(signals, propertyMap)

+    {

+        // update property cache

+        for (var propertyIndex in propertyMap) {

+            var propertyValue = propertyMap[propertyIndex];

+            object.__propertyCache__[propertyIndex] = propertyValue;

+        }

+

+        for (var signalName in signals) {

+            // Invoke all callbacks, as signalEmitted() does not. This ensures the

+            // property cache is updated before the callbacks are invoked.

+            invokeSignalCallbacks(signalName, signals[signalName]);

+        }

+    }

+

+    this.signalEmitted = function(signalName, signalArgs)

+    {

+        invokeSignalCallbacks(signalName, this.unwrapQObject(signalArgs));

+    }

+

+    function addMethod(methodData)

+    {

+        var methodName = methodData[0];

+        var methodIdx = methodData[1];

+        object[methodName] = function() {

+            var args = [];

+            var callback;

+            for (var i = 0; i < arguments.length; ++i) {

+                var argument = arguments[i];

+                if (typeof argument === "function")

+                    callback = argument;

+                else if (argument instanceof QObject && webChannel.objects[argument.__id__] !== undefined)

+                    args.push({

+                        "id": argument.__id__

+                    });

+                else

+                    args.push(argument);

+            }

+

+            webChannel.exec({

+                "type": QWebChannelMessageTypes.invokeMethod,

+                "object": object.__id__,

+                "method": methodIdx,

+                "args": args

+            }, function(response) {

+                if (response !== undefined) {

+                    var result = object.unwrapQObject(response);

+                    if (callback) {

+                        (callback)(result);

+                    }

+                }

+            });

+        };

+    }

+

+    function bindGetterSetter(propertyInfo)

+    {

+        var propertyIndex = propertyInfo[0];

+        var propertyName = propertyInfo[1];

+        var notifySignalData = propertyInfo[2];

+        // initialize property cache with current value

+        // NOTE: if this is an object, it is not directly unwrapped as it might

+        // reference other QObject that we do not know yet

+        object.__propertyCache__[propertyIndex] = propertyInfo[3];

+

+        if (notifySignalData) {

+            if (notifySignalData[0] === 1) {

+                // signal name is optimized away, reconstruct the actual name

+                notifySignalData[0] = propertyName + "Changed";

+            }

+            addSignal(notifySignalData, true);

+        }

+

+        Object.defineProperty(object, propertyName, {

+            configurable: true,

+            get: function () {

+                var propertyValue = object.__propertyCache__[propertyIndex];

+                if (propertyValue === undefined) {

+                    // This shouldn't happen

+                    console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__);

+                }

+

+                return propertyValue;

+            },

+            set: function(value) {

+                if (value === undefined) {

+                    console.warn("Property setter for " + propertyName + " called with undefined value!");

+                    return;

+                }

+                object.__propertyCache__[propertyIndex] = value;

+                var valueToSend = value;

+                if (valueToSend instanceof QObject && webChannel.objects[valueToSend.__id__] !== undefined)

+                    valueToSend = { "id": valueToSend.__id__ };

+                webChannel.exec({

+                    "type": QWebChannelMessageTypes.setProperty,

+                    "object": object.__id__,

+                    "property": propertyIndex,

+                    "value": valueToSend

+                });

+            }

+        });

+

+    }

+

+    // ----------------------------------------------------------------------

+

+    data.methods.forEach(addMethod);

+

+    data.properties.forEach(bindGetterSetter);

+

+    data.signals.forEach(function(signal) { addSignal(signal, false); });

+

+    for (var name in data.enums) {

+        object[name] = data.enums[name];

+    }

+}

+

+//required for use with nodejs

+if (typeof module === 'object') {

+    module.exports = {

+        QWebChannel: QWebChannel

+    };

+}

diff --git a/webchathelpers.cpp b/webchathelpers.cpp
new file mode 100644
index 0000000..9503d2d
--- /dev/null
+++ b/webchathelpers.cpp
@@ -0,0 +1,141 @@
+/***************************************************************************
+ * Copyright (C) 2017-2018 by Savoir-faire Linux                           *
+ * Author: Alexandre Viau <alexandre.viau@savoirfairelinux.com>            *
+ * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>            *
+ * Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>              *
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>          *
+
+ *                                                                         *
+ * This program is free software; you can redistribute it and/or modify    *
+ * it under the terms of the GNU General Public License as published by    *
+ * the Free Software Foundation; either version 3 of the License, or       *
+ * (at your option) any later version.                                     *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful,         *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License       *
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ **************************************************************************/
+
+#include "webchathelpers.h"
+
+QJsonObject
+buildInteractionJson(lrc::api::ConversationModel& conversationModel,
+    const uint64_t msgId,
+    const lrc::api::interaction::Info& interaction)
+{
+    auto sender = QString(interaction.authorUri.c_str());
+    auto timestamp = QString::number(interaction.timestamp);
+    auto direction = lrc::api::interaction::isOutgoing(interaction) ? QString("out") : QString("in");
+
+    QJsonObject interactionObject = QJsonObject();
+    interactionObject.insert("text", QJsonValue(QString(interaction.body.c_str())));
+    interactionObject.insert("id", QJsonValue(QString::number(msgId)));
+    interactionObject.insert("sender", QJsonValue(sender));
+    interactionObject.insert("sender_contact_method", QJsonValue(sender));
+    interactionObject.insert("timestamp", QJsonValue(timestamp));
+    interactionObject.insert("direction", QJsonValue(direction));
+
+    switch (interaction.type)
+    {
+    case lrc::api::interaction::Type::TEXT:
+        interactionObject.insert("type", QJsonValue("text"));
+        break;
+    case lrc::api::interaction::Type::CALL:
+        interactionObject.insert("type", QJsonValue("call"));
+        break;
+    case lrc::api::interaction::Type::CONTACT:
+        interactionObject.insert("type", QJsonValue("contact"));
+        break;
+    case lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER:
+    case lrc::api::interaction::Type::INCOMING_DATA_TRANSFER: {
+        interactionObject.insert("type", QJsonValue("data_transfer"));
+        lrc::api::datatransfer::Info info = {};
+        conversationModel.getTransferInfo(msgId, info);
+        if (info.status != lrc::api::datatransfer::Status::INVALID) {
+            interactionObject.insert("totalSize", QJsonValue(qint64(info.totalSize)));
+            interactionObject.insert("progress", QJsonValue(qint64(info.progress)));
+        }
+        break;
+    }
+    case lrc::api::interaction::Type::INVALID:
+    default:
+        interactionObject.insert("type", QJsonValue(""));
+        break;
+    }
+
+    switch (interaction.status)
+    {
+    case lrc::api::interaction::Status::READ:
+        interactionObject.insert("delivery_status", QJsonValue("read"));
+        break;
+    case lrc::api::interaction::Status::SUCCEED:
+        interactionObject.insert("delivery_status", QJsonValue("sent"));
+        break;
+    case lrc::api::interaction::Status::FAILED:
+    case lrc::api::interaction::Status::TRANSFER_ERROR:
+        interactionObject.insert("delivery_status", QJsonValue("failure"));
+        break;
+    case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
+        interactionObject.insert("delivery_status", QJsonValue("unjoinable peer"));
+        break;
+    case lrc::api::interaction::Status::SENDING:
+        interactionObject.insert("delivery_status", QJsonValue("sending"));
+        break;
+    case lrc::api::interaction::Status::TRANSFER_CREATED:
+        interactionObject.insert("delivery_status", QJsonValue("connecting"));
+        break;
+    case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
+        interactionObject.insert("delivery_status", QJsonValue("accepted"));
+        break;
+    case lrc::api::interaction::Status::TRANSFER_CANCELED:
+        interactionObject.insert("delivery_status", QJsonValue("canceled"));
+        break;
+    case lrc::api::interaction::Status::TRANSFER_ONGOING:
+        interactionObject.insert("delivery_status", QJsonValue("ongoing"));
+        break;
+    case lrc::api::interaction::Status::TRANSFER_AWAITING_PEER:
+        interactionObject.insert("delivery_status", QJsonValue("awaiting peer"));
+        break;
+    case lrc::api::interaction::Status::TRANSFER_AWAITING_HOST:
+        interactionObject.insert("delivery_status", QJsonValue("awaiting host"));
+        break;
+    case lrc::api::interaction::Status::TRANSFER_TIMEOUT_EXPIRED:
+        interactionObject.insert("delivery_status", QJsonValue("awaiting peer timeout"));
+        break;
+    case lrc::api::interaction::Status::TRANSFER_FINISHED:
+        interactionObject.insert("delivery_status", QJsonValue("finished"));
+        break;
+    case lrc::api::interaction::Status::INVALID:
+    case lrc::api::interaction::Status::UNKNOWN:
+    case lrc::api::interaction::Status::UNREAD:
+    default:
+        interactionObject.insert("delivery_status", QJsonValue("unknown"));
+        break;
+    }
+    return interactionObject;
+}
+
+QString
+interactionToJsonInteractionObject(lrc::api::ConversationModel& conversationModel,
+    const uint64_t msgId,
+    const lrc::api::interaction::Info& interaction)
+{
+    auto interactionObject = buildInteractionJson(conversationModel, msgId, interaction);
+    return QString(QJsonDocument(interactionObject).toJson(QJsonDocument::Compact));
+}
+
+QString
+interactionsToJsonArrayObject(lrc::api::ConversationModel& conversationModel,
+    const std::map<uint64_t,
+    lrc::api::interaction::Info> interactions)
+{
+    QJsonArray array;
+    for (const auto& interaction : interactions) {
+        array.append(buildInteractionJson(conversationModel, interaction.first, interaction.second));
+    }
+    return QString(QJsonDocument(array).toJson(QJsonDocument::Compact));
+}
\ No newline at end of file
diff --git a/webchathelpers.h b/webchathelpers.h
new file mode 100644
index 0000000..6267a2c
--- /dev/null
+++ b/webchathelpers.h
@@ -0,0 +1,40 @@
+/***************************************************************************
+ * Copyright (C) 2017-2018 by Savoir-faire Linux                           *
+ * Author: Alexandre Viau <alexandre.viau@savoirfairelinux.com>            *
+ * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>            *
+ * Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>              *
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>          *
+
+ *                                                                         *
+ * This program is free software; you can redistribute it and/or modify    *
+ * it under the terms of the GNU General Public License as published by    *
+ * the Free Software Foundation; either version 3 of the License, or       *
+ * (at your option) any later version.                                     *
+ *                                                                         *
+ * This program is distributed in the hope that it will be useful,         *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
+ * GNU General Public License for more details.                            *
+ *                                                                         *
+ * You should have received a copy of the GNU General Public License       *
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ **************************************************************************/
+
+#pragma once
+
+#include <QJsonObject>
+#include <QJsonDocument>
+#include <QJsonArray>
+#include <QFile>
+
+#include "api/conversationmodel.h"
+
+QJsonObject buildInteractionJson(lrc::api::ConversationModel& conversationModel,
+                                 const uint64_t msgId,
+                                 const lrc::api::interaction::Info& interaction);
+QString interactionToJsonInteractionObject(lrc::api::ConversationModel& conversationModel,
+                                           const uint64_t msgId,
+                                           const lrc::api::interaction::Info& interaction);
+QString interactionsToJsonArrayObject(lrc::api::ConversationModel& conversationModel,
+                                      const std::map<uint64_t,
+                                      lrc::api::interaction::Info> interactions);
\ No newline at end of file