From 6183acc30a13140115e38d5efa926c9032b0b1c7 Mon Sep 17 00:00:00 2001 From: "Mr. Stallion" <mrstallion@nobody.nowhere.fauxdomain.ext> Date: Mon, 15 Jul 2019 11:59:16 -0500 Subject: [PATCH] Charater rating badge on private chats --- chat/AdView.vue | 5 +- chat/CharacterSearch.vue | 2 +- chat/ChatView.vue | 21 +++- chat/ConversationView.vue | 55 ++++++++- chat/PmPartnerAdder.vue | 40 +++++++ chat/RecentConversations.vue | 2 +- chat/StatusSwitcher.vue | 2 +- chat/UserChannelList.vue | 76 ++++++++++++ chat/UserList.vue | 2 +- chat/UserMenu.vue | 31 ++++- chat/UserView.vue | 219 +++++++++++++++++++++++++++++++++++ chat/bbcode.ts | 2 +- chat/event-bus.ts | 1 + chat/message_view.ts | 2 +- chat/user_view.ts | 58 ---------- learn/cache-manager.ts | 16 ++- learn/profile-cache.ts | 11 ++ readme.md | 9 +- 18 files changed, 475 insertions(+), 79 deletions(-) create mode 100644 chat/PmPartnerAdder.vue create mode 100644 chat/UserChannelList.vue create mode 100644 chat/UserView.vue delete mode 100644 chat/user_view.ts diff --git a/chat/AdView.vue b/chat/AdView.vue index ab4ff11..4b5db58 100644 --- a/chat/AdView.vue +++ b/chat/AdView.vue @@ -1,7 +1,7 @@ <template> <modal :buttons="false" ref="dialog" @open="onOpen" @close="onClose" style="width:98%" dialogClass="ads-dialog"> <template slot="title"> - Channel Ads for {{character.name}} + Channel Ads for <user :character="character">{{character.name}}</user> </template> <div class="row ad-viewer" ref="pageBody"> @@ -25,9 +25,10 @@ import { Character } from '../fchat/interfaces'; import { AdCachedPosting } from '../learn/ad-cache'; import core from './core'; import {formatTime} from './common'; +import UserView from './UserView.vue'; @Component({ - components: {modal: Modal} + components: {modal: Modal, user: UserView} }) export default class AdView extends CustomDialog { @Prop({required: true}) diff --git a/chat/CharacterSearch.vue b/chat/CharacterSearch.vue index 303785b..7b3f29b 100644 --- a/chat/CharacterSearch.vue +++ b/chat/CharacterSearch.vue @@ -36,7 +36,7 @@ import core from './core'; import {Character, Connection} from './interfaces'; import l from './localize'; - import UserView from './user_view'; + import UserView from './UserView.vue'; type Options = { kinks: Kink[], diff --git a/chat/ChatView.vue b/chat/ChatView.vue index 722d75c..ef041ab 100644 --- a/chat/ChatView.vue +++ b/chat/ChatView.vue @@ -25,7 +25,11 @@ {{conversations.consoleTab.name}} </a> </div> + + {{l('chat.pms')}} + <div @click.prevent="showAddPmPartner()" class="pm-add"><a href="#"><span class="fas fa-plus"></span></a></div> + <div class="list-group conversation-nav" ref="privateConversations"> <a v-for="conversation in conversations.privateConversations" href="#" @click.prevent="conversation.show()" :class="getClasses(conversation)" :data-character="conversation.character.name" data-touch="false" @@ -91,6 +95,7 @@ <user-menu ref="userMenu" :reportDialog="$refs['reportDialog']"></user-menu> <recent-conversations ref="recentDialog"></recent-conversations> <image-preview ref="imagePreview"></image-preview> + <add-pm-partner ref="addPmPartnerDialog"></add-pm-partner> </div> </template> @@ -107,12 +112,13 @@ import core from './core'; import {Character, Connection, Conversation} from './interfaces'; import l from './localize'; + import PmPartnerAdder from './PmPartnerAdder.vue'; import RecentConversations from './RecentConversations.vue'; import ReportDialog from './ReportDialog.vue'; import SettingsView from './SettingsView.vue'; import Sidebar from './Sidebar.vue'; import StatusSwitcher from './StatusSwitcher.vue'; - import {getStatusIcon} from './user_view'; + import {getStatusIcon} from './UserView.vue'; import UserList from './UserList.vue'; import UserMenu from './UserMenu.vue'; import ImagePreview from './ImagePreview.vue'; @@ -128,7 +134,8 @@ 'user-list': UserList, channels: ChannelList, 'status-switcher': StatusSwitcher, 'character-search': CharacterSearch, settings: SettingsView, conversation: ConversationView, 'report-dialog': ReportDialog, sidebar: Sidebar, 'user-menu': UserMenu, 'recent-conversations': RecentConversations, - 'image-preview': ImagePreview + 'image-preview': ImagePreview, + 'add-pm-partner': PmPartnerAdder } }) export default class ChatView extends Vue { @@ -294,6 +301,10 @@ (<StatusSwitcher>this.$refs['statusDialog']).show(); } + showAddPmPartner(): void { + (<PmPartnerAdder>this.$refs['addPmPartnerDialog']).show(); + } + userMenuHandle(e: MouseEvent | TouchEvent): void { (<UserMenu>this.$refs['userMenu']).handleEvent(e); } @@ -329,6 +340,12 @@ user-select: text; } + .pm-add { + font-size: 90%; + float: right; + margin-right: 5px; + } + .list-group.conversation-nav { margin-bottom: 10px; .list-group-item { diff --git a/chat/ConversationView.vue b/chat/ConversationView.vue index a945409..5846c82 100644 --- a/chat/ConversationView.vue +++ b/chat/ConversationView.vue @@ -4,7 +4,7 @@ <img :src="characterImage" style="height:60px;width:60px;margin-right:10px" v-if="settings.showAvatars"/> <div style="flex:1;position:relative;display:flex;flex-direction:column"> <div> - <user :character="conversation.character"></user> + <user :character="conversation.character" :match="true"></user> <a href="#" @click.prevent="showLogs()" class="btn"> <span class="fa fa-file-alt"></span> <span class="btn-text">{{l('logs.title')}}</span> </a> @@ -17,6 +17,10 @@ <a href="#" @click.prevent="showAds()" class="btn"> <span class="fa fa-ad"></span><span class="btn-text">Ads</span> </a> + + <a href="#" @click.prevent="showChannels()" class="btn"> + <span class="fa fa-tv"></span><span class="btn-text">Channels</span> + </a> </div> <div style="overflow:auto;max-height:50px"> {{l('status.' + conversation.character.status)}} @@ -140,7 +144,8 @@ <settings ref="settingsDialog" :conversation="conversation"></settings> <logs ref="logsDialog" :conversation="conversation"></logs> <manage-channel ref="manageDialog" v-if="isChannel(conversation)" :channel="conversation.channel"></manage-channel> - <ad-view ref="adViewer" v-if="isPrivate(conversation)" :character="conversation.character"></ad-view> + <ad-view ref="adViewer" v-if="isPrivate(conversation) && conversation.character" :character="conversation.character"></ad-view> + <channel-list ref="channelList" v-if="isPrivate(conversation)" :character="conversation.character"></channel-list> </div> </template> @@ -163,13 +168,14 @@ import MessageView from './message_view'; import ReportDialog from './ReportDialog.vue'; import {isCommand} from './slash_commands'; - import UserView from './user_view'; + import UserView from './UserView.vue'; + import UserChannelList from './UserChannelList.vue'; @Component({ components: { user: UserView, 'bbcode-editor': Editor, 'manage-channel': ManageChannel, settings: ConversationSettings, logs: Logs, 'message-view': MessageView, bbcode: BBCodeView, 'command-help': CommandHelp, - 'ad-view': AdView + 'ad-view': AdView, 'channel-list': UserChannelList } }) export default class ConversationView extends Vue { @@ -411,6 +417,10 @@ (<AdView>this.$refs['adViewer']).show(); } + showChannels(): void { + (<UserChannelList>this.$refs['channelList']).show(); + } + isAutopostingAds(): boolean { return this.conversation.adManager.isActive(); @@ -635,6 +645,43 @@ } } + + .user-view { + .match-found { + margin-left: 3px; + padding-left: 2px; + padding-right: 2px; + border-radius: 3px; + color: rgba(255, 255, 255, 0.8); + font-size: 75%; + padding-top: 0; + padding-bottom: 0; + text-align: center; + display: inline-block; + text-transform: uppercase; + + &.match { + background-color: rgb(0, 142, 0); + border: solid 1px rgb(0, 113, 0); + } + + &.weak-match { + background-color: rgb(0, 80, 0); + border: 1px solid rgb(0, 64, 0); + } + + &.weak-mismatch { + background-color: rgb(152, 134, 0); + border: 1px solid rgb(142, 126, 0); + } + + &.mismatch { + background-color: rgb(171, 0, 0); + border: 1px solid rgb(128, 0, 0); + } + } + } + .message { &.message-event { font-size: 85%; diff --git a/chat/PmPartnerAdder.vue b/chat/PmPartnerAdder.vue new file mode 100644 index 0000000..c6aacc7 --- /dev/null +++ b/chat/PmPartnerAdder.vue @@ -0,0 +1,40 @@ +<template> + <modal action="Open Conversation" ref="dialog" @submit="submit" style="width:98%" dialogClass="ads-dialog" buttonText="Open"> + <div> + <input type="text" id="name" v-model="name" placeholder="Name" /> + <div class="error" v-if="error">{{error}}</div> + </div> + + </modal> +</template> + + +<script lang="ts"> +import { Component } from '@f-list/vue-ts'; +import CustomDialog from '../components/custom_dialog'; +import Modal from '../components/Modal.vue'; +import core from './core'; + +@Component({ + components: {modal: Modal} +}) +export default class PmPartnerAdder extends CustomDialog { + name = ''; + error: string | null = null; + + submit(): void { + const c = core.characters.get(this.name); + + if (c) { + const conversation = core.conversations.getPrivate(c); + + conversation.show(); + + this.name = ''; + this.error = ''; + } else { + this.error = `Unknown character '${this.name}'`; + } + } +} +</script> diff --git a/chat/RecentConversations.vue b/chat/RecentConversations.vue index f6f2bb3..be98bc3 100644 --- a/chat/RecentConversations.vue +++ b/chat/RecentConversations.vue @@ -24,7 +24,7 @@ import core from './core'; import {Character, Conversation} from './interfaces'; import l from './localize'; - import UserView from './user_view'; + import UserView from './UserView.vue'; @Component({ components: {'user-view': UserView, 'channel-view': ChannelView, modal: Modal, tabs: Tabs} diff --git a/chat/StatusSwitcher.vue b/chat/StatusSwitcher.vue index 9bed284..d36d53e 100644 --- a/chat/StatusSwitcher.vue +++ b/chat/StatusSwitcher.vue @@ -30,7 +30,7 @@ import core from './core'; import {Character, userStatuses} from './interfaces'; import l from './localize'; - import {getStatusIcon} from './user_view'; + import {getStatusIcon} from './UserView.vue'; @Component({ components: {modal: Modal, editor: Editor, dropdown: Dropdown} diff --git a/chat/UserChannelList.vue b/chat/UserChannelList.vue new file mode 100644 index 0000000..d0c2ea6 --- /dev/null +++ b/chat/UserChannelList.vue @@ -0,0 +1,76 @@ +<template> + <modal :buttons="false" ref="dialog" style="width:98%" dialogClass=""> + <template slot="title"> + Channels for <user :character="character">{{character.name}}</user> + </template> + + <div class="user-channel-list" ref="pageBody"> + <template v-for="channel in channels"> + <h3><a href="#" @click.prevent="jumpToChannel(channel)">#{{channel.name}}</a></h3> + </template> + </div> + + </modal> +</template> + + +<script lang="ts"> + +import * as _ from 'lodash'; +import { Component, Hook, Prop, Watch } from '@f-list/vue-ts'; +import CustomDialog from '../components/custom_dialog'; +import Modal from '../components/Modal.vue'; +import { Character } from '../fchat/interfaces'; +import core from './core'; +import { Conversation } from './interfaces'; +import UserView from './UserView.vue'; +import ChannelConversation = Conversation.ChannelConversation; + +@Component({ + components: {modal: Modal, user: UserView} +}) +export default class UserChannelList extends CustomDialog { + @Prop({required: true}) + readonly character!: Character; + + channels: ChannelConversation[] = []; + + @Watch('character') + onNameUpdate(): void { + this.update(); + } + + + @Hook('mounted') + onMounted(): void { + this.update(); + } + + update(): void { + if (!this.character) { + this.channels = []; + return; + } + + this.channels = _.sortBy( + _.filter( + core.conversations.channelConversations, + (cc: ChannelConversation) => !!cc.channel.members[this.character.name] + ), + 'name' + ); + } + + jumpToChannel(channel: ChannelConversation): void { + channel.show(); + } + +} +</script> + + +<style lang="scss"> + .user-channel-list h3 { + font-size: 120%; + } +</style> \ No newline at end of file diff --git a/chat/UserList.vue b/chat/UserList.vue index f52b652..9ecd8cd 100644 --- a/chat/UserList.vue +++ b/chat/UserList.vue @@ -36,7 +36,7 @@ import {Channel, Character, Conversation} from './interfaces'; import l from './localize'; import Sidebar from './Sidebar.vue'; - import UserView from './user_view'; + import UserView from './UserView.vue'; @Component({ components: {user: UserView, sidebar: Sidebar, tabs: Tabs} diff --git a/chat/UserMenu.vue b/chat/UserMenu.vue index 8d2bd5d..8cd7d09 100644 --- a/chat/UserMenu.vue +++ b/chat/UserMenu.vue @@ -21,6 +21,9 @@ <span class="far fa-fw fa-sticky-note"></span>{{l('user.memo')}}</a> <a tabindex="-1" href="#" @click.prevent="setBookmarked()" class="list-group-item list-group-item-action"> <span class="far fa-fw fa-bookmark"></span>{{l('user.' + (character.isBookmarked ? 'unbookmark' : 'bookmark'))}}</a> + <a tabindex="-1" href="#" @click.prevent="showAdLogs()" class="list-group-item list-group-item-action" :class="{ disabled: !hasAdLogs()}"> + <span class="far fa-fw fa-ad"></span>Show ad log + </a> <a tabindex="-1" href="#" @click.prevent="setHidden()" class="list-group-item list-group-item-action" v-show="!isChatOp"> <span class="fa fa-fw fa-eye-slash"></span>{{l('user.' + (isHidden ? 'unhide' : 'hide'))}}</a> <a tabindex="-1" href="#" @click.prevent="report()" class="list-group-item list-group-item-action" style="border-top-width:1px"> @@ -36,6 +39,7 @@ <div style="float:right;text-align:right;">{{getByteLength(memo)}} / 1000</div> <textarea class="form-control" v-model="memo" :disabled="memoLoading" maxlength="1000"></textarea> </modal> + <ad-view ref="adViewDialog" :character="character" v-if="character"></ad-view> </div> </template> @@ -43,6 +47,7 @@ import {Component, Prop} from '@f-list/vue-ts'; import Vue from 'vue'; import Modal from '../components/Modal.vue'; + import AdView from './AdView.vue'; import {BBCodeView} from './bbcode'; import {characterImage, errorToString, getByteLength, profileLink} from './common'; import core from './core'; @@ -51,7 +56,7 @@ import ReportDialog from './ReportDialog.vue'; @Component({ - components: {bbcode: BBCodeView, modal: Modal} + components: {bbcode: BBCodeView, modal: Modal, 'ad-view': AdView} }) export default class UserMenu extends Vue { @Prop({required: true}) @@ -120,6 +125,30 @@ .catch((e: object) => alert(errorToString(e))); } + + showAdLogs(): void { + if (!this.hasAdLogs()) { + return; + } + + (<AdView>this.$refs['adViewDialog']).show(); + } + + + hasAdLogs(): boolean { + if (!this.character) { + return false; + } + + const cache = core.cache.adCache.get(this.character.name); + + if (!cache) { + return false; + } + + return (cache.count() > 0); + } + get isChannelMod(): boolean { if(this.channel === undefined) return false; if(core.characters.ownCharacter.isChatOp) return true; diff --git a/chat/UserView.vue b/chat/UserView.vue new file mode 100644 index 0000000..e21c29a --- /dev/null +++ b/chat/UserView.vue @@ -0,0 +1,219 @@ +<!-- Linebreaks inside this template will break BBCode views --> +<template><span :class="userClass" v-bind:bbcodeTag.prop="'user'" v-bind:character.prop="character" v-bind:channel.prop="channel"><span v-if="!!statusClass" :class="statusClass"></span><span v-if="!!rankIcon" :class="rankIcon"></span>{{character.name}}<span v-if="!!matchClass" :class="matchClass">{{getMatchScoreTitle(matchScore)}}</span></span></template> + + +<script lang="ts"> +import { Component, Hook, Prop } from '@f-list/vue-ts'; +import Vue from 'vue'; +import {Channel, Character} from '../fchat'; +import { Score, Scoring } from '../learn/matcher'; +import core from './core'; +import { EventBus } from './event-bus'; + + +export function getStatusIcon(status: Character.Status): string { + switch(status) { + case 'online': + return 'far fa-user'; + case 'looking': + return 'fa fa-eye'; + case 'dnd': + return 'fa fa-minus-circle'; + case 'offline': + return 'fa fa-ban'; + case 'away': + return 'far fa-circle'; + case 'busy': + return 'fa fa-cog'; + case 'idle': + return 'far fa-clock'; + case 'crown': + return 'fa fa-birthday-cake'; + } +} + + +@Component({ + components: { + + } +}) +export default class UserView extends Vue { + @Prop({required: true}) + readonly character!: Character; + + @Prop() + readonly channel?: Channel; + + @Prop() + readonly showStatus?: boolean = true; + + @Prop() + readonly bookmark?: boolean = false; + + @Prop() + readonly match?: boolean = false; + + userClass = ''; + + rankIcon: string | null = null; + statusClass: string | null = null; + matchClass: string | null = null; + matchScore: number | null = null; + + // tslint:disable-next-line no-any + scoreWatcher: ((event: any) => void) | null = null; + + @Hook('mounted') + onMounted(): void { + this.update(); + + if ((this.match) && (!this.matchClass)) { + if (this.scoreWatcher) { + EventBus.$off('character-score', this.scoreWatcher); + } + + // tslint:disable-next-line no-unsafe-any no-any + this.scoreWatcher = (event: any): void => { + // tslint:disable-next-line no-unsafe-any no-any + if ((event.character) && (event.character.name === this.character.name)) { + this.update(); + + if (this.scoreWatcher) { + EventBus.$off('character-score', this.scoreWatcher); + + delete this.scoreWatcher; + } + } + }; + + EventBus.$on( + 'character-score', + this.scoreWatcher + ); + } + } + + @Hook('beforeDestroy') + onBeforeDestroy(): void { + if (this.scoreWatcher) + EventBus.$off('character-score', this.scoreWatcher); + } + + + @Hook('beforeUpdate') + onBeforeUpdate(): void { + this.update(); + } + + + update(): void { + this.rankIcon = null; + this.statusClass = null; + this.matchClass = null; + + if (this.match) console.log('Update'); + + if(this.character.isChatOp) { + this.rankIcon = 'far fa-gem'; + } else if(this.channel !== undefined) { + this.rankIcon = (this.channel.owner === this.character.name) + ? 'fa fa-key' + : this.channel.opList.indexOf(this.character.name) !== -1 + ? (this.channel.id.substr(0, 4) === 'adh-' ? 'fa fa-shield-alt' : 'fa fa-star') + : null; + } + + if ((this.showStatus) || (this.character.status === 'crown')) + this.statusClass = `fa-fw ${getStatusIcon(this.character.status)}`; + + if (this.match) console.log('Update prematch'); + + if (this.match) { + const cache = core.cache.profileCache.getSync(this.character.name); + + if (cache) { + this.matchClass = `match-found ${Score.getClasses(cache.matchScore)}`; + this.matchScore = cache.matchScore; + } else { + core.cache.addProfile(this.character.name); + } + } + + if (this.match) console.log('Update post match'); + + const gender = this.character.gender !== undefined ? this.character.gender.toLowerCase() : 'none'; + + const isBookmark = (this.bookmark) && (core.connection.isOpen) && (core.state.settings.colorBookmarks) && + ((this.character.isFriend) || (this.character.isBookmarked)); + + + this.userClass = `user-view gender-${gender}${isBookmark ? ' user-bookmark' : ''}`; + + if (this.match) console.log('Update done'); + } + + + getMatchScoreTitle(score: number | null): string { + switch (score) { + case Scoring.MATCH: + return 'Great'; + + case Scoring.WEAK_MATCH: + return 'Good'; + + case Scoring.WEAK_MISMATCH: + return 'Maybe'; + + case Scoring.MISMATCH: + return 'No'; + } + + return ''; + } +} + +//tslint:disable-next-line:variable-name +/* const UserView = Vue.extend({ + functional: true, + render(this: void | Vue, createElement: CreateElement, context?: RenderContext): VNode { + const props = <{character: Character, channel?: Channel, showStatus?: true, bookmark?: false, match?: false}>( + context !== undefined ? context.props : (<Vue>this).$options.propsData); + + const character = props.character; + + let matchClasses: string | undefined; + + if (props.match) { + const cache = core.cache.profileCache.getSync(character.name); + + if (cache) { + matchClasses = Score.getClasses(cache.matchScore); + } + } + + let rankIcon; + if(character.isChatOp) rankIcon = 'far fa-gem'; + else if(props.channel !== undefined) + rankIcon = props.channel.owner === character.name ? 'fa fa-key' : props.channel.opList.indexOf(character.name) !== -1 ? + (props.channel.id.substr(0, 4) === 'adh-' ? 'fa fa-shield-alt' : 'fa fa-star') : ''; + else rankIcon = ''; + const children: (VNode | string)[] = [character.name]; + if(rankIcon !== '') children.unshift(createElement('span', {staticClass: rankIcon})); + if(props.showStatus !== undefined || character.status === 'crown') + children.unshift(createElement('span', {staticClass: `fa-fw ${getStatusIcon(character.status)}`})); + const gender = character.gender !== undefined ? character.gender.toLowerCase() : 'none'; + const isBookmark = props.bookmark !== false && core.connection.isOpen && core.state.settings.colorBookmarks && + (character.isFriend || character.isBookmarked); + return createElement('span', { + attrs: {class: `user-view gender-${gender}${isBookmark ? ' user-bookmark' : ''} ${matchClasses}`}, + domProps: {character, channel: props.channel, bbcodeTag: 'user'} + }, children); + } +}); + +export default UserView; +*/ + +</script> + diff --git a/chat/bbcode.ts b/chat/bbcode.ts index 19121bb..17e95f3 100644 --- a/chat/bbcode.ts +++ b/chat/bbcode.ts @@ -8,7 +8,7 @@ import {characterImage} from './common'; import core from './core'; import {Character} from './interfaces'; import {default as UrlView} from '../bbcode/UrlTagView.vue'; -import UserView from './user_view'; +import UserView from './UserView.vue'; export const BBCodeView: Component = { functional: true, diff --git a/chat/event-bus.ts b/chat/event-bus.ts index f9087f9..0eea540 100644 --- a/chat/event-bus.ts +++ b/chat/event-bus.ts @@ -9,6 +9,7 @@ import ChannelConversation = Conversation.ChannelConversation; * 'imagepreview-show': {url: string} * 'imagepreview-toggle-stickyness': {url: string} * 'character-data': {character: Character} + * 'character-score': {character: Character, score: number} * 'private-message': {message: Message} * 'channel-ad': {message: Message, channel: Conversation, profile: ComplexCharacter | undefined} * 'channel-message': {message: Message, channel: Conversation} diff --git a/chat/message_view.ts b/chat/message_view.ts index 6bfee6b..0d47550 100644 --- a/chat/message_view.ts +++ b/chat/message_view.ts @@ -6,7 +6,7 @@ import {BBCodeView} from './bbcode'; import {formatTime} from './common'; import core from './core'; import {Conversation} from './interfaces'; -import UserView from './user_view'; +import UserView from './UserView.vue'; const userPostfix: {[key: number]: string | undefined} = { [Conversation.Message.Type.Message]: ': ', diff --git a/chat/user_view.ts b/chat/user_view.ts deleted file mode 100644 index 75941db..0000000 --- a/chat/user_view.ts +++ /dev/null @@ -1,58 +0,0 @@ -// TODO convert this to single-file once Vue supports it for functional components. -//template: -//<span class="gender" :class="genderClass" @click="click" @contextmenu.prevent="showMenu" style="cursor:pointer;" ref="main"><span -//class="fa" :class="statusIcon"></span> <span class="fa" :class="rankIcon"></span>{{character.name}}</span> - -import Vue, {CreateElement, RenderContext, VNode} from 'vue'; -import {Channel, Character} from '../fchat'; -import core from './core'; - -export function getStatusIcon(status: Character.Status): string { - switch(status) { - case 'online': - return 'far fa-user'; - case 'looking': - return 'fa fa-eye'; - case 'dnd': - return 'fa fa-minus-circle'; - case 'offline': - return 'fa fa-ban'; - case 'away': - return 'far fa-circle'; - case 'busy': - return 'fa fa-cog'; - case 'idle': - return 'far fa-clock'; - case 'crown': - return 'fa fa-birthday-cake'; - } -} - -//tslint:disable-next-line:variable-name -const UserView = Vue.extend({ - functional: true, - render(this: void | Vue, createElement: CreateElement, context?: RenderContext): VNode { - const props = <{character: Character, channel?: Channel, showStatus?: true, bookmark?: false}>( - context !== undefined ? context.props : (<Vue>this).$options.propsData); - const character = props.character; - let rankIcon; - if(character.isChatOp) rankIcon = 'far fa-gem'; - else if(props.channel !== undefined) - rankIcon = props.channel.owner === character.name ? 'fa fa-key' : props.channel.opList.indexOf(character.name) !== -1 ? - (props.channel.id.substr(0, 4) === 'adh-' ? 'fa fa-shield-alt' : 'fa fa-star') : ''; - else rankIcon = ''; - const children: (VNode | string)[] = [character.name]; - if(rankIcon !== '') children.unshift(createElement('span', {staticClass: rankIcon})); - if(props.showStatus !== undefined || character.status === 'crown') - children.unshift(createElement('span', {staticClass: `fa-fw ${getStatusIcon(character.status)}`})); - const gender = character.gender !== undefined ? character.gender.toLowerCase() : 'none'; - const isBookmark = props.bookmark !== false && core.connection.isOpen && core.state.settings.colorBookmarks && - (character.isFriend || character.isBookmarked); - return createElement('span', { - attrs: {class: `user-view gender-${gender}${isBookmark ? ' user-bookmark' : ''}`}, - domProps: {character, channel: props.channel, bbcodeTag: 'user'} - }, children); - } -}); - -export default UserView; \ No newline at end of file diff --git a/learn/cache-manager.ts b/learn/cache-manager.ts index e5d3482..06fc6aa 100644 --- a/learn/cache-manager.ts +++ b/learn/cache-manager.ts @@ -3,7 +3,7 @@ import core from '../chat/core'; import { ChannelAdEvent, ChannelMessageEvent, CharacterDataEvent, EventBus } from '../chat/event-bus'; import { Conversation } from '../chat/interfaces'; import { methods } from '../site/character_page/data_store'; -import { Character } from '../site/character_page/interfaces'; +import { Character as ComplexCharacter } from '../site/character_page/interfaces'; import { Gender } from './matcher'; import { AdCache } from './ad-cache'; import { ChannelConversationCache } from './channel-conversation-cache'; @@ -74,7 +74,15 @@ export class CacheManager { } - updateAdScoringForProfile(c: Character, score: number): void { + updateAdScoringForProfile(c: ComplexCharacter, score: number): void { + EventBus.$emit( + 'character-score', + { + character: c, + score + } + ); + _.each( core.conversations.channelConversations, (ch: ChannelConversation) => { @@ -92,7 +100,7 @@ export class CacheManager { } - async addProfile(character: string | Character): Promise<void> { + async addProfile(character: string | ComplexCharacter): Promise<void> { if (typeof character === 'string') { // console.log('Learn discover', character); @@ -237,7 +245,7 @@ export class CacheManager { } - setProfile(c: Character): void { + setProfile(c: ComplexCharacter): void { this.characterProfiler = new CharacterProfiler(c, this.adCache); } } diff --git a/learn/profile-cache.ts b/learn/profile-cache.ts index 0c3681e..cbbcefb 100644 --- a/learn/profile-cache.ts +++ b/learn/profile-cache.ts @@ -30,6 +30,17 @@ export class ProfileCache extends AsyncCache<CharacterCacheRecord> { } + getSync(name: string): CharacterCacheRecord | null { + const key = AsyncCache.nameKey(name); + + if (key in this.cache) { + return this.cache[key]; + } + + return null; + } + + async get(name: string, skipStore: boolean = false): Promise<CharacterCacheRecord | null> { const key = AsyncCache.nameKey(name); diff --git a/readme.md b/readme.md index 29d66c7..77ab7d5 100644 --- a/readme.md +++ b/readme.md @@ -6,8 +6,8 @@ This repository contains a modified version of the mainline F-Chat 3.0 client. ## Key Differences * Ads view - * Highlight ads from characters most interesting to you / hide ads from characters clearly incompatible - * View recent ads from a character on any channel to which you subscribe + * Highlight ads from characters most interesting to you + * View a character's recent ads * Ad auto-posting * Manage channel's ad settings via "Tab Settings" * Automatically re-post ads every 11-18 minutes (randomized) for up to 180 minutes @@ -35,6 +35,11 @@ This repository contains a modified version of the mainline F-Chat 3.0 client. * Improvements to log browsing * Fix broken BBCode, such as `[big]` in character profiles * Which channels my chart partner is on? +* Reposition ad settings and toggle +* Cache image list, guestbook pages +* Bug: Invalid Ticket +* Bug: Posting on the same second +* Bug: Images tab count is off # F-List Exported