BareRTC/web/templates/chat.html

668 lines
31 KiB
HTML

{{define "index"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/static/css/bulma.min.css">
<link rel="stylesheet" type="text/css" href="/static/css/bulma-prefers-dark.css">
<link rel="stylesheet" href="/static/fontawesome-free-6.1.2-web/css/all.css">
<link rel="stylesheet" type="text/css" href="/static/css/chat.css?{{.CacheHash}}">
<title>{{.Config.Title}}</title>
</head>
<body>
<div id="BareRTC-App">
<!-- Sign In modal -->
<div class="modal" :class="{'is-active': loginModal.visible}">
<div class="modal-background"></div>
<div class="modal-content">
<div class="card">
<header class="card-header has-background-info">
<p class="card-header-title has-text-light">Sign In</p>
</header>
<div class="card-content">
<form @submit.prevent="signIn()">
<div class="field">
<label class="label">Username</label>
<input class="input"
v-model="username"
placeholder="Username"
autocomplete="off"
autofocus
required>
</div>
<div class="field">
<div class="control">
<button class="button is-link">Submit</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Settings modal -->
<div class="modal" :class="{'is-active': settingsModal.visible}">
<div class="modal-background"></div>
<div class="modal-content">
<div class="card">
<header class="card-header has-background-info">
<p class="card-header-title has-text-light">Chat Settings</p>
</header>
<div class="card-content">
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">Video size</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<div class="select is-fullwidth">
<select v-model="webcam.videoScale">
<option v-for="s in webcam.videoScaleOptions"
v-bind:key="s[0]"
:value="s[0]">
[[ s[1] ]]
</option>
</select>
</div>
</div>
</div>
</div>
</div>
<h3 class="subtitle">Sounds</h3>
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">DM chat</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<div class="select is-fullwidth">
<select v-model="config.sounds.settings.DM" @change="setSoundPref('DM')">
<option v-for="s in config.sounds.available"
v-bind:key="s.name"
:value="s.name">
[[s.name]]
</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">Public chat</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<div class="select is-fullwidth">
<select v-model="config.sounds.settings.Chat" @change="setSoundPref('Chat')">
<option v-for="s in config.sounds.available"
v-bind:key="s.name"
:value="s.name">
[[s.name]]
</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">Room enter</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<div class="select is-fullwidth">
<select v-model="config.sounds.settings.Enter" @change="setSoundPref('Enter')">
<option v-for="s in config.sounds.available"
v-bind:key="s.name"
:value="s.name">
[[s.name]]
</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">Room leave</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<div class="select is-fullwidth">
<select v-model="config.sounds.settings.Leave" @change="setSoundPref('Leave')">
<option v-for="s in config.sounds.available"
v-bind:key="s.name"
:value="s.name">
[[s.name]]
</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
<footer class="card-footer">
<div class="card-footer-item">
<button type="button" class="button is-primary"
@click="hideSettings()">
Close
</button>
</div>
</footer>
</div>
</div>
</div>
<!-- NSFW Modal: before user activates their webcam -->
<div class="modal" :class="{'is-active': nsfwModalCast.visible}">
<div class="modal-background"></div>
<div class="modal-content">
<div class="card">
<header class="card-header has-background-info">
<p class="card-header-title has-text-light">Broadcast my webcam</p>
</header>
<div class="card-content">
<p class="block">
You can turn on your webcam and enable others in the room to connect to yours.
The controls to stop, <i class="fa fa-microphone-slash"></i> mute audio, and
<i class="fa fa-eye"></i> see who is watching will be at the top of the page.
</p>
<p class="block">
If your camera will be featuring "<abbr title="Not Safe For Work">NSFW</abbr>" or sexual content, please mark it as such by
clicking on the <i class="fa fa-fire has-text-danger"></i> button or checking the box below to start with it enabled.
</p>
<div class="field">
<label class="checkbox">
<input type="checkbox"
v-model="webcam.nsfw">
Check this box if your webcam will <em>definitely</em> be NSFW. 😈
</label>
</div>
<div class="field">
<div class="control has-text-centered">
<button type="button"
class="button is-link mr-4"
@click="startVideo(true); nsfwModalCast.visible=false">Start webcam</button>
<button type="button"
class="button"
@click="nsfwModalCast.visible=false">Cancel</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- NSFW Modal: before user views a NSFW camera the first time -->
<div class="modal" :class="{'is-active': nsfwModalView.visible}">
<div class="modal-background"></div>
<div class="modal-content">
<div class="card">
<header class="card-header has-background-info">
<p class="card-header-title has-text-light">This camera may contain NSFW content</p>
</header>
<div class="card-content">
<p class="block">
This camera has been marked as "<abbr title="Not Safe For Work">NSFW</abbr>" and may
contain displays of sexuality. If you do not want to see this, look for cameras with
a <span class="button is-small is-info is-outlined px-1"><i class="fa fa-video"></i></span>
blue icon rather than the <span class="button is-small is-danger is-outlined px-1"><i class="fa fa-video"></i></span>
red ones.
</p>
<div class="field">
<label class="checkbox">
<input type="checkbox"
v-model="nsfwModalView.dontShowAgain">
Don't show this message again
</label>
</div>
<div class="field">
<div class="control has-text-centered">
<button type="button"
class="button is-link mr-4"
@click="openVideo(nsfwModalView.user, true); nsfwModalView.visible=false">Open webcam</button>
<button type="button"
class="button"
@click="nsfwModalView.visible=false">Cancel</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="chat-container">
<!-- Top header panel -->
<header class="chat-header">
<div class="columns is-mobile">
<div class="column is-narrow pr-1">
<strong class="is-6">{{AsHTML .Config.Branding}}</strong>
</div>
<div class="column px-1">
<!-- Stop/Start video buttons -->
<button type="button"
v-if="webcam.active"
class="button is-small is-danger px-1"
@click="stopVideo()">
<i class="fa fa-stop mr-2"></i>
Stop
</button>
<button type="button"
v-else
class="button is-small is-success px-1"
@click="startVideo()"
:disabled="webcam.busy">
<i class="fa fa-video mr-2"></i>
Share webcam
</button>
<!-- Mute/Unmute my mic buttons (if streaming)-->
<button type="button"
v-if="webcam.active && !webcam.muted"
class="button is-small is-danger is-outlined ml-1 px-1"
@click="muteMe()">
<i class="fa fa-microphone mr-2"></i>
Mute
</button>
<button type="button"
v-if="webcam.active && webcam.muted"
class="button is-small is-danger ml-1 px-1"
@click="muteMe()">
<i class="fa fa-microphone mr-2"></i>
Unmute
</button>
<!-- Watchers button -->
<button type="button"
v-if="webcam.active"
class="button is-small is-info is-outlined ml-1 px-1"
@click="showViewers()">
<i class="fa fa-eye mr-2"></i>
[[Object.keys(webcam.watching).length]]
</button>
<!-- NSFW toggle button -->
<button type="button"
v-if="webcam.active && config.permitNSFW"
class="button is-small px-1 ml-1"
:class="{'is-outlined is-dark': !webcam.nsfw,
'is-danger': webcam.nsfw}"
@click.prevent="webcam.nsfw = !webcam.nsfw; sendMe()"
title="Toggle the NSFW setting for your camera broadcast">
<i class="fa fa-fire"></i>
</button>
</div>
<div class="column is-narrow pl-1">
<a href="/about" target="_blank" class="button is-small is-link px-2">
<i class="fa fa-info-circle"></i>
</a>
<button type="button"
class="button is-small is-light ml-1 px-2"
@click="showSettings()"
title="Chat Settings">
<i class="fa fa-gear"></i>
</button>
</div>
</div>
</header>
<!-- Left Column: Channels & DMs -->
<div class="left-column">
<div class="card grid-card">
<header class="card-header has-background-success-dark">
<div class="columns is-mobile card-header-title has-text-light">
<div class="column is-narrow mobile-only">
<button type="button"
class="button is-success"
@click="openChatPanel">
<i class="fa fa-arrow-left"></i>
</button>
</div>
<div class="column">Channels</div>
</div>
</header>
<div class="card-content">
<aside class="menu">
<p class="menu-label">
Chat Rooms
</p>
<ul class="menu-list">
<li v-for="c in activeChannels()"
v-bind:key="c.ID">
<a :href="'#'+c.ID"
@click.prevent="setChannel(c)"
:class="{'is-active': c.ID == channel}">
[[c.Name]]
<span v-if="hasUnread(c.ID)"
class="tag is-danger">
[[hasUnread(c.ID)]]
</span>
</a>
</li>
</ul>
<p class="menu-label">
Private Messages
</p>
<ul class="menu-list">
<li v-for="c in activeDMs()"
v-bind:key="c.channel">
<a :href="'#'+c.channel"
@click.prevent="setChannel(c.channel)"
:class="{'is-active': c.channel == channel}">
[[c.name]]
<span v-if="hasUnread(c.channel)"
class="tag is-danger">
[[hasUnread(c.channel)]]
</span>
</a>
</li>
</ul>
</aside>
</div>
</div>
</div>
<!-- Middle Column: Chat Room/History -->
<div class="chat-column">
<div class="card grid-card">
<header class="card-header has-background-link">
<div class="columns is-mobile card-header-title has-text-light">
<div class="column is-narrow mobile-only pr-0">
<!-- Responsive mobile button to pan to Left Column -->
<button type="button"
class="button is-success"
:class="{'is-small': isDM}"
@click="openChannelsPanel">
<i v-if="isDM" class="fa fa-arrow-left"></i>
<i v-else class="fa fa-message"></i>
</button>
</div>
<div class="column">
[[channelName]]
</div>
<div class="column is-narrow">
<!-- If a DM thread and the user has a profile URL -->
<button type="button"
v-if="this.channel.indexOf('@') === 0 && profileURLForUsername(this.channel)"
class="button is-small is-outlined is-light mr-1"
@click="openProfile({username: this.channel})">
<i class="fa fa-user"></i>
</button>
<!-- DMs: Leave convo button -->
<button type="button"
v-if="channel.indexOf('@') === 0"
class="float-right button is-small is-warning is-outlined"
@click="leaveDM()">
<i class="fa fa-trash"></i>
</button>
</div>
<!-- Who List button, only shown on public channel view -->
<div v-if="!isDM" class="column is-narrow mobile-only">
<!-- Responsive mobile button to pan to Right Column -->
<button type="button"
class="button is-success"
@click="openWhoPanel">
<i class="fa fa-user-group"></i>
</button>
</div>
</div>
</header>
<div class="video-feeds" :class="webcam.videoScale" v-show="webcam.active || Object.keys(WebRTC.streams).length > 0">
<!-- Video Feeds-->
<!-- My video -->
<video class="feed"
id="localVideo"
v-show="webcam.active"
autoplay muted>
</video>
<!-- Others' videos -->
<div class="feed" v-for="(stream, username) in WebRTC.streams" v-bind:key="username">
<video class="feed"
:id="'videofeed-'+username"
autoplay>
</video>
<div class="caption">
[[username]]
</div>
<div class="close">
<a href="#"
class="has-text-danger"
title="Close video"
@click="closeVideo(username, 'offerer')">
<i class="fa fa-close"></i>
</a>
</div>
<div class="controls">
<button type="button"
v-if="isMuted(username)"
class="button is-small is-danger is-outlined p-1"
title="Unmute this video"
@click="muteVideo(username)">
<i class="fa fa-volume-xmark"></i>
</button>
<button type="button"
v-else
class="button is-small is-danger is-outlined p-1"
title="Mute this video"
@click="muteVideo(username)">
<i class="fa fa-volume-high"></i>
</button>
</div>
</div>
</div>
<div class="card-content" id="chatHistory">
<!-- No history? -->
<div v-if="chatHistory.length === 0">
<em v-if="isDM">
Starting a direct message chat with [[channel]]. Type a message and say hello!
</em>
<em v-else>
There are no messages in this channel yet.
</em>
</div>
<div v-for="(msg, i) in chatHistory" v-bind:key="i" class="mb-2">
<div class="media mb-0">
<div class="media-left">
<a :href="profileURLForUsername(msg.username)" @click.prevent="openProfile({username: msg.username})"
:class="{'cursor-default': !profileURLForUsername(msg.username)}">
<figure class="image is-24x24">
<img v-if="msg.isChatServer"
src="/static/img/server.png">
<img v-else-if="msg.isChatClient"
src="/static/img/client.png">
<img v-else-if="avatarForUsername(msg.username)"
:src="avatarForUsername(msg.username)">
<img v-else src="/static/img/shy.png">
</figure>
</a>
</div>
<div class="media-content">
<div class="columns is-mobile">
<div class="column is-narrow">
<label class="label"
:class="{'has-text-success is-dark': msg.isChatServer,
'has-text-warning is-dark': msg.isAdmin,
'has-text-danger': msg.isChatClient}">
[[msg.username]]
</label>
</div>
<div class="column is-narrow">
<small class="has-text-grey" :title="msg.at">[[prettyDate(msg.at)]]</small>
</div>
<div class="column"
v-if="!(msg.isChatServer || msg.isChatClient || msg.username === username || isDM)">
<button type="button"
class="button is-grey is-outlined is-small px-2"
@click="openDMs({username: msg.username})">
<i class="fa fa-message"></i>
</button>
</div>
</div>
</div>
</div>
<div v-if="msg.action === 'presence'">
<em>[[msg.message]]</em>
</div>
<div v-else class="content">
<div v-html="msg.message"></div>
</div>
</div>
</div>
</div>
</div>
<div class="chat-footer">
<div class="card">
<div class="card-content p-2">
<div class="columns">
<div class="column">
<form @submit.prevent="sendMessage()">
<input type="text" class="input"
v-model="message"
placeholder="Message"
@keydown="sendTypingNotification()">
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Right Column: Who Is Online -->
<div class="right-column">
<div class="card grid-card">
<header class="card-header has-background-success-dark">
<div class="columns is-mobile card-header-title has-text-light">
<div class="column">Who Is Online</div>
<div class="column is-narrow mobile-only">
<button type="button"
class="button is-success"
@click="openChatPanel">
<i class="fa fa-arrow-left"></i>
</button>
</div>
</div>
</header>
<div class="card-content p-2">
<ul class="menu-list">
<li v-for="(u, i) in whoList" v-bind:key="i">
<div class="columns is-mobile">
<!-- Avatar URL if available -->
<div class="column is-narrow pr-0">
<img v-if="u.avatar" :src="avatarURL(u)"
width="24" height="24"
:alt="'Avatar image for ' + u.username">
<img v-else src="/static/img/shy.png"
width="24" height="24">
</div>
<div class="column pr-0"
:class="{'pl-1': u.avatar}">
<i class="fa fa-gavel has-text-warning-dark"
v-if="u.op"
title="Operator"></i>
[[ u.username ]]
</div>
<div class="column is-narrow pl-0">
<!-- Profile button -->
<button type="button"
v-if="u.profileURL"
class="button is-small px-2 py-1"
@click="openProfile(u)"
title="Open profile page">
<i class="fa fa-user"></i>
</button>
<!-- DM button -->
<button type="button"
class="button is-small px-2 py-1"
@click="openDMs(u)"
title="Start direct message thread"
:disabled="u.username === username">
<i class="fa fa-message"></i>
</button>
<!-- Video button -->
<button type="button" class="button is-small px-2 py-1"
:disabled="!u.videoActive"
:class="{
'is-danger is-outlined': u.videoActive && u.nsfw,
'is-info is-outlined': u.videoActive && !u.nsfw,
}"
title="Open video stream"
@click="openVideo(u)">
<i class="fa fa-video"></i>
</button>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div><!-- /app -->
<script type="text/javascript">
const PublicChannels = {{.Config.GetChannels}};
const WebsiteURL = "{{.Config.WebsiteURL}}";
const PermitNSFW = {{AsJS .Config.PermitNSFW}};
const UserJWTToken = {{.JWTTokenString}};
const UserJWTValid = {{if .JWTAuthOK}}true{{else}}false{{end}};
const UserJWTClaims = {{.JWTClaims.ToJSON}};
</script>
<script src="/static/js/vue-3.2.45.js"></script>
<script src="/static/js/sounds.js?{{.CacheHash}}"></script>
<script src="/static/js/BareRTC.js?{{.CacheHash}}"></script>
</body>
</html>
{{end}}