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 ¤tIdx, 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 ¤tIdx, 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>NUL >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>NUL >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, '"');
+ }
+
+ 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, '&').replace(/</g, '<').replace(/>/g, '>');
+ }
+
+ function escapeAttr(href) {
+ return href.replace(/"/g, '"');
+ }
+
+ 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