Commit 039001d7 authored by aviau's avatar aviau Committed by Stepan Salenikovich

New chat view using gtkwebkit

This changes the text buffer widget to a WebKitWebView so that we can
use web technologies to control the display.

This change comes with a new dependency: libwebkit2gtk-4.0. Should
this dependency not be available on the system, we can also build the
client using libwebkit2gtk-3.0. However, the links won't be clickable.

New features:
 - Implemented delivery reports.
 - Avatars are now displayed in the chat window.
 - Links in the chat window are now clickable.

When the client is launched with the -d option, you may right click on
the chat view to open up the dev tools.

In order to improve performance, one WebKitWebView is re-used for all
of the ChatViews, since we only display one at a time.

Tuleap: #1073
Change-Id: Ic945fa6c92f92e391f0362310ddc2f0fa16641bf
[stepan.salenikovich@savoirfairelinux.com: added change_view(); start
 loading webkit on window init; destroy webkit on dispose; prevent
 warning when dispose is called more than once on ChatView]
Signed-off-by: default avatarStepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
parent 2d4b4728
......@@ -81,6 +81,14 @@ FIND_PACKAGE(Gettext) #optional for translations
PKG_CHECK_MODULES(LIBQRENCODE libqrencode>=3.4)
PKG_CHECK_MODULES(LIBNM libnm-glib>=0.9.8.8) #optional to detect changes in the network
PKG_CHECK_MODULES(WEBKIT webkit2gtk-4.0)
IF(WEBKIT_FOUND)
SET(HAVE_WEBKIT2GTK4 1)
ELSE()
PKG_CHECK_MODULES(WEBKIT REQUIRED webkit2gtk-3.0)
SET(HAVE_WEBKIT2GTK4 0)
ENDIF()
IF(USE_APPINDICATOR)
PKG_CHECK_MODULES(APPINDICATOR appindicator3-0.1) #optional
IF(APPINDICATOR_FOUND)
......@@ -102,6 +110,7 @@ INCLUDE_DIRECTORIES(${EBOOK_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${LIBNOTIFY_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${APPINDICATOR_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${LIBNM_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${WEBKIT_INCLUDE_DIRS})
# link libs
LINK_DIRECTORIES(${GTK3_LIBRARY_DIRS})
......@@ -113,6 +122,7 @@ LINK_DIRECTORIES(${EBOOK_LIBRARY_DIRS})
LINK_DIRECTORIES(${LIBNOTIFY_LIBRARY_DIRS})
LINK_DIRECTORIES(${APPINDICATOR_LIBRARY_DIRS})
LINK_DIRECTORIES(${LIBNM_LIBRARY_DIRS})
LINK_DIRECTORIES(${WEBKIT_LIBRARY_DIRS})
# lib compiler flags
ADD_DEFINITIONS(${GTK3_CFLAGS})
......@@ -125,6 +135,7 @@ ADD_DEFINITIONS(${EBOOK_CFLAGS})
ADD_DEFINITIONS(${LIBNOTIFY_CFLAGS})
ADD_DEFINITIONS(${APPINDICATOR_CFLAGS})
ADD_DEFINITIONS(${LIBNM_CFLAGS})
ADD_DEFINITIONS(${WEBKIT_CFLAGS})
IF(NOT ${ENABLE_STATIC} MATCHES false)
SET(QT5_MODULE_PATH ${QT5_PATH}/lib/cmake)
......@@ -295,6 +306,8 @@ SET( SRC_FILES
src/ringwelcomeview.cpp
src/recentcontactsview.h
src/recentcontactsview.cpp
src/webkitchatcontainer.h
src/webkitchatcontainer.cpp
src/chatview.h
src/chatview.cpp
src/avatarmanipulation.h
......@@ -312,6 +325,7 @@ GLIB_COMPILE_RESOURCES( GLIB_RESOURCES_RING
SOURCE
pixmaps/pixmaps.gresource.xml
ui/ui.gresource.xml
web/web.gresource.xml
)
# IF(${ENABLE_TEST} MATCHES true)
......@@ -361,6 +375,7 @@ TARGET_LINK_LIBRARIES(gnome-ring
${LIBNOTIFY_LIBRARIES}
${APPINDICATOR_LIBRARIES}
${LIBNM_LIBRARIES}
${WEBKIT_LIBRARIES}
${LIBQRENCODE_LIBRARIES}
-lpthread
-lrt
......@@ -376,6 +391,7 @@ TARGET_LINK_LIBRARIES(gnome-ring
${LIBNOTIFY_LIBRARIES}
${APPINDICATOR_LIBRARIES}
${LIBNM_LIBRARIES}
${WEBKIT_LIBRARIES}
${LIBQRENCODE_LIBRARIES}
)
ENDIF()
......
This diff is collapsed.
......@@ -17,10 +17,10 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _CHATVIEW_H
#define _CHATVIEW_H
#pragma once
#include <gtk/gtk.h>
#include "webkitchatcontainer.h"
class Call;
class ContactMethod;
......@@ -39,14 +39,12 @@ typedef struct _ChatViewClass ChatViewClass;
GType chat_view_get_type (void) G_GNUC_CONST;
GtkWidget *chat_view_new_call (Call*);
GtkWidget *chat_view_new_cm (ContactMethod*);
GtkWidget *chat_view_new_person (Person* p);
GtkWidget *chat_view_new_call (WebKitChatContainer* view, Call* call);
GtkWidget *chat_view_new_cm (WebKitChatContainer* view, ContactMethod* cm);
GtkWidget *chat_view_new_person (WebKitChatContainer* view, Person* p);
Call *chat_view_get_call (ChatView*);
ContactMethod *chat_view_get_cm (ChatView*);
Person *chat_view_get_person (ChatView*);
void chat_view_set_header_visible(ChatView*, gboolean);
G_END_DECLS
#endif /* _CHATVIEW_H */
......@@ -7,6 +7,7 @@
#define USE_LIBNOTIFY @USE_LIBNOTIFY@
#define USE_APPINDICATOR @HAVE_APPINDICATOR@
#define USE_LIBNM @USE_LIBNM@
#define HAVE_WEBKIT2GTK4 @HAVE_WEBKIT2GTK4@
#define RING_CLIENT_APP_ID "cx.ring.RingGnome"
......
......@@ -81,6 +81,10 @@ struct _CurrentCallViewPrivate
GtkWidget *scalebutton_quality;
GtkWidget *checkbutton_autoquality;
/* The webkit_chat_container is created once, then reused for all chat
* views */
GtkWidget *webkit_chat_container;
/* flag used to keep track of the video quality scale pressed state;
* we do not want to update the codec bitrate until the user releases the
* scale button */
......@@ -749,7 +753,7 @@ set_call_info(CurrentCallView *view, Call *call) {
}
/* init chat view */
auto chat_view = chat_view_new_call(priv->call);
auto chat_view = chat_view_new_call(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), priv->call);
gtk_container_add(GTK_CONTAINER(priv->frame_chat), chat_view);
/* check if there were any chat notifications and open the chat view if so */
......@@ -761,9 +765,11 @@ set_call_info(CurrentCallView *view, Call *call) {
}
GtkWidget *
current_call_view_new(Call *call)
current_call_view_new(Call *call, WebKitChatContainer *webkit_chat_container)
{
auto self = g_object_new(CURRENT_CALL_VIEW_TYPE, NULL);
CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
set_call_info(CURRENT_CALL_VIEW(self), call);
return GTK_WIDGET(self);
......
......@@ -21,6 +21,7 @@
#define _CURRENTCALLVIEW_H
#include <gtk/gtk.h>
#include "webkitchatcontainer.h"
class Call;
......@@ -37,7 +38,7 @@ typedef struct _CurrentCallViewClass CurrentCallViewClass;
GType current_call_view_get_type (void) G_GNUC_CONST;
GtkWidget *current_call_view_new (Call*);
GtkWidget *current_call_view_new (Call*, WebKitChatContainer*);
Call *current_call_view_get_call (CurrentCallView*);
G_END_DECLS
......
......@@ -58,6 +58,10 @@ struct _IncomingCallViewPrivate
GtkWidget *button_end_call;
GtkWidget *frame_chat;
/* The webkit_chat_container is created once, then reused for all chat
* views */
GtkWidget *webkit_chat_container;
Call *call;
QMetaObject::Connection state_change_connection;
......@@ -228,16 +232,20 @@ set_call_info(IncomingCallView *view, Call *call) {
);
/* show chat */
auto chat_view = chat_view_new_cm(priv->call->peerContactMethod());
auto chat_view = chat_view_new_cm(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), priv->call->peerContactMethod());
gtk_widget_show(chat_view);
chat_view_set_header_visible(CHAT_VIEW(chat_view), FALSE);
gtk_container_add(GTK_CONTAINER(priv->frame_chat), chat_view);
}
GtkWidget *
incoming_call_view_new(Call *call)
incoming_call_view_new(Call *call, WebKitChatContainer *webkit_chat_container)
{
auto self = g_object_new(INCOMING_CALL_VIEW_TYPE, NULL);
IncomingCallViewPrivate *priv = INCOMING_CALL_VIEW_GET_PRIVATE(self);
priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
set_call_info(INCOMING_CALL_VIEW(self), call);
return GTK_WIDGET(self);
......
......@@ -21,6 +21,7 @@
#define _INCOMINGCALLVIEW_H
#include <gtk/gtk.h>
#include "webkitchatcontainer.h"
class Call;
......@@ -37,7 +38,7 @@ typedef struct _IncomingCallViewClass IncomingCallViewClass;
GType incoming_call_view_get_type (void) G_GNUC_CONST;
GtkWidget *incoming_call_view_new (Call*);
GtkWidget *incoming_call_view_new (Call*, WebKitChatContainer*);
Call *incoming_call_view_get_call (IncomingCallView*);
G_END_DECLS
......
......@@ -44,6 +44,7 @@ option_debug_cb(G_GNUC_UNUSED const gchar *option_name,
G_GNUC_UNUSED GError **error)
{
g_setenv("G_MESSAGES_DEBUG", "all", TRUE);
g_setenv("RING_CHATVIEW_DEBUG", "true", TRUE);
g_debug("debug enabled");
return TRUE;
}
......
This diff is collapsed.
This diff is collapsed.
/*
* Copyright (C) 2016 Savoir-faire Linux Inc.
* Author: Alexandre Viau <alexandre.viau@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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include <gtk/gtk.h>
class QModelIndex;
class QString;
class QVariant;
G_BEGIN_DECLS
#define WEBKIT_CHAT_CONTAINER_TYPE (webkit_chat_container_get_type ())
#define WEBKIT_CHAT_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WEBKIT_CHAT_CONTAINER_TYPE, WebKitChatContainer))
#define WEBKIT_CHAT_CONTAINER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), WEBKIT_CHAT_CONTAINER_TYPE, WebKitChatContainerClass))
#define IS_WEBKIT_CHAT_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), WEBKIT_CHAT_CONTAINER_TYPE))
#define IS_WEBKIT_CHAT_CONTAINER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), WEBKIT_CHAT_CONTAINER_TYPE))
typedef struct _WebKitChatContainer WebKitChatContainer;
typedef struct _WebKitChatContainerClass WebKitChatContainerClass;
GType webkit_chat_container_get_type (void) G_GNUC_CONST;
GtkWidget* webkit_chat_container_new (void);
void webkit_chat_container_clear (WebKitChatContainer *view);
void webkit_chat_container_print_new_message (WebKitChatContainer *view, const QModelIndex &idx);
void webkit_chat_container_update_message (WebKitChatContainer *view, const QModelIndex &idx);
void webkit_chat_container_set_sender_image (WebKitChatContainer *view, QString sender_name, QVariant sender_image);
gboolean webkit_chat_container_is_ready (WebKitChatContainer *view);
G_END_DECLS
......@@ -78,13 +78,10 @@
<property name="visible">True</property>
<property name="min-content-height">100</property>
<child>
<object class="GtkTextView" id="textview_chat">
<object class="GtkBox" id="box_webkit_chat_container">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="wrap-mode">word-char</property>
<property name="left-margin">5</property>
<property name="right-margin">5</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
</object>
</child>
</object>
......
......@@ -21,5 +21,6 @@
<file preprocess="xml-stripblanks">chatview.ui</file>
<file preprocess="xml-stripblanks">avatarmanipulation.ui</file>
<file preprocess="xml-stripblanks">accountimportexportview.ui</file>
<file preprocess="xml-stripblanks">webkitchatcontainer.ui</file>
</gresource>
</gresources>
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.10"/>
<template class="WebKitChatContainer" parent="GtkBox">
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="WebKitWebView" id="webview_chat">
<property name="visible">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
</object>
</child>
</object>
</child>
</template>
</interface>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/*
* 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 linkifyJquery = function (jquery, linkify) {
'use strict';
/**
Linkify a HTML DOM node
*/
var tokenize = linkify.tokenize;
var options = linkify.options;
var Options = options.Options;
var TEXT_TOKEN = linkify.parser.TOKENS.TEXT;
var HTML_NODE = 1;
var TXT_NODE = 3;
/**
Given a parent element and child node that the parent contains, replaces
that child with the given array of new children
*/
function replaceChildWithChildren(parent, oldChild, newChildren) {
var lastNewChild = newChildren[newChildren.length - 1];
parent.replaceChild(lastNewChild, oldChild);
for (var i = newChildren.length - 2; i >= 0; i--) {
parent.insertBefore(newChildren[i], lastNewChild);
lastNewChild = newChildren[i];
}
}
/**
Given an array of MultiTokens, return an array of Nodes that are either
(a) Plain Text nodes (node type 3)
(b) Anchor tag nodes (usually, unless tag name is overridden in the options)
Takes the same options as linkifyElement and an optional doc element
(this should be passed in by linkifyElement)
*/
function tokensToNodes(tokens, opts, doc) {
var result = [];
for (var _iterator = tokens, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
var _ref;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref = _i.value;
}
var token = _ref;
if (token.type === 'nl' && opts.nl2br) {
result.push(doc.createElement('br'));
continue;
} else if (!token.isLink || !opts.check(token)) {
result.push(doc.createTextNode(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 events = _opts$resolve.events;
var attributes = _opts$resolve.attributes;
// Build the link
var link = doc.createElement(tagName);
link.setAttribute('href', formattedHref);
if (className) {
link.setAttribute('class', className);
}
if (target) {
link.setAttribute('target', target);
}
// Build up additional attributes
if (attributes) {
for (var attr in attributes) {
link.setAttribute(attr, attributes[attr]);
}
}
if (events) {
for (var event in events) {
if (link.addEventListener) {
link.addEventListener(event, events[event]);
} else if (link.attachEvent) {
link.attachEvent('on' + event, events[event]);
}
}
}
link.appendChild(doc.createTextNode(formatted));
result.push(link);
}
return result;
}
// Requires document.createElement
function linkifyElementHelper(element, opts, doc) {
// Can the element be linkified?
if (!element || element.nodeType !== HTML_NODE) {
throw new Error('Cannot linkify ' + element + ' - Invalid DOM Node type');
}
var ignoreTags = opts.ignoreTags;
// Is this element already a link?
if (element.tagName === 'A' || options.contains(ignoreTags, element.tagName)) {
// No need to linkify
return element;
}
var childElement = element.firstChild;
while (childElement) {
switch (childElement.nodeType) {
case HTML_NODE:
linkifyElementHelper(childElement, opts, doc);
break;
case TXT_NODE:
var str = childElement.nodeValue;
var tokens = tokenize(str);
if (tokens.length === 0 || tokens.length === 1 && tokens[0] instanceof TEXT_TOKEN) {
// No node replacement required
break;
}
var nodes = tokensToNodes(tokens, opts, doc);
// Swap out the current child for the set of nodes
replaceChildWithChildren(element, childElement, nodes);
// so that the correct sibling is selected next
childElement = nodes[nodes.length - 1];
break;
}
childElement = childElement.nextSibling;
}
return element;
}
function linkifyElement(element, opts) {
var doc = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
try {
doc = doc || document || window && window.document || global && global.document;
} catch (e) {/* do nothing for now */}
if (!doc) {
throw new Error('Cannot find document implementation. ' + 'If you are in a non-browser environment like Node.js, ' + 'pass the document implementation as the third argument to linkifyElement.');
}
opts = new Options(opts);
return linkifyElementHelper(element, opts, doc);
}
// Maintain reference to the recursive helper to cache option-normalization
linkifyElement.helper = linkifyElementHelper;
linkifyElement.normalize = function (opts) {
return new Options(opts);
};
// Applies the plugin to jQuery
function apply($) {
var doc = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
$.fn = $.fn || {};
try {
doc = doc || document || window && window.document || global && global.document;
} catch (e) {/* do nothing for now */}
if (!doc) {
throw new Error('Cannot find document implementation. ' + 'If you are in a non-browser environment like Node.js, ' + 'pass the document implementation as the second argument to linkify/jquery');
}
if (typeof $.fn.linkify === 'function') {
// Already applied
return;
}
function jqLinkify(opts) {
opts = linkifyElement.normalize(opts);
return this.each(function () {
linkifyElement.helper(this, opts, doc);
});
}
$.fn.linkify = jqLinkify;
$(doc).ready(function () {
$('[data-linkify]').each(function () {
var $this = $(this);
var data = $this.data();
var target = data.linkify;
var nl2br = data.linkifyNlbr;
var options = {
attributes: data.linkifyAttributes,
defaultProtocol: data.linkifyDefaultProtocol,
events: data.linkifyEvents,
format: data.linkifyFormat,
formatHref: data.linkifyFormatHref,
nl2br: !!nl2br && nl2br !== 0 && nl2br !== 'false',
tagName: data.linkifyTagname,
target: data.linkifyTarget,
className: data.linkifyClassName || data.linkifyLinkclass, // linkClass is deprecated
validate: data.linkifyValidate,
ignoreTags: data.linkifyIgnoreTags
};
var $target = target === 'this' ? $this : $this.find(target);
$target.linkify(options);
});
});
}
// Try assigning linkifyElement to the browser scope
try {
var a = !define && (window.linkifyElement = linkifyElement);
} catch (e) {}
return apply;
}($, linkify);
if (typeof $.fn.linkify !== 'function') {
linkifyJquery($);
}
})(window, linkify, jQuery);
/*
* Copyright (c) 2016 SoapBox Innovations Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
'use strict';
;(function (window, linkify) {
var linkifyString = function (linkify) {
'use strict';
/**
Convert strings of text into linkable HTML text
*/
var tokenize = linkify.tokenize;
var options = linkify.options;
var Options = options.Options;
function escapeText(text) {
return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function escapeAttr(href) {
return href.replace(/"/g, '&quot;');
}
function attributesToString(attributes) {
if (!attributes) {
return '';
}
var result = [];
for (var attr in attributes) {
var val = attributes[attr] + '';
result.push(attr + '="' + escapeAttr(val) + '"');
}
return result.join(' ');
}
function linkifyStr(str) {
var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
opts = new Options(opts);
var tokens = tokenize(str);
var result = [];
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (token.type === 'nl' && opts.nl2br) {
result.push('<br>\n');
continue;
} else if (!token.isLink || !opts.check(token)) {
result.push(escapeText(token.toString()));
continue;
}
var _opts$resolve = opts.resolve(token);
var formatted = _opts$resolve.formatted;
var formattedHref = _opts$resolve.formattedHref;
var tagName = _opts$resolve.tagName;
var className = _opts$resolve.className;
var target = _opts$resolve.target;
var attributes = _opts$resolve.attributes;
var link = '<' + tagName + ' href="' + escapeAttr(formattedHref) + '"';
if (className) {
link += ' class="' + escapeAttr(className) + '"';
}
if (target) {
link += ' target="' + escapeAttr(target) + '"';
}
if (attributes) {
link += ' ' + attributesToString(attributes);
}
link += '>' + escapeText(formatted) + '</' + tagName + '>';
result.push(link);
}
return result.join('');
}
if (!String.prototype.linkify) {
String.prototype.linkify = function (opts) {
return linkifyStr(this, opts);
};
}
return linkifyStr;
}(linkify);
window.linkifyStr = linkifyString;
})(window, linkify);
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/cx/ring/RingGnome">
<!-- HTML -->
<file>chatview.html</file>
<!-- JavaScript -->
<file>jquery.js</file>
<file>linkify.js</file>
<file>linkify-string.js</file>
<file>linkify-html.js</file>
<file>linkify-jquery.js</file>
</gresource>
</gresources>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment