fchat-rising/chat/ConversationView.vue

445 lines
22 KiB
Vue
Raw Normal View History

2017-09-02 01:50:31 +00:00
<template>
<div style="height:100%;display:flex;flex-direction:column;flex:1;margin:0 5px;position:relative" id="conversation">
2019-01-03 17:38:17 +00:00
<div style="display:flex" v-if="isPrivate(conversation)" class="header">
2018-03-28 13:51:05 +00:00
<img :src="characterImage" style="height:60px;width:60px;margin-right:10px" v-if="settings.showAvatars"/>
2019-09-17 17:14:14 +00:00
<div style="flex:1;position:relative;display:flex;flex-direction:column;user-select:text">
2017-09-02 01:50:31 +00:00
<div>
<user :character="conversation.character"></user>
2019-01-03 17:38:17 +00:00
<a href="#" @click.prevent="showLogs()" class="btn">
2018-03-28 13:51:05 +00:00
<span class="fa fa-file-alt"></span> <span class="btn-text">{{l('logs.title')}}</span>
</a>
2019-01-03 17:38:17 +00:00
<a href="#" @click.prevent="showSettings()" class="btn">
2018-01-06 16:14:21 +00:00
<span class="fa fa-cog"></span> <span class="btn-text">{{l('conversationSettings.title')}}</span>
2017-09-02 01:50:31 +00:00
</a>
2019-01-03 17:38:17 +00:00
<a href="#" @click.prevent="reportDialog.report()" class="btn">
2018-03-28 13:51:05 +00:00
<span class="fa fa-exclamation-triangle"></span><span class="btn-text">{{l('chat.report')}}</span></a>
2017-09-02 01:50:31 +00:00
</div>
2019-09-17 17:14:14 +00:00
<div style="overflow:auto;overflow-x:hidden;max-height:50px;user-select:text">
2017-09-02 01:50:31 +00:00
{{l('status.' + conversation.character.status)}}
<span v-show="conversation.character.statusText"> <bbcode :text="conversation.character.statusText"></bbcode></span>
</div>
</div>
</div>
2019-01-03 17:38:17 +00:00
<div v-else-if="isChannel(conversation)" class="header">
2017-09-02 01:50:31 +00:00
<div style="display: flex; align-items: center;">
<div style="flex: 1;">
<span v-show="conversation.channel.id.substr(0, 4) !== 'adh-'" class="fa fa-star" :title="l('channel.official')"
style="margin-right:5px;vertical-align:sub"></span>
<h5 style="margin:0;display:inline;vertical-align:middle">{{conversation.name}}</h5>
2018-08-10 16:59:37 +00:00
<a href="#" @click.prevent="descriptionExpanded = !descriptionExpanded" class="btn">
2017-09-02 01:50:31 +00:00
<span class="fa" :class="{'fa-chevron-down': !descriptionExpanded, 'fa-chevron-up': descriptionExpanded}"></span>
2018-01-06 16:14:21 +00:00
<span class="btn-text">{{l('channel.description')}}</span>
2017-09-02 01:50:31 +00:00
</a>
2019-01-03 17:38:17 +00:00
<a href="#" @click.prevent="showManage()" v-show="isChannelMod" class="btn">
2018-07-20 01:12:26 +00:00
<span class="fa fa-edit"></span> <span class="btn-text">{{l('manageChannel.open')}}</span>
</a>
2019-01-03 17:38:17 +00:00
<a href="#" @click.prevent="showLogs()" class="btn">
2018-03-28 13:51:05 +00:00
<span class="fa fa-file-alt"></span> <span class="btn-text">{{l('logs.title')}}</span>
</a>
2019-01-03 17:38:17 +00:00
<a href="#" @click.prevent="showSettings()" class="btn">
2018-01-06 16:14:21 +00:00
<span class="fa fa-cog"></span> <span class="btn-text">{{l('conversationSettings.title')}}</span>
2017-09-02 01:50:31 +00:00
</a>
2019-01-03 17:38:17 +00:00
<a href="#" @click.prevent="reportDialog.report()" class="btn">
2018-03-28 13:51:05 +00:00
<span class="fa fa-exclamation-triangle"></span><span class="btn-text">{{l('chat.report')}}</span></a>
2017-09-02 01:50:31 +00:00
</div>
<ul class="nav nav-pills mode-switcher">
<li v-for="mode in modes" class="nav-item">
2019-01-03 17:38:17 +00:00
<a :class="isChannel(conversation) ? {active: conversation.mode == mode, disabled: conversation.channel.mode != 'both'} : undefined"
class="nav-link" href="#" @click.prevent="setMode(mode)">{{l('channel.mode.' + mode)}}</a>
2017-09-02 01:50:31 +00:00
</li>
</ul>
</div>
2017-10-16 23:58:57 +00:00
<div style="z-index:5;position:absolute;left:0;right:0;max-height:60%;overflow:auto"
2019-01-03 17:38:17 +00:00
:style="{display: descriptionExpanded ? 'block' : 'none'}" class="bg-solid-text border-bottom">
2017-09-02 01:50:31 +00:00
<bbcode :text="conversation.channel.description"></bbcode>
</div>
</div>
<div v-else class="header" style="display:flex;align-items:center">
<h4>{{l('chat.consoleTab')}}</h4>
2019-01-03 17:38:17 +00:00
<a href="#" @click.prevent="showLogs()" class="btn">
2018-03-28 13:51:05 +00:00
<span class="fa fa-file-alt"></span> <span class="btn-text">{{l('logs.title')}}</span>
</a>
2017-09-02 01:50:31 +00:00
</div>
<div class="search input-group" v-show="showSearch">
<div class="input-group-prepend">
<div class="input-group-text"><span class="fas fa-search"></span></div>
</div>
2019-01-03 17:38:17 +00:00
<input v-model="searchInput" @keydown.esc="hideSearch()" @keypress="lastSearchInput = Date.now()"
:placeholder="l('chat.search')" ref="searchField" class="form-control"/>
2018-04-11 19:17:58 +00:00
<a class="btn btn-sm btn-light" style="position:absolute;right:5px;top:50%;transform:translateY(-50%);line-height:0;z-index:10"
2018-04-16 23:14:13 +00:00
@click="hideSearch"><i class="fas fa-times"></i></a>
</div>
2019-01-03 17:38:17 +00:00
<div class="border-top messages" :class="isChannel(conversation) ? 'messages-' + conversation.mode : undefined" ref="messages"
@scroll="onMessagesScroll" style="flex:1;overflow:auto;margin-top:2px">
<template v-for="message in messages">
2019-01-03 17:38:17 +00:00
<message-view :message="message" :channel="isChannel(conversation) ? conversation.channel : undefined" :key="message.id"
2017-10-16 23:58:57 +00:00
:classes="message == conversation.lastRead ? 'last-read' : ''">
2017-09-02 01:50:31 +00:00
</message-view>
2019-01-03 17:38:17 +00:00
<span v-if="hasSFC(message) && message.sfc.action === 'report'" :key="'r' + message.id">
2017-10-16 23:58:57 +00:00
<a :href="'https://www.f-list.net/fchat/getLog.php?log=' + message.sfc.logid"
v-if="message.sfc.logid" target="_blank">{{l('events.report.viewLog')}}</a>
2017-10-16 23:58:57 +00:00
<span v-else>{{l('events.report.noLog')}}</span>
<span v-show="!message.sfc.confirmed">
2019-01-03 17:38:17 +00:00
| <a href="#" @click.prevent="message.sfc.action === 'report' && acceptReport(message.sfc)">{{l('events.report.confirm')}}</a>
2017-09-02 01:50:31 +00:00
</span>
2017-10-16 23:58:57 +00:00
</span>
2017-09-02 01:50:31 +00:00
</template>
</div>
2018-07-20 01:12:26 +00:00
<bbcode-editor v-model="conversation.enteredText" @keydown="onKeyDown" :extras="extraButtons" @input="keepScroll"
2019-01-03 17:38:17 +00:00
:classes="'form-control chat-text-box' + (isChannel(conversation) && conversation.isSendingAds ? ' ads-text-box' : '')"
:hasToolbar="settings.bbCodeBar" ref="textBox" style="position:relative;margin-top:5px"
:maxlength="isChannel(conversation) || isPrivate(conversation) ? conversation.maxMessageLength : undefined">
<span v-if="isPrivate(conversation) && conversation.typingStatus !== 'clear'" class="chat-info-text">
2017-09-02 01:50:31 +00:00
{{l('chat.typing.' + conversation.typingStatus, conversation.name)}}
</span>
2018-07-20 01:12:26 +00:00
<div v-show="conversation.infoText" class="chat-info-text">
2018-01-06 16:14:21 +00:00
<span class="fa fa-times" style="cursor:pointer" @click.stop="conversation.infoText = ''"></span>
2017-09-02 01:50:31 +00:00
<span style="flex:1;margin-left:5px">{{conversation.infoText}}</span>
</div>
2018-07-20 01:12:26 +00:00
<div v-show="conversation.errorText" class="chat-info-text">
2018-01-06 16:14:21 +00:00
<span class="fa fa-times" style="cursor:pointer" @click.stop="conversation.errorText = ''"></span>
2017-09-02 01:50:31 +00:00
<span class="redText" style="flex:1;margin-left:5px">{{conversation.errorText}}</span>
</div>
2018-07-20 01:12:26 +00:00
<div class="bbcode-editor-controls">
2019-01-03 17:38:17 +00:00
<div v-if="isChannel(conversation) || isPrivate(conversation)" style="margin-right:5px">
2018-07-20 01:12:26 +00:00
{{getByteLength(conversation.enteredText)}} / {{conversation.maxMessageLength}}
</div>
2019-01-03 17:38:17 +00:00
<ul class="nav nav-pills send-ads-switcher" v-if="isChannel(conversation)"
2018-07-20 01:12:26 +00:00
style="position:relative;z-index:10;margin-right:5px">
<li class="nav-item">
<a href="#" :class="{active: !conversation.isSendingAds, disabled: conversation.channel.mode != 'both'}"
class="nav-link" @click.prevent="setSendingAds(false)">{{l('channel.mode.chat')}}</a>
</li>
<li class="nav-item">
<a href="#" :class="{active: conversation.isSendingAds, disabled: conversation.channel.mode != 'both'}"
class="nav-link" @click.prevent="setSendingAds(true)">{{adsMode}}</a>
</li>
</ul>
<div class="btn btn-sm btn-primary" v-show="!settings.enterSend" @click="sendButton">{{l('chat.send')}}</div>
2017-09-02 01:50:31 +00:00
</div>
2018-07-20 01:12:26 +00:00
</bbcode-editor>
2018-03-28 13:51:05 +00:00
<command-help ref="helpDialog"></command-help>
2017-09-02 01:50:31 +00:00
<settings ref="settingsDialog" :conversation="conversation"></settings>
2018-03-28 13:51:05 +00:00
<logs ref="logsDialog" :conversation="conversation"></logs>
2019-01-03 17:38:17 +00:00
<manage-channel ref="manageDialog" v-if="isChannel(conversation)" :channel="conversation.channel"></manage-channel>
2017-09-02 01:50:31 +00:00
</div>
</template>
<script lang="ts">
2019-01-03 17:38:17 +00:00
import {Component, Hook, Prop, Watch} from '@f-list/vue-ts';
2017-09-02 01:50:31 +00:00
import Vue from 'vue';
import {EditorButton, EditorSelection} from '../bbcode/editor';
2019-09-17 17:14:14 +00:00
import {BBCodeView} from '../bbcode/view';
2018-07-20 01:12:26 +00:00
import {isShowing as anyDialogsShown} from '../components/Modal.vue';
import {Keys} from '../keys';
2019-09-17 17:14:14 +00:00
import {Editor} from './bbcode';
2017-09-02 01:50:31 +00:00
import CommandHelp from './CommandHelp.vue';
import {characterImage, getByteLength, getKey} from './common';
import ConversationSettings from './ConversationSettings.vue';
import core from './core';
2018-03-28 13:51:05 +00:00
import {Channel, channelModes, Character, Conversation, Settings} from './interfaces';
2017-09-02 01:50:31 +00:00
import l from './localize';
import Logs from './Logs.vue';
import ManageChannel from './ManageChannel.vue';
import MessageView from './message_view';
import ReportDialog from './ReportDialog.vue';
import {isCommand} from './slash_commands';
import UserView from './user_view';
@Component({
components: {
2018-03-28 13:51:05 +00:00
user: UserView, 'bbcode-editor': Editor, 'manage-channel': ManageChannel, settings: ConversationSettings,
2019-09-17 17:14:14 +00:00
logs: Logs, 'message-view': MessageView, bbcode: BBCodeView(core.bbCodeParser), 'command-help': CommandHelp
2017-09-02 01:50:31 +00:00
}
})
export default class ConversationView extends Vue {
@Prop({required: true})
readonly reportDialog!: ReportDialog;
2017-09-02 01:50:31 +00:00
modes = channelModes;
descriptionExpanded = false;
l = l;
extraButtons: EditorButton[] = [];
getByteLength = getByteLength;
tabOptions: string[] | undefined;
tabOptionsIndex!: number;
tabOptionSelection!: EditorSelection;
showSearch = false;
searchInput = '';
search = '';
lastSearchInput = 0;
2017-09-02 01:50:31 +00:00
messageCount = 0;
searchTimer = 0;
2018-07-20 01:12:26 +00:00
messageView!: HTMLElement;
resizeHandler!: EventListener;
keydownHandler!: EventListener;
2018-07-20 01:12:26 +00:00
keypressHandler!: EventListener;
scrolledDown = true;
scrolledUp = false;
2018-08-18 19:37:53 +00:00
ignoreScroll = false;
2018-08-10 16:59:37 +00:00
adCountdown = 0;
adsMode = l('channel.mode.ads');
2019-01-03 17:38:17 +00:00
isChannel = Conversation.isChannel;
isPrivate = Conversation.isPrivate;
2017-09-02 01:50:31 +00:00
2019-01-03 17:38:17 +00:00
@Hook('mounted')
2018-07-20 01:12:26 +00:00
mounted(): void {
2017-09-02 01:50:31 +00:00
this.extraButtons = [{
title: 'Help\n\nClick this button for a quick overview of slash commands.',
tag: '?',
icon: 'fa-question',
2018-03-28 13:51:05 +00:00
handler: () => (<CommandHelp>this.$refs['helpDialog']).show()
2017-09-02 01:50:31 +00:00
}];
2018-08-18 19:37:53 +00:00
window.addEventListener('resize', this.resizeHandler = () => this.keepScroll());
2018-07-20 01:12:26 +00:00
window.addEventListener('keypress', this.keypressHandler = () => {
2018-09-28 00:08:10 +00:00
const selection = document.getSelection();
if((selection === null || selection.isCollapsed) && !anyDialogsShown &&
(document.activeElement === document.body || document.activeElement === null || document.activeElement.tagName === 'A'))
2018-07-20 01:12:26 +00:00
(<Editor>this.$refs['textBox']).focus();
});
window.addEventListener('keydown', this.keydownHandler = ((e: KeyboardEvent) => {
if(getKey(e) === Keys.KeyF && (e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey) {
this.showSearch = true;
this.$nextTick(() => (<HTMLElement>this.$refs['searchField']).focus());
}
}) as EventListener);
this.searchTimer = window.setInterval(() => {
if(Date.now() - this.lastSearchInput > 500 && this.search !== this.searchInput)
this.search = this.searchInput;
}, 500);
2018-07-20 01:12:26 +00:00
this.messageView = <HTMLElement>this.$refs['messages'];
2018-08-10 16:59:37 +00:00
this.$watch('conversation.nextAd', (value: number) => {
const setAdCountdown = () => {
const diff = ((<Conversation.ChannelConversation>this.conversation).nextAd - Date.now()) / 1000;
if(diff <= 0) {
if(this.adCountdown !== 0) window.clearInterval(this.adCountdown);
this.adCountdown = 0;
this.adsMode = l('channel.mode.ads');
} else this.adsMode = l('channel.mode.ads.countdown', Math.floor(diff / 60), Math.floor(diff % 60));
};
2018-09-28 00:08:10 +00:00
if(Date.now() < value && this.adCountdown === 0)
this.adCountdown = window.setInterval(setAdCountdown, 1000);
setAdCountdown();
2018-08-10 16:59:37 +00:00
});
}
2019-01-03 17:38:17 +00:00
@Hook('destroyed')
destroyed(): void {
window.removeEventListener('resize', this.resizeHandler);
window.removeEventListener('keydown', this.keydownHandler);
2018-07-20 01:12:26 +00:00
window.removeEventListener('keypress', this.keypressHandler);
clearInterval(this.searchTimer);
2017-09-02 01:50:31 +00:00
}
2018-04-16 23:14:13 +00:00
hideSearch(): void {
this.showSearch = false;
this.searchInput = '';
}
2017-09-02 01:50:31 +00:00
get conversation(): Conversation {
return core.conversations.selectedConversation;
}
2019-01-03 17:38:17 +00:00
get messages(): ReadonlyArray<Conversation.Message | Conversation.SFCMessage> {
if(this.search === '') return this.conversation.messages;
const filter = new RegExp(this.search.replace(/[^\w]/gi, '\\$&'), 'i');
return this.conversation.messages.filter((x) => filter.test(x.text));
}
2018-08-18 19:37:53 +00:00
async sendButton(): Promise<void> {
return this.conversation.send();
}
2017-09-02 01:50:31 +00:00
@Watch('conversation')
conversationChanged(): void {
2018-07-20 01:12:26 +00:00
if(!anyDialogsShown) (<Editor>this.$refs['textBox']).focus();
2018-08-18 19:37:53 +00:00
this.$nextTick(() => setTimeout(() => this.messageView.scrollTop = this.messageView.scrollHeight));
2018-07-20 01:12:26 +00:00
this.scrolledDown = true;
2017-09-02 01:50:31 +00:00
}
@Watch('conversation.messages')
messageAdded(newValue: Conversation.Message[]): void {
2018-07-20 01:12:26 +00:00
this.keepScroll();
if(!this.scrolledDown && newValue.length === this.messageCount)
this.messageView.scrollTop -= (this.messageView.firstElementChild!).clientHeight;
2017-09-02 01:50:31 +00:00
this.messageCount = newValue.length;
}
2018-07-20 01:12:26 +00:00
keepScroll(): void {
2018-09-28 00:08:10 +00:00
if(this.scrolledDown) {
this.ignoreScroll = true;
2018-08-18 19:37:53 +00:00
this.$nextTick(() => setTimeout(() => {
this.ignoreScroll = true;
this.messageView.scrollTop = this.messageView.scrollHeight;
}, 0));
2018-09-28 00:08:10 +00:00
}
2017-09-02 01:50:31 +00:00
}
onMessagesScroll(): void {
2018-08-18 19:37:53 +00:00
if(this.ignoreScroll) {
this.ignoreScroll = false;
return;
}
2018-08-10 16:59:37 +00:00
if(this.messageView.scrollTop < 20) {
if(!this.scrolledUp) {
const firstMessage = this.messageView.firstElementChild;
2019-09-17 17:14:14 +00:00
if(this.conversation.loadMore() && firstMessage !== null) {
this.messageView.style.overflow = 'hidden';
this.$nextTick(() => {
this.messageView.scrollTop = (<HTMLElement>firstMessage).offsetTop;
this.messageView.style.overflow = 'auto';
});
}
2018-08-10 16:59:37 +00:00
}
2018-07-20 01:12:26 +00:00
this.scrolledUp = true;
} else this.scrolledUp = false;
this.scrolledDown = this.messageView.scrollTop + this.messageView.offsetHeight >= this.messageView.scrollHeight - 15;
2017-09-02 01:50:31 +00:00
}
@Watch('conversation.errorText')
@Watch('conversation.infoText')
textChanged(newValue: string, oldValue: string): void {
if(oldValue.length === 0 && newValue.length > 0) this.keepScroll();
}
@Watch('conversation.typingStatus')
typingStatusChanged(_: string, oldValue: string): void {
if(oldValue === 'clear') this.keepScroll();
}
2018-01-06 16:14:21 +00:00
async onKeyDown(e: KeyboardEvent): Promise<void> {
2017-09-02 01:50:31 +00:00
const editor = <Editor>this.$refs['textBox'];
if(getKey(e) === Keys.Tab) {
2018-04-11 19:17:58 +00:00
if(e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) return;
2017-09-02 01:50:31 +00:00
e.preventDefault();
if(this.conversation.enteredText.length === 0 || this.isConsoleTab) return;
if(this.tabOptions === undefined) {
const selection = editor.getSelection();
if(selection.text.length === 0) {
const match = /\b[\w]+$/.exec(editor.text.substring(0, selection.end));
if(match === null) return;
selection.start = match.index < 0 ? 0 : match.index;
selection.text = editor.text.substring(selection.start, selection.end);
if(selection.text.length === 0) return;
}
const search = new RegExp(`^${selection.text.replace(/[^\w]/gi, '\\$&')}`, 'i');
2017-09-02 01:50:31 +00:00
const c = (<Conversation.PrivateConversation>this.conversation);
let options: ReadonlyArray<{character: Character}>;
options = Conversation.isChannel(this.conversation) ? this.conversation.channel.sortedMembers :
[{character: c.character}, {character: core.characters.ownCharacter}];
this.tabOptions = options.filter((x) => search.test(x.character.name)).map((x) => x.character.name);
this.tabOptionsIndex = 0;
this.tabOptionSelection = selection;
}
if(this.tabOptions.length > 0) {
const selection = editor.getSelection();
if(selection.end !== this.tabOptionSelection.end) return;
if(this.tabOptionsIndex >= this.tabOptions.length) this.tabOptionsIndex = 0;
const name = this.tabOptions[this.tabOptionsIndex];
const userName = (isCommand(this.conversation.enteredText) ? name : `[user]${name}[/user]`);
this.tabOptionSelection.end = this.tabOptionSelection.start + userName.length;
this.conversation.enteredText = this.conversation.enteredText.substr(0, this.tabOptionSelection.start) + userName +
this.conversation.enteredText.substr(selection.end);
++this.tabOptionsIndex;
}
} else {
if(this.tabOptions !== undefined) this.tabOptions = undefined;
if(getKey(e) === Keys.ArrowUp && this.conversation.enteredText.length === 0
&& !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey)
2017-09-02 01:50:31 +00:00
this.conversation.loadLastSent();
else if(getKey(e) === Keys.Enter) {
2018-03-28 13:51:05 +00:00
if(e.shiftKey === this.settings.enterSend) return;
e.preventDefault();
2018-08-18 19:37:53 +00:00
await this.conversation.send();
}
2017-09-02 01:50:31 +00:00
}
}
setMode(mode: Channel.Mode): void {
const conv = (<Conversation.ChannelConversation>this.conversation);
if(conv.channel.mode === 'both') conv.mode = mode;
}
acceptReport(sfc: {callid: number}): void {
core.connection.send('SFC', {action: 'confirm', callid: sfc.callid});
}
setSendingAds(is: boolean): void {
const conv = (<Conversation.ChannelConversation>this.conversation);
if(conv.channel.mode === 'both') {
conv.isSendingAds = is;
(<Editor>this.$refs['textBox']).focus();
}
}
2019-01-03 17:38:17 +00:00
showLogs(): void {
(<Logs>this.$refs['logsDialog']).show();
}
showSettings(): void {
(<ConversationSettings>this.$refs['settingsDialog']).show();
}
showManage(): void {
(<ManageChannel>this.$refs['manageDialog']).show();
}
hasSFC(message: Conversation.Message): message is Conversation.SFCMessage {
return (<Partial<Conversation.SFCMessage>>message).sfc !== undefined;
}
2017-09-02 01:50:31 +00:00
get characterImage(): string {
return characterImage(this.conversation.name);
}
2018-03-28 13:51:05 +00:00
get settings(): Settings {
return core.state.settings;
2017-09-02 01:50:31 +00:00
}
get isConsoleTab(): boolean {
return this.conversation === core.conversations.consoleTab;
}
get isChannelMod(): boolean {
if(core.characters.ownCharacter.isChatOp) return true;
const conv = (<Conversation.ChannelConversation>this.conversation);
const member = conv.channel.members[core.connection.character];
return member !== undefined && member.rank > Channel.Rank.Member;
}
}
</script>
<style lang="scss">
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins/breakpoints";
2017-09-02 01:50:31 +00:00
#conversation {
.header {
2018-03-28 13:51:05 +00:00
@media (min-width: breakpoint-min(md)) {
2017-09-02 01:50:31 +00:00
margin-right: 32px;
}
2018-03-28 13:51:05 +00:00
.btn {
2017-09-02 01:50:31 +00:00
padding: 2px 5px;
}
}
.send-ads-switcher a {
padding: 3px 10px;
}
2018-03-28 13:51:05 +00:00
@media (max-width: breakpoint-max(sm)) {
2017-09-02 01:50:31 +00:00
.mode-switcher a {
padding: 5px 8px;
}
}
}
2018-07-20 01:12:26 +00:00
.chat-info-text {
2018-08-10 16:59:37 +00:00
display: flex;
align-items: center;
flex: 1 51%;
2018-07-20 01:12:26 +00:00
@media (max-width: breakpoint-max(xs)) {
flex-basis: 100%;
}
}
2017-09-02 01:50:31 +00:00
</style>