{{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"> <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> <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" @click="stopVideo()"> <i class="fa fa-camera mr-2"></i> Stop </button> <button type="button" v-else class="button is-small is-success" @click="startVideo()" :disabled="webcam.busy"> <i class="fa fa-camera mr-2"></i> Start </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> </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" v-show="webcam.active || Object.keys(WebRTC.streams).length > 0"> <!-- Video Feeds--> <!-- My video --> <video class="feed" v-show="webcam.active" id="localVideo" 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)"> <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"> </form> </div> <div class="column is-narrow"> </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" v-if="u.avatar"> <img :src="avatarURL(u)" width="24" height="24" :alt="'Avatar image for ' + u.username"> </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" 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 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}}