<?php
/**
 * SeekQuarry/Yioop --
 * Open Source Pure PHP Search Engine, Crawler, and Indexer
 *
 * Copyright (C) 2009 - 2026  Chris Pollett chris@pollett.org
 *
 * LICENSE:
 *
 * 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 <https://www.gnu.org/licenses/>.
 *
 * END LICENSE
 *
 * @author Chris Pollett chris@pollett.org
 * @license https://www.gnu.org/licenses/ GPL3
 * @link https://www.seekquarry.com/
 * @copyright 2009 - 2026
 * @filesource
 */
namespace seekquarry\yioop\views\elements;

use seekquarry\yioop as B;
use seekquarry\yioop\configs as C;
use seekquarry\yioop\library as L;
use seekquarry\yioop\library\CrawlConstants;

/**
 * Element responsible for draw the feeds a user is subscribed to
 *
 * @author Chris Pollett
 */
class UsermessagesElement extends Element implements CrawlConstants
{
    /**
     * Emojipicker helper
     * @var helper
     */
    public $emoji_helper;
    /**
     * constructor stores a reference to the view this element will reside on
     *
     * @param object $view   object this element will reside on
     */
    public function __construct($view = null)
    {
        parent::__construct($view);
        $this->emoji_helper = $this->view->helper('emojipicker');
    }
    /**
     * Renders the user-messages page (contact list + selected
     * conversation panel) into HTML.
     *
     * @param array $data field variables prepared by SocialComponent's
     *      userMessages activity for this view
     */
    public function render($data)
    {
        if (!empty($data['STATUS'])) {
            if (!empty($data['MESSAGES'])) {
                /* A push carries only the new messages, so it renders
                   the conversation list alone. The header, call
                   controls and message form the full page draws around
                   it are not sent: the browser keeps those from the
                   first load and only folds in the new messages. */
                $this->renderConversationMessages($data);
            }
            return;
        }
        $selection_class = empty($data['CONTACT_ID']) ? "no-selection" :
            "has-selection";
        ?>
        <div class="messages-container <?= $selection_class ?>"><?php
            ?>
            <div class="user-contacts">
            <div class="socialcontrols-wrap">
            <?php
            $this->view->element("socialcontrols")->render($data);
            ?></div>
            <?=$this->renderContacts($data); ?>
            </div>
            <div class="split-adjuster hide-narrow" id="split-adjuster">
            </div>
            <div class="contact-messages"><?php
            if (empty($data['CONTACT_ID'])) { ?>
                <div class="user-messages-warning">
                    <?= tl("usermessages_element_select_contact") ?>
                </div><?php
            } else {
                $this->renderContactMessages($data);
            } ?>
            </div>
        </div>
        <?php
    }
    /**
     * renderContacts draws the box a person types a name into and the
     * list of people they write to beneath it. Typing three letters
     * offers the names that begin with them, both people already written
     * to and anybody else on the site, and choosing one either opens
     * that conversation or asks that person to become a contact. The one
     * control beside the box filters the list already shown to what the
     * box holds.
     *
     * @param array $data field variables prepared by SocialComponent's
     *      userMessages activity, including CONTACTS and CONTACT_FILTER
     */
    public function renderContacts($data)
    {
        ?>
        <form class="center">
        <input type="hidden" name='<?=C\p('CSRF_TOKEN')?>'
            value='<?=$data[C\p('CSRF_TOKEN')]?>' >
        <input type="hidden" name='a' value='userMessages' >
        <input type="hidden" name='c' value='<?=$data['CONTROLLER'] ?>' >
        <div class="contact-actions suggestions-anchor" tabindex="1">
        <input type="search" name='contact_name'
            class="narrow-field" autocomplete="off"
            placeholder="<?=tl('usermessages_element_contact_filter')?>"
            id='contact-filter' value="<?=$data['CONTACT_FILTER'] ?? '' ?>"
            role="combobox" aria-expanded="false"
            aria-controls="contact-suggestions"
            aria-autocomplete="list" >
        <ul id="contact-suggestions" class="suggestions none"
            role="listbox"></ul><?php
        /* One control now, which filters the contacts already listed to
           what the box holds. Somebody wanting a person they do not yet
           write to picks them out of the names offered as they type
           rather than pressing a second control. */
        ?><button type='submit' name='arg' value="search"
            class="button-box" title="<?=
            tl('search_or_filter_says') ?>" aria-label="<?=
            tl('search_or_filter_says') ?>"><?=
            tl('usermessages_element_search') ?></button>
        </div>
        </form>
        <?php
        /* The script asks the api for names as somebody types, so it is
           given the address to ask and the two numbers saying when to
           ask: how many letters before asking at all, and how long to
           wait after a keystroke. A plain ampersand is written here
           rather than an escaped one, since this is script rather than
           markup and a browser sends what it is given. */
        $suggest_url = C\SHORT_BASE_URL . "?c=api&a=suggest" .
            "&kind=contact&" . C\p('CSRF_TOKEN') . "=" .
            $data[C\p('CSRF_TOKEN')];
        ?>
        <script>
        window.suggest_url = "<?= $suggest_url ?>";
        window.suggest_least = <?= C\MIN_SUGGEST_LENGTH ?>;
        window.suggest_wait = <?= C\SUGGEST_WAIT ?>;
        window.suggest_known_says =
            "<?= tl('usermessages_element_a_contact') ?>";
        window.suggest_new_says =
            "<?= tl('usermessages_element_not_yet_a_contact') ?>";
        </script>
        <?php
        $token_string = C\p('CSRF_TOKEN') . "=". $data[C\p('CSRF_TOKEN')];
        foreach ($data['CONTACTS'] as $contact_id => $contact_info) {
            $selected = ($contact_id == $data['CONTACT_ID']) ?
                " selected-contact " : "";
            $presence = $contact_info['PRESENCE'] ?? "";
            /* A contact already selected has the call controls in the
               conversation beside the list, so the row does not repeat
               them; only an unselected contact needs a way to take the
               call from the row. */
            $is_selected = !empty($data['CONTACT_ID']) &&
                $data['CONTACT_ID'] == $contact_id;
            $ringing = !empty($contact_info['RINGING']);
            $presence_class = ($presence === "") ? "" :
                " contact-seen-" . $presence . " ";
            $presence_words = match ($presence) {
                'here' => tl('usermessages_element_seen_here'),
                'lately' => tl('usermessages_element_seen_lately'),
                'a-while' => tl('usermessages_element_seen_a_while'),
                'long' => tl('usermessages_element_seen_long'),
                default => tl('usermessages_element_signed_out')
            };
            ?>
            <div class="contact-detail <?=$selected?>" tabindex="0"
                onclick="javascript:openContactRow(event, '<?=
                    $contact_id ?>')"><?php
            if ($contact_info['NUM_UNREAD_MESSAGES'] > 0) {?>
                <div class='unread-messages'><?=
                    $contact_info['NUM_UNREAD_MESSAGES'] ?></div><?php
            }?>
            <div class="contact-detail-inner">
            <img class="message-icon <?=$presence_class?>" src="<?=
                $contact_info["ICON_URL"]; ?>"
                title="<?= $presence_words ?>"
                alt="<?=tl('usermessages_element_user_icon') . ", " .
                    $presence_words; ?>" >
            <span class="contact-detail-line"><a id="contact-id-<?=$contact_id
                ?>" href="<?=$this->formatHref(B\feedsUrl('user_messages',
                $contact_id, true, $data['CONTROLLER']) .
                $token_string)?>" class="<?=
                ($ringing && !$is_selected) ? "contact-ringing" : "" ?><?=
                $is_selected ? " contact-selected" : "" ?>"><?=
                    $contact_info["USER_NAME"]; ?></a><?php
            /* The words and the two buttons are drawn for every
               contact and hidden until that contact calls, since the
               screen is built once and a call may start while it is
               open. The watcher shows them by taking the hidden class
               off, so a call arriving needs no markup built in the
               browser and the buttons keep the place the page gives
               them. */
            $hidden = ($ringing && !$is_selected) ? "" :
                " hidden-until-ringing";
            ?>
                <span class="contact-ringing-said<?= $hidden ?>"
                    id="ringing-said-<?= $contact_id ?>"><?=
                    tl('usermessages_element_calling_you',
                    $contact_info["USER_NAME"]) ?></span>
                <span class="contact-ringing-answer<?= $hidden ?>"
                    id="ringing-answer-<?= $contact_id ?>"
                    onclick="event.stopPropagation()">
                <?= $this->view->helper("iconlink")->renderButton(
                    ["answer-call-$contact_id", $this->formatHref(
                    B\feedsUrl('user_messages', $contact_id, true,
                    $data['CONTROLLER']) . $token_string) .
                    '&amp;answer_call=' . $contact_id], 'video_call',
                    div_classes: 'contact-answer-call');?>
                <?= $this->view->helper("iconlink")->renderButton(
                    ["decline-call-$contact_id", $this->formatHref(
                    B\feedsUrl('user_messages', $contact_id, true,
                    $data['CONTROLLER']) . $token_string) .
                    '&amp;decline_call=' . $contact_id], 'video_call_end',
                    div_classes: 'contact-decline-call');?>
                </span></span><?php
            ?>
            </div>
            <div class="contact-detail-options"
                onclick="event.stopPropagation()"><?php
            /* The row carries what can be done to the conversation
               behind it. The three marks are drawn here rather than
               written as a letter, since a letter for them is missing
               from some of the faces a browser has to hand. */
            $conversation_url = B\feedsUrl('user_messages',
                $contact_id, true, $data['CONTROLLER']) .
                $token_string . "&amp;contact_id=$contact_id&amp;arg=";
            ?>
            <div class="dropdown-container contact-options">
            <ul class="link-dropdown">
            <li tabindex="0"><a class="dropdown-selector contact-options-dots"
                href="javascript:void(0);" title="<?=
                tl('usermessages_element_conversation_options') ?>"
                ><span></span><span></span><span></span></a>
            <ul>
            <li><a href="<?= $this->formatHref($conversation_url .
                "deleteconversation") ?>" onclick="return confirm('<?=
                tl('usermessages_element_delete_conversation') ?>');"><?=
                tl('usermessages_element_delete_conversation_option')
                ?></a></li>
            <li><a href="<?= $this->formatHref($conversation_url .
                "removecontact") ?>" onclick="return confirm('<?=
                tl('usermessages_element_remove_contact') ?>');"><?=
                tl('usermessages_element_remove_contact_option')
                ?></a></li>
            </ul>
            </li>
            </ul>
            </div>
            </div>
            </div>
            <?php
        }
        if (!empty($data['CONTACT_REQUESTS'])) {
            $request_options = [
                "addcontact" => tl('usermessages_element_accept'),
                "blockcontact" => tl('usermessages_element_block'),
                "ignorecontact" => tl('usermessages_element_ignore'),
            ];
            foreach ($data['CONTACT_REQUESTS'] as $contact_id => $contact_info){
                $contact_request_url = B\feedsUrl('user_messages',
                    $contact_id, true, $data['CONTROLLER']) .
                    $token_string . "&amp;contact_id=$contact_id&amp;arg=";
                ?>
                <div class="contact-detail">
                <div class="contact-detail-inner">
                <img class="message-icon" src="<?=
                    $contact_info["ICON_URL"]; ?>"
                    alt="<?=tl('usermessages_element_user_icon'); ?>" >
                <span><?= $contact_info["USER_NAME"]; ?></span>
                </div>
                </div>
                <div class="contact-detail-request float-opposite">
                <?php
                $this->view->helper("options")->renderLinkDropDown(
                    "request-options$contact_id", $request_options,
                    '', $contact_request_url,
                    false, "",
                    tl('usermessages_element_request'));
                ?>
                </div>
                <?php
            }
        }
        if (empty($data['CONTACTS']) && empty($data['CONTACT_REQUESTS'])) { ?>
            <div class="user-messages-warning">
                <?= tl("usermessages_element_no_contacts") ?>
            </div>
            <?php
        }
    }
    /**
     * Renders the message-thread panel for the currently selected
     * contact into HTML.
     *
     * @param array $data field variables prepared by SocialComponent's
     *      userMessages activity, including MESSAGES and ADMIN
     */
    public function renderContactMessages($data)
    {
        $logged_in = !empty($data["ADMIN"]);
        $token_string = ($logged_in) ? C\p('CSRF_TOKEN') . "=" .
            $data[C\p('CSRF_TOKEN')]
            : "";
        $messages_url = B\controllerUrl("user_messages", true) .
            $token_string;
        /* A contact id can outlive the contact, for instance when one is
           removed while its conversation is open or reached from an old
           link. Only a contact still in the list gets the conversation
           panel and the box to write in; a stale one falls back to the
           same prompt shown when nothing is picked, so no writing box is
           left over a contact that is no longer there. */
        $is_current_contact = !empty($data['CONTACT_ID']) &&
            isset($data['CONTACTS'][$data['CONTACT_ID']]);
        if (!$is_current_contact) { ?>
            <div class="user-messages-warning">
                <?= tl("usermessages_element_select_contact") ?>
            </div><?php
            return;
        }
        if (empty($data['MESSAGES'])) { ?>
            <div class="user-messages-warning">
                <?= tl("usermessages_element_no_messages") ?>
            </div><?php
        } else {
            ?>
            <div id="conversation-header"
                class="selected-conversation-header"><?php
            if (!empty($data['MOBILE'])) {
                $arrow = ($data['LOCALE_DIR'] == 'rtl') ? "&gt;" : "&lt;";
                ?>
                <a class="float-same contacts-arrow" href="<?=
                    $messages_url?>"><?=$arrow ?></a>
                <?php
            }
            $now = time();
            if (!empty($data['CALL_ENABLED'])) {
                ?><audio id="phone-sound"><source
                        src="<?= C\SHORT_BASE_URL . "resources/" .
                        "freesound_org_audacitier_phone_ringing_5.mp3" ?>"
                        type="audio/mpeg">
                </audio>
                <div class="call-controls float-opposite">
                <?= $this->view->helper("iconlink")->renderButton(
                    ['video-end',
                    'javascript:setCallState("video-end")'], 'video_call_end',
                    div_classes: 'video-call-end  float-opposite none');?>
                <?php
                /* Where no relay answered, the call control says so and
                   does not place a call: a call between two home
                   networks needs a relay to carry it, and one placed
                   without one rings and then fails. */
                $relay_found = !empty($data['CALL_RELAY_FOUND']);
                $call_press = $relay_found ?
                    'javascript:setCallState("video")' :
                    'javascript:sayCallingIsDown()';
                $call_classes = 'video-call  float-opposite' .
                    ($relay_found ? '' : ' no-relay');
                ?>
                <?= $this->view->helper("iconlink")->renderButton(
                    ['video-start', $call_press], 'video_call',
                    div_classes: $call_classes);?><?php
                if (!$relay_found) { ?>
                <span class="no-relay-said"><?=
                    tl('usermessages_element_no_relay') ?></span><?php
                } ?>
                </div><?php
            }?>
            <h2>
            <img class="message-icon" src="<?=
                $data["CONTACT_ICON_URL"]; ?>"
                alt="<?=tl('usermessages_element_user_icon'); ?>" >
                <span><?= $data['CONTACT_NAME'] ?></span>
            </h2>
            </div>
            <div id="av-call"></div>
            <div id="call-trouble" class="call-trouble none"></div>
            <?php $this->renderConversationMessages($data); ?>
        <?php
        }
        $this->drawNewMessageForm($data);
    }
    /**
     * Renders just the conversation list: the scrolling box and one
     * entry per message, with nothing of the page around it. The full
     * page draws this inside its header and message form; a push sends
     * this alone, since the browser already holds the rest and only
     * needs the new messages to fold in.
     *
     * @param array $data field variables with MESSAGES, USERNAME,
     *      CONTACT_ID and the locale flags the message layout reads
     */
    public function renderConversationMessages($data)
    {
        $logged_in = !empty($data["ADMIN"]);
        $token_string = ($logged_in) ? C\p('CSRF_TOKEN') . "=" .
            $data[C\p('CSRF_TOKEN')]
            : "";
        $now = time();
        ?>
            <div id="conversation" class="conversation" data-time="<?=
                $now?>" data-contact-id="<?= $data['CONTACT_ID'] ?? '' ?>"
                data-loaded-count="<?= count($data['MESSAGES']) ?>"
                data-oldest-timestamp="<?= isset($data['MESSAGES']) &&
                count($data['MESSAGES']) > 0 ?
                $data['MESSAGES'][count($data['MESSAGES']) - 1]['PUBDATE'] :
                $now ?>"
                onscroll="handleConversationScroll()" ><?php
            $num_messages = count($data['MESSAGES']);
            for ($i = 0; $i < $num_messages; $i++) {
                $message_info = $data['MESSAGES'][$i];
                $next_message_info = $data['MESSAGES'][$i + 1] ??
                    $message_info;
                $next_user_name = $next_message_info['USER_NAME'];
                if (empty($message_info[self::DESCRIPTION])) {
                    continue;
                }
                $pub_date = $message_info['PUBDATE'];
                $next_pub_date = $next_message_info['PUBDATE'];
                $pub_date_diff = abs($next_pub_date - $pub_date);
                $pub_date_age = $now - $pub_date;
                $user_change = $message_info['USER_NAME'] != $next_user_name;
                $pub_data_change = $user_change ||
                    ($pub_date_age < C\ONE_HOUR &&
                    $pub_date_diff > 5 * C\ONE_MINUTE) ||
                    ($pub_date_age < C\ONE_DAY &&
                    $pub_date_diff > C\ONE_HOUR) ||
                    ($pub_date_diff > C\ONE_DAY);
                $pub_date_string =
                    $this->view->helper("feeds")->getPubdateString(
                    $now, $pub_date, false);
                $color = "message-theirs";
                $align = "align-same";
                $is_user = false;
                $user_string = "other";
                $top_corner = " top-corner ";
                if ($data['USERNAME'] == $message_info['USER_NAME']) {
                    $color = "message-mine";
                    $align = "align-opposite";
                    $is_user = true;
                    $user_string = "user";
                    $top_corner =  "";
                }
                $comma = "";
                ?>
                <div class="<?=$align ?>" data-message-id="<?=
                    $message_info['ID'] ?? "" ?>">
                <div class="message-detail-outer">
                <div class='message-<?=$user_string?>-time' >
                <?php
                if (!$is_user && ($pub_data_change || $user_change)) {
                    e('<img class="message-icon float-same" src="'.
                        $message_info['USER_ICON'].'" >&nbsp;');
                    $comma = ", ";
                    e($message_info['USER_NAME']);
                }
                if ($pub_data_change) {
                    e($comma . $pub_date_string);
                }?>
                </div>
                <div class="message-detail <?=$color . $top_corner?>"><?php
                /* Any message in this person's own half of the
                   conversation can be taken off it, whoever wrote it.
                   Their copy goes; the other person keeps theirs. The
                   corner mark asks first, since a message once gone
                   cannot be had again. */
                if (!empty($message_info['ID'])) { ?>
                    <a class="message-delete" href="<?=
                        $this->formatHref(B\feedsUrl('user_messages',
                        $data['CONTACT_ID'], true, $data['CONTROLLER']) .
                        "arg=deletemessage&amp;message_id=" .
                        $message_info['ID'] . "&amp;" . $token_string)
                        ?>" onclick="return confirm('<?=
                        tl('usermessages_element_delete_message')
                        ?>');" title="<?=
                        tl('usermessages_element_delete_message') ?>"
                        >&#10005;</a>
                <?php } ?><?=
                $message_info[self::DESCRIPTION];
                ?>
                </div>
                </div>
                </div>
                <?php
            }?>
        </div><?php
    }
    /**
     * Used to draw the new message form to add a message to an
     * existing conversation
     *
     * @param array $data containing other field needed to draw the form
     */
    private function drawNewMessageForm($data)
    {
        /* An upward arrow reads as sending rather than as going onward. */
        $arrow = "↑";
        ?>
        <div id="new-message-container">
        <form  id="newmessageForm" method="post" action="<?=C\SHORT_BASE_URL?>"
            enctype='multipart/form-data' onsubmit="parseEmojiShortcuts()">
        <input type="hidden" name="c" value="<?=$data['CONTROLLER'] ?>" >
        <input type="hidden" name="a" value="userMessages" >
        <input type="hidden" name="arg" value="newmessage" >
        <input type="hidden" name="contact_id" value="<?=
            $data['CONTACT_ID']; ?>" >
        <input type="hidden" name="<?= C\p('CSRF_TOKEN') ?>"
            value="<?= $data[C\p('CSRF_TOKEN')] ?>" >
        <input type="file" id="file-new-message"
            name="file_new_message" class="none" multiple="multiple" >
        <a id="attach-new-file"
            href="javascript:elt('file-new-message').click()"
            >+</a>
        <span id="user-input-container">
            <button id="emoji-picker-button" type="button"
                style="filter: grayscale(100%);"
                onclick="toggleEmojiPicker()">🙂</button>
            <div class="emoji-picker-container" id="emoji-picker-container">
                <?=$this->emoji_helper->render();?>
            </div>
            <textarea id="new-message" name="description" rows="1"
                placeholder="<?=tl('usermessages_element_new_message') ?>"
                ></textarea>
            <?php
            /* Yioop makes a recording over into the one kind every
               browser plays with its own audio library, so recording is
               offered whether or not an outside program is installed. */
            { ?>
            <button type="button" id="audio-record-btn" class="audio-control">
            <span id="mic-icon">🎤</span>
            </button>
            <div id="audio-controls" class="none">
                <button
                    type="button"
                    id="play-audio-btn"
                    class="audio-control">▶️</button>
                <button
                    type="button"
                    id="transcribe-audio-btn"
                    class="audio-control"
                    title="<?=tl('usermessages_element_transcribe')
                    ?>">📝</button>
                <button
                    type="button"
                    id="delete-audio-btn"
                    class="audio-control">🗑️</button>
            </div>
            <?php } ?>
            <button type="submit" id="send-message" class="none"
                ><?=$arrow ?></button>
        </span>
        </form>
        </div>
        <?php
        /* The upload handling this form uses says things to the person in
           their own language, and reads them from a table the file-upload
           helper writes. Without that table every upload raised an error
           partway through, which stopped whatever came after it. */
        $this->view->helper("fileupload")->setupFileUploadParams();
        ?>
        <script>
        if (typeof yioop_post_scripts !== 'object' ) {
            yioop_post_scripts = {};
        }
        yioop_post_scripts['new-message'] = function() {
            initializeFileHandler('new-message', "file-new-message",
              <?= min(L\metricToInt(ini_get('upload_max_filesize')),
              L\metricToInt(ini_get('post_max_size')))
              ?>, "textarea", null, true);
            initializeAudioRecorder();
            addTranscriptToggles(elt('conversation'));
            updateMessageSendControl();
            autoGrowMessageField();
            let message_field = elt('new-message');
            message_field.addEventListener('input',
                updateMessageSendControl);
            message_field.addEventListener('input', autoGrowMessageField);
            message_field.addEventListener('keydown', messageFieldKeydown);
        }
        const emojis_array = <?php
            $typed = [];
            foreach ($this->emoji_helper->emojiGroups() as $group_data) {
                $typed += $group_data["emojis"];
            }
            echo json_encode($typed); ?>;
        function insertEmoji(emoji)
        {
            let input = elt('new-message');
            input.value += emoji;
        }
        function toggleEmojiPicker()
        {
            let emoji_picker = elt('emoji-picker-container');
            let emoji_picker_button = elt('emoji-picker-button');
            if (emoji_picker.style.display === "none"
                || emoji_picker.style.display === "") {
                emoji_picker.style.display = "inline-block";
                emoji_picker_button.innerHTML = "🔽";
            } else {
                emoji_picker.style.display = "none";
                emoji_picker_button.innerHTML = "🙂";
            }
        }
        function toggleEmojiGroup(emoji_grp_tab, emoji_grp_name)
        {
            let emoji_grp_id = "emoji-group-" + emoji_grp_name;
            let emoji_groups =
                document.getElementsByClassName("emoji-group-container");
            for (let i=0;i<emoji_groups.length;i++) {
                if (emoji_groups[i].id === emoji_grp_id) {
                    emoji_groups[i].className += " active-emoji-group";
                } else {
                    emoji_groups[i].className = "emoji-group-container";
                }
            }
            let emoji_grps_tab =
                document.getElementsByClassName("emoji-group-tab");
            for (let i = 0; i < emoji_grps_tab.length; i++) {
                emoji_grps_tab[i].className = "emoji-group-tab";
            }
            emoji_grp_tab.className += " active-emoji-group-tab";
        }
        function updateMessageSendControl()
        {
            let message_field = elt('new-message');
            let send_button = elt('send-message');
            let record_button = elt('audio-record-btn');
            /* Where the site offers no recording there is no button to
               show or hide in its place, and the send button simply
               stands whenever there are words to send. */
            let has_words = (message_field.value.trim() !== "");
            send_button.style.display = has_words ? "inline-flex" : "none";
            if (record_button) {
                record_button.style.display = has_words ? "none" :
                    "inline-flex";
            }
        }
        function autoGrowMessageField()
        {
            let message_field = elt('new-message');
            message_field.style.height = "auto";
            message_field.style.height = message_field.scrollHeight + "px";
        }
        function messageFieldKeydown(event)
        {
            if (event.key === "Enter" && !event.shiftKey) {
                event.preventDefault();
                if (elt('new-message').value.trim() !== "") {
                    elt('newmessageForm').requestSubmit();
                }
            }
        }
        function parseEmojiShortcuts()
        {
            let input = document.getElementById("new-message");
            let raw_message = input.value;
            let possible_shortcuts = raw_message.matchAll(/\[([^\]]+)\]/g);
            let parse_message = "";
            let last_start_idx = 0;
            for (let shortcut of possible_shortcuts) {
                let text = shortcut[1];
                let emoji = emojis_array[text];
                if (emoji !== undefined) {
                    parse_message += raw_message.substring(last_start_idx,
                        shortcut["index"]) + emoji;
                } else {
                    parse_message += raw_message.substring(last_start_idx,
                        shortcut["index"]) + "[" + text + "]";
                }
                last_start_idx = shortcut["index"] + text.length + 2;
            }
            parse_message += raw_message.substring(last_start_idx);
            input.value = parse_message;
            return true;
        }
    </script></div><?php
    }
}
