Enable links in the default Chat application


#1

If you want to have web links in chat (very usefull in VR mode where can’t copy links) you must change a little the ChatPage.html file.
Using this moddified html file, you can:

  • load websites,
  • load json files
  • load external JavaScript files (based on user confirmation dialog)
  • portal dropper (by typing hifi://domainname in chat, each user that will click on this link will go to that domain)
  • etc.

The code of the new html page is this:

<!--
//  ChatPage.html
//
//  Created by Faye Li on 3 Feb 2017
//  Modified by Don Hopkins (dhopkins@donhopkins.com).
//  Copyright 2017 High Fidelity, Inc.
//
//  Distributed under the Apache License, Version 2.0.
//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-->
<html>
    <head>
        <title>Chat</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="https://fonts.googleapis.com/css?family=Raleway:300,400,600,700"" rel="stylesheet">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>

        <style>
            input[type=button],
            button {
                font-family: 'Raleway';
                font-weight: bold;
                font-size: 20px;
                vertical-align: top;
                width: 100%;
                height: 40px;
                min-width: 120px;
                padding: 0px 18px;
                margin-top: 5px;
                margin-right: 6px;
                border-radius: 5px;
                border: none;
                color: #fff;
                background-color: #000;
                background: linear-gradient(#343434 20%, #000 100%);
                cursor: pointer;
            }

            .commandButton {
                width: 100px !important;
            }

            input[type=text] {
                font-family: 'Raleway';
                font-weight: bold;
                font-size: 20px;
                vertical-align: top;
                height: 40px;
                color: #000;
                width: 100%;
                background-color: #fff;
                background: linear-gradient(#343434 20%, #fff 100%);
            }

            input[type=button].red {
                color: #fff;
                background-color: #94132e;
                background: linear-gradient(#d42043 20%, #94132e 100%);
            }
            input[type=button].blue {
                color: #fff;
                background-color: #1080b8;
                background: linear-gradient(#00b4ef 20%, #1080b8 100%);
            }
            input[type=button].white {
                color: #121212;
                background-color: #afafaf;
                background: linear-gradient(#fff 20%, #afafaf 100%);
            }

            input[type=button]:enabled:hover {
                background: linear-gradient(#000, #000);
                border: none;
            }
            input[type=button].red:enabled:hover {
                background: linear-gradient(#d42043, #d42043);
                border: none;
            }
            input[type=button].blue:enabled:hover {
                background: linear-gradient(#00b4ef, #00b4ef);
                border: none;
            }
            input[type=button].white:enabled:hover {
                background: linear-gradient(#fff, #fff);
                border: none;
            }

            input[type=button]:active {
                background: linear-gradient(#343434, #343434);
            }
            input[type=button].red:active {
                background: linear-gradient(#94132e, #94132e);
            }
            input[type=button].blue:active {
                background: linear-gradient(#1080b8, #1080b8);
            }
            input[type=button].white:active {
                background: linear-gradient(#afafaf, #afafaf);
            }

            input[type=button]:disabled {
                color: #252525;
                background: linear-gradient(#575757 20%, #252525 100%);
            }

            input[type=button][pressed=pressed] {
                color: #00b4ef;
            }

            body {
                width: 100%;
                overflow-x: hidden;
                overflow-y: hidden;
                margin: 0;
                font-family: 'Raleway', sans-serif;
                color: white;
                background: linear-gradient(#2b2b2b, #0f212e);
            }

            .Content {
                font-size: 20px;
                width: 100%;
                height: 100%; 
                display: flex; 
                flex-direction: column;
            }

            .TopBar {
                height: 40px;
                background: linear-gradient(#2b2b2b, #1e1e1e);
                font-weight: bold;
                padding: 10px 10px 10px 10px;
                display: flex;
                align-items: center;
                width: 100%;
                font-size: 28px;
                flex-grow: 0;
            }

            .ChatLog {
                padding: 20px;
                font-size: 20px;
                flex-grow: 1;
                color: white;
                background-color: black;
                overflow-x: hidden;
                overflow-y: scroll;
                word-wrap: break-word;
            }

            .ChatLogLine {
                margin-bottom: 15px;
            }

            .ChatLogLineDisplayName {
                font-weight: bold;
            }

            .ChatLogLineMessage {
            }

            .LogLogLine {
                margin-bottom: 15px;
            }

            .LogLogLineMessage {
                font-style: italic;
            }

            .ChatInput {
                display: flex;
                flex-direction: row;
                flex-grow: 0;
            }

            .ChatInputText {
                padding: 20px 20px 20px 20px;
                height: 60px !important;
                font-size: 20px !important;
                flex-grow: 1;
                display: flex;
                flex-direction: column;
            }
			.responsive {
				width: 100%;
				max-width: 420px;
				height: auto;
			}

        </style>
    </head>
    <body>

        <div class="Content">

            <div class="TopBar">
                <b>Chat</b>
            </div>

            <div class="ChatLog" id="ChatLog"></div>

            <div class="ChatInput">
              <input type="text" class="ChatInputText" id="ChatInputText" size="256" maxlength="256" />
            </div>

        </div>

    </body>

    <script>
//linky function

(function($) {

    "use strict";

    $.fn.linky = function(options) {
        return this.each(function() {
            var $el = $(this),
                linkifiedContent = _linkify($el, options);

            $el.html(linkifiedContent);
        });
    };

    function _linkify($el, options) {
        var links = {
                twitter: {
                    baseUrl: "https://twitter.com/",
                    hashtagSearchUrl: "hashtag/"
                },
                instagram: {
                    baseUrl: "http://instagram.com/",
                    hashtagSearchUrl: null // Doesn't look like there is one?
                },
                github: {
                    baseUrl: "https://github.com/",
                    hashtagSearchUrl: null
                }
            },
            defaultOptions = {
                mentions: false,
                hashtags: false,
                urls: true,
                linkTo: "twitter" // Let's default to Twitter
            },
            extendedOptions = $.extend(defaultOptions, options),
            elContent = $el.html(),
            // Regular expression courtesy of Matthew O'Riordan, see: http://goo.gl/3syEKK
            urlRegEx = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;,:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+,~%\/\.\w\-]*)?\??(?:[\-\+=&;,:%@\.\w]*)#?(?:[\.\!\/\\\w]*))?)/g,
            matches;

            // Linkifying URLs
            if (extendedOptions.urls) {
                matches = elContent.match(urlRegEx);
                if (matches) {
                    elContent = _linkifyUrls(matches, $el);
                }
            }

            // Linkifying mentions
            if (extendedOptions.mentions) {
                elContent = _linkifyMentions(elContent, links[extendedOptions.linkTo].baseUrl);
            }

            // Linkifying hashtags
            if (extendedOptions.hashtags) {
                elContent = _linkifyHashtags(elContent, links[extendedOptions.linkTo]);
            }

        return elContent;
    }

    // For any URLs present, unless they are already identified within
    // an `a` element, linkify them.
    function _linkifyUrls(matches, $el) {
        var elContent = $el.html();

        $.each(matches, function() {
		
			//var arr = [ "jpeg", "jpg", "gif", "png" ];
			var ext = this.split('.').pop();
			
			switch(true){
			case ext == "png":
			case ext == "jpg":
			case ext == "gif":
			case ext == "jpeg":
				elContent = elContent.replace(this, "<img src='" + this + "'class=\"responsive\">");
				break;
			case ext == "webm":
				elContent = elContent.replace(this, "<video controls class=\"responsive\"><source src='" + this + "' type='video/" + ext + "'></video>");
				break;
			case !!this.match(/^(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?(?=.*v=((\w|-){11}))(?:\S+)?$/):
				var youtubeMatch = this.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/);
				if(youtubeMatch && youtubeMatch[2].length == 11 && this.match(/^(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?(?=.*v=((\w|-){11}))(?:\S+)?$/)){
					elContent = "<iframe width='420' height='236' src='//www.youtube.com/embed/" + youtubeMatch[2] + "' frameborder='0'></iframe>";
					break;
				}
				// else fall through to default
			default:
					elContent = elContent.replace(this, "<a href='" + this + "' target='_blank'>" + this + "</a>");
				break;
			}

        });
        return elContent;
    }

    // Find any mentions (e.g. @andrs) and turn them into links that
    // refer to the appropriate social profile (e.g. twitter or instagram).
    function _linkifyMentions(text, baseUrl) {
        return text.replace(/(^|\s|\(|>)@(\w+)/g, "$1<a href='" + baseUrl + "$2' target='_blank'>@$2</a>");
    }

    // Find any hashtags (e.g. #linkyrocks) and turn them into links that refer
    // to the appropriate social profile.
    function _linkifyHashtags(text, links) {
        // If there is no search URL for a hashtag, there isn't much we can do
        if (links.hashtagSearchUrl === null) return text;
        return text.replace(/(^|\s|\(|>)#((\w|[\u00A1-\uFFFF])+)/g, "$1<a href='" + links.baseUrl + links.hashtagSearchUrl + "$2' target='_blank'>#$2</a>");
    }

}(jQuery));

        //console.log("ChatPage: loading script...");

        var messageData = {}; // The data that is sent along with the message. 
        var typing = false; // True while the user is typing.
        var typingTimerDuration = 1; // How long to wait before ending typing, in seconds.
        var typingTimer = null; // The timer to end typing.
        var $ChatLog; // The scrolling chat log.
        var $ChatInputText; // The text field for entering text.

        // Recreate the lines in chatLog as the DOM in $ChatLog.
        function updateChatLog() {
            $ChatLog.html('');
            for (var i = 0, n = chatLog.length; i < n; i++) {
                var a = chatLog[i];
                var avatarID = a[0];
                var displayName = a[1];
                var message = a[2];
                var data = a[3];
                //console.log("updateChatLog", i, a, displayName, message);
                if (avatarID) {
                    receiveChatMessage(avatarID, displayName, message, data);
                } else {
                    logMessage(message);
                }
            }
        }

        // Call this no every keystroke.
        function type() {
            beginTyping();
            handleType();
        }

        // Reset the typing timer, and notify if we're starting.
        function beginTyping() {
            if (typingTimer) {
                clearTimeout(typingTimer);
            }

            typingTimer = setTimeout(function() {
                typing = false;
                handleEndTyping();
            }, typingTimerDuration * 1000);

            if (typing) {
                return;
            }

            typing = true;
            handleBeginTyping();
        }

        // Clear the typing timer and notify if we're finished.
        function endTyping() {
            if (typingTimer) {
                clearTimeout(typingTimer);
                typingTimer = null;
            }

            if (!typing) {
                return;
            }

            typing = false;
            handleEndTyping();
        }

        // Notify the interface script on every keystroke.
        function handleType() {
            EventBridge.emitWebEvent(
                JSON.stringify({
                    type: "Type"
                }));
        }

        // Notify the interface script when we begin typing.
        function handleBeginTyping() {
            EventBridge.emitWebEvent(
                JSON.stringify({
                    type: "BeginTyping"
                }));
        }

        // Notify the interface script when we end typing.
        function handleEndTyping() {
            EventBridge.emitWebEvent(
                JSON.stringify({
                    type: "EndTyping"
                }));
        }

        // Append a chat message to $ChatLog.
        function receiveChatMessage(avatarID, displayName, message, data) {
            var $logLine =
                $('<div/>')
                    .addClass('ChatLogLine')
                    .data('chat-avatarID', avatarID)
                    .data('chat-displayName', displayName)
                    .data('chat-message', message)
                    .data('chat-data', data)
                    .appendTo($ChatLog);
		    
            var $logLineDisplayName =
                $('<span/>')
                    .addClass('ChatLogLineDisplayName')
                    .text(displayName + ': ')
                    .on('mouseenter', function(event) {
                        identifyAvatar(avatarID);
                        //event.cancelBubble();
                        $logLineDisplayName.css({
                            color: '#00ff00',
                            'text-decoration': 'underline'
                        });
                    })
                    .on('mouseleave', function(event) {
                        unidentifyAvatar(avatarID);
                        //event.cancelBubble();
                        $logLineDisplayName.css({
                            color: 'white',
                            'text-decoration': 'none'
                        });
                    })
                    .click(function(event) {
                        faceAvatar(avatarID, displayName);
                        //event.cancelBubble();
                    })
                    .appendTo($logLine);
            var $logLineMessage =
                $('<span/>')
                    .addClass('ChatLogLineMessage')
                    .text(message).linky()
                    .appendTo($logLine);
        }

        // Append a log message to $ChatLog.
        function logMessage(message) {
            var $logLine =
                $('<div/>')
                    .addClass('LogLogLine')
                    .data('chat-message', message)
                    .appendTo($ChatLog);
            var $logLineMessage =
                $('<span/>')
                    .addClass('LogLogLineMessage')
                    .text(message)
                    .appendTo($logLine);
        }

        // Scroll $ChatLog so the last line is visible.
        function scrollChatLog() {
            var $logLine = $ChatLog.children().last();
            if (!$logLine || !$logLine.length) {
                return;
            }
            var chatLogHeight = $ChatLog.outerHeight(true);
            var logLineTop = ($logLine.offset().top - $ChatLog.offset().top);
            var logLineBottom = logLineTop + $logLine.outerHeight(true);
            var scrollUp = logLineBottom - chatLogHeight;
            if (scrollUp > 0) {
                $ChatLog.scrollTop($ChatLog.scrollTop() + scrollUp);
            }
        }

        // Tell the interface script we have initialized.
        function emitReadyEvent() {
            EventBridge.emitWebEvent(
                JSON.stringify({
                    type: "Ready"
                }));
        }

        // The user entered an empty chat message.
        function emptyChatMessage() {
            EventBridge.emitWebEvent(
                JSON.stringify({
                    type: "EmptyChatMessage",
                    data: null
                }));
        }

        // The user entered a non-empty chat message.
        function handleChatMessage(message, data) {
            //console.log("handleChatMessage", message);
            EventBridge.emitWebEvent(
                JSON.stringify({
                    type: "HandleChatMessage",
                    message: message,
                    data: data
                }));
        }

        // Clear the chat log, of course.
        function clearChatLog() {
            EventBridge.emitWebEvent(
                JSON.stringify({
                    type: "ClearChatLog"
                }));
        }

        // Identify an avatar.
        function identifyAvatar(avatarID) {
            EventBridge.emitWebEvent(
                JSON.stringify({
                    type: "IdentifyAvatar",
                    avatarID: avatarID
                }));
        }

        // Stop identifying an avatar.
        function unidentifyAvatar(avatarID) {
            EventBridge.emitWebEvent(
                JSON.stringify({
                    type: "UnidentifyAvatar",
                    avatarID: avatarID
                }));
        }

        // Face an avatar.
        function faceAvatar(avatarID, displayName) {
            EventBridge.emitWebEvent(
                JSON.stringify({
                    type: "FaceAvatar",
                    avatarID: avatarID,
                    displayName: displayName
                }));
        }

        // Let's get this show on the road!
        function main() {

            //console.log("ChatPage: main");

            $ChatLog = $('#ChatLog');
            $ChatInputText = $('#ChatInputText');

            // Whenever the chat log resizes, or the input text gets or loses focus, 
            // scroll the chat log to the last line.
            $ChatLog.on('resize', function(event) {
                //console.log("ChatLog resize", $ChatLog, event);
                scrollChatLog();
            });
            $ChatInputText.on('focus blur', function(event) {
                //console.log("ChatInputText focus blur", $ChatInputText, event);
                scrollChatLog();
            });

            // Track when the user is typing, and handle the message when the user hits return.
            $ChatInputText.on('keydown', function(event) {
                type();
                if (event.keyCode == 13) {
                    var message = $ChatInputText.val().substr(0, 256);
                    $ChatInputText.val('');
                    if (message == '') {
                        emptyChatMessage();
                    } else {
                        handleChatMessage(message, messageData);
                    }
                    endTyping();
                }
            });

            // Start out with the input text in focus.
            $ChatInputText.focus();

            // Hook up a handler for events that come from hifi.
            EventBridge.scriptEventReceived.connect(function (message) {

                //console.log("ChatPage: main: scriptEventReceived", message);

                var messageData = JSON.parse(message);
                var messageType = messageData['type'];

                switch (messageType) {

                    case "Update":
                        chatLog = messageData['chatLog'];
                        updateChatLog();
                        scrollChatLog();
                        break;

                    case "ReceiveChatMessage":
                        receiveChatMessage(messageData['avatarID'], messageData['displayName'], messageData['message'], message['data']);
                        scrollChatLog();
                        break;

                    case "LogMessage":
                        logMessage(messageData['message']);
                        scrollChatLog();
                        break;

                    default:
                        console.log("WEB: received unexpected script event message: " + message);
                        break;

                }
            });

            emitReadyEvent();
        }

        // Start up once the document is loaded.
        $(document).ready(main);

    </script>

</html>

You can also download this html file from:

https://transmissiongate.com/linkify/ChatPage.zip

Unzip the zip file and copy ChatPage.html to this location:

C:\Program Files\High Fidelity\scripts\system\html\

Load or refresh the chat.js script in Edit / Running Scripts

Or even simpler (thanx @Manithal for the idea), without editing and replacing the file, just run this chat script:

https://transmissiongate.com/hifi/chat/chat.js

Enjoy Hyperlinking!


#2

yaays good job
can or do u know how to make this chat work like the chat in help where it auto loads and notifies u with a red dot, if anyone messages anything?
I think it might be nice to stick somthing like that in our domains
then we can try to bypas the people shall only text chat externally decree that philip seems to have cursed us all with


#3

I will try to figure it out.


#4

i replaced the html file with yours pasted a link in but it dosent appear clickable


#5

You must reload the chat.js and maybe clear cache


#6

i re loaded ill try clearing the cache nah still nothing (and you wonder why i want an auto loader lol)


#7

Try to close Chat.js script and open the chat.js from system scripts


#8

Would it not be easier just to create a copy of the chat.js script and point it to your new one? :slight_smile: instead of playing around with replacement issues


#9

ah that worked openeing it from the system scripts


#10

yes, will be easier, I think.
You can load this script:

http://transmissiongate.com/hifi/chat/chat.js

#11

next problem lol

to err is human


#12

you must use links like: http://highfidelity.io or https://www.google.com or hifi://help


#13

that worked , is it possible to make it so you dont need that bit?

if i try and open this


link it says coudl not upload the asset to the asset server


#15

George! This is so great!

If you put a hifi:// URL in there, it will even teleport you. This means you could use it as a portal dropper! All you need to do is not open that browser window after a hifi:// hyperlink is entered.

Also, if you have commas in the hifi URL, the need encoding or it won’t record them

for example, this will work

hifi://codex/1994.99%2C1996.47%2C1979.37%2F0%2C0.979946%2C0%2C0.199261%0A

It would be cool if you could add a button that says something like “Add location” at the top of the text chat, and if you press it it would automatically include the properly encoded location as a URL!


#16

I’ve managed to solve this issue, by dealing with REGEX.

Now we can use full links for domains and also webpages with ports and multiple variable.

I will continue to test it.


#17

I’ve updated the html in order to display images in chat, for instance if you will pass an address like this:

https://transmissiongate.com/hifi/green-apple.jpg

In chat we will have this:

green-apple


#18

perfect u sir are a genius

wow gifs work too :smiley:


#19

This is go great! Tested several until I found one that borked:

I think it’s the asterisk (how weird to use an asterisk like that!)


#20

Script updated. Now you can paste Youtube links like:

https://www.youtube.com/watch?v=2OU4zdqO8j0

or webm video links like:

https://www.quirksmode.org/html5/videos/big_buck_bunny.webm

Enjoy!


#21

ahaha 2 cool youtube vidss
knows u like a challenge soooo
can u add in giphy support
and knows u made vids sync b4 could they in this?
make me love again :stuck_out_tongue: