diff --git a/CHANGELOG.md b/CHANGELOG.md
index 06e58e1..e11314f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,8 @@
# Changelog
## Canary
-* More conifugrable settings for F-Chat Rising
+* More configurable settings for F-Chat Rising
+* Hover mouse over a character name to see a character preview
## 1.3.0
diff --git a/bbcode/UrlTagView.vue b/bbcode/UrlTagView.vue
index fa4465e..29f5d46 100644
--- a/bbcode/UrlTagView.vue
+++ b/bbcode/UrlTagView.vue
@@ -55,7 +55,6 @@
EventBus.$emit('imagepreview-show', {url: this.url});
}
-
toggleStickyness(): void {
EventBus.$emit('imagepreview-toggle-stickyness', {url: this.url});
}
diff --git a/chat/UserMenu.vue b/chat/UserMenu.vue
index d2821c2..be06699 100644
--- a/chat/UserMenu.vue
+++ b/chat/UserMenu.vue
@@ -10,9 +10,7 @@
-
+
{{l('user.profile')}}
@@ -59,12 +57,12 @@ import core from './core';
import { Channel, Character } from './interfaces';
import l from './localize';
import ReportDialog from './ReportDialog.vue';
-import { Matcher, MatchResultScores } from '../learn/matcher';
-import { TagId } from '../learn/matcher-types';
+import { Matcher, MatchReport } from '../learn/matcher';
import _ from 'lodash';
+import MatchTags from './preview/MatchTags.vue';
@Component({
- components: {bbcode: BBCodeView(core.bbCodeParser), modal: Modal, 'ad-view': CharacterAdView}
+ components: {'match-tags': MatchTags, bbcode: BBCodeView(core.bbCodeParser), modal: Modal, 'ad-view': CharacterAdView}
})
export default class UserMenu extends Vue {
@Prop({required: true})
@@ -80,7 +78,7 @@ import _ from 'lodash';
memo = '';
memoId = 0;
memoLoading = false;
- match: MatchResultScores | null = null;
+ match: MatchReport | null = null;
openConversation(jump: boolean): void {
const conversation = core.conversations.getPrivate(this.character!);
@@ -226,9 +224,6 @@ import _ from 'lodash';
this.showContextMenu = false;
}
- getTagDesc(key: any): any {
- return TagId[key].toString().replace(/([A-Z])/g, ' $1').trim();
- }
private async openMenu(touch: MouseEvent | Touch, character: Character, channel: Channel | undefined): Promise {
this.channel = channel;
@@ -246,7 +241,7 @@ import _ from 'lodash';
const match = Matcher.identifyBestMatchReport(myProfile.character, theirProfile.character.character);
if (_.keys(match.merged).length > 0) {
- this.match = match.merged;
+ this.match = match;
}
}
}
@@ -272,44 +267,6 @@ import _ from 'lodash';
border-top-width: 0;
z-index: -1;
}
-
- #userMenu {
- .menu-character-score {
- span {
- padding-left: 3px;
- padding-right: 3px;
- margin-bottom: 3px;
- margin-right: 3px;
- display: inline-block;
- border: 1px solid;
- border-radius: 3px;
-
- i {
- color: white;
- }
-
- &.match {
- background-color: var(--scoreMatchBg);
- border: solid 1px var(--scoreMatchFg);
- }
-
- &.weak-match {
- background-color: var(--scoreWeakMatchBg);
- border: 1px solid var(--scoreWeakMatchFg);
- }
-
- &.weak-mismatch {
- background-color: var(--scoreWeakMismatchBg);
- border: 1px solid var(--scoreWeakMismatchFg);
- }
-
- &.mismatch {
- background-color: var(--scoreMismatchBg);
- border: 1px solid var(--scoreMismatchFg);
- }
- }
- }
- }
diff --git a/chat/UserView.vue b/chat/UserView.vue
index 14aa25e..511b8fb 100644
--- a/chat/UserView.vue
+++ b/chat/UserView.vue
@@ -1,5 +1,5 @@
-{{character.name}}{{getMatchScoreTitle(matchScore)}}
+{{character.name}}{{getMatchScoreTitle(matchScore)}}
diff --git a/chat/preview/CharacterPreview.vue b/chat/preview/CharacterPreview.vue
new file mode 100644
index 0000000..6c693a9
--- /dev/null
+++ b/chat/preview/CharacterPreview.vue
@@ -0,0 +1,290 @@
+
+
+
+
+
![]()
+
+
+
+
{{ character.character.name }}
+
{{ getOnlineStatus() }}
+
+
+
+ {{age}}-years-old
+ {{sexualOrientation}}
+ {{gender}}
+ {{species}}
+
+
+
{{furryPref}}
+
{{subDomRole}}
+
+
+
+
+
+
Latest Ad {{formatTime(latestAd.datePosted)}}
+
+
+
+
+
+ Loading...
+
+
+
+
+
+
+
diff --git a/chat/preview/ImagePreview.vue b/chat/preview/ImagePreview.vue
index 125ac5c..fde29a1 100644
--- a/chat/preview/ImagePreview.vue
+++ b/chat/preview/ImagePreview.vue
@@ -21,15 +21,20 @@
id="image-preview-ext"
ref="imagePreviewExt"
class="image-preview-external"
- :style="externalPreviewStyle">
+ :style="previewStyles.ExternalImagePreviewHelper">
+
+
@@ -44,12 +49,17 @@
import {domain} from '../../bbcode/core';
import {ImageDomMutator} from './image-dom-mutator';
- import { ExternalImagePreviewHelper, LocalImagePreviewHelper } from './helper';
+ import {
+ ExternalImagePreviewHelper,
+ LocalImagePreviewHelper,
+ PreviewManager,
+ CharacterPreviewHelper, RenderStyle
+ } from './helper';
import {Point, WebviewTag, remote} from 'electron';
import Timer = NodeJS.Timer;
import IpcMessageEvent = Electron.IpcMessageEvent;
-
+ import CharacterPreview from './CharacterPreview.vue';
const screen = remote.screen;
@@ -63,15 +73,30 @@
httpStatusText: string;
}
-
- @Component
+ @Component({
+ components: {
+ 'character-preview': CharacterPreview
+ }
+ })
export default class ImagePreview extends Vue {
private readonly MinTimePreviewVisible = 100;
visible = false;
- externalPreviewHelper = new ExternalImagePreviewHelper(this);
- localPreviewHelper = new LocalImagePreviewHelper(this);
+ previewManager = new PreviewManager(
+ this,
+ [
+ new ExternalImagePreviewHelper(this),
+ new LocalImagePreviewHelper(this),
+ new CharacterPreviewHelper(this)
+ // new ChannelPreviewHelper(this)
+ ]
+ );
+
+ // externalPreviewHelper = new ExternalImagePreviewHelper(this);
+ // localPreviewHelper = new LocalImagePreviewHelper(this);
+ // externalPreviewStyle: Record = {};
+ // localPreviewStyle: Record = {};
url: string | null = null;
domain: string | undefined;
@@ -82,15 +107,11 @@
jsMutator = new ImageDomMutator(this.debug);
- externalPreviewStyle: Record = {};
- localPreviewStyle: Record = {};
-
state = 'hidden';
shouldShowSpinner = false;
shouldShowError = true;
-
private interval: Timer | null = null;
private exitInterval: Timer | null = null;
@@ -100,6 +121,9 @@
private shouldDismiss = false;
private visibleSince = 0;
+ previewStyles: Record = {};
+
+
@Hook('mounted')
onMounted(): void {
console.warn('Mounted ImagePreview');
@@ -299,43 +323,33 @@
reRenderStyles(): void {
- // tslint:disable-next-line:no-unsafe-any
- this.externalPreviewStyle = this.externalPreviewHelper.renderStyle();
- // tslint:disable-next-line:no-unsafe-any
- this.localPreviewStyle = this.localPreviewHelper.renderStyle();
-
- this.debugLog(
- 'ImagePreview: reRenderStyles', 'external',
- JSON.parse(JSON.stringify(this.externalPreviewStyle)),
- 'local', JSON.parse(JSON.stringify(this.localPreviewStyle))
- );
+ this.previewStyles = this.previewManager.renderStyles();
}
updatePreviewSize(width: number, height: number): void {
- if (!this.externalPreviewHelper.isVisible()) {
- return;
+ const helper = this.previewManager.getVisiblePreview();
+
+ if ((!helper) || (!helper.reactsToSizeUpdates())) {
+ return;
}
if ((width) && (height)) {
this.debugLog('ImagePreview: updatePreviewSize', width, height, width / height);
- this.externalPreviewHelper.setRatio(width / height);
+ helper.setRatio(width / height);
this.reRenderStyles();
}
}
hide(): void {
- this.debugLog('ImagePreview: hide', this.externalPreviewHelper.isVisible(), this.localPreviewHelper.isVisible());
-
this.cancelExitTimer();
this.url = null;
this.visible = false;
- this.localPreviewHelper.hide();
- this.externalPreviewHelper.hide();
+ this.previewManager.hide();
this.exitUrl = null;
this.exitInterval = null;
@@ -378,7 +392,7 @@
if ((!this.hasMouseMovedSince()) && (!force))
return;
- this.debugLog('ImagePreview: dismiss.exec', this.externalPreviewHelper.isVisible(), this.localPreviewHelper.isVisible(), url);
+ this.debugLog('ImagePreview: dismiss.exec', this.previewManager.getVisibilityStatus(), url);
// This timeout makes the preview window disappear with a slight delay, which helps UX
// when dealing with situations such as quickly scrolling text that moves the cursor away
@@ -393,7 +407,7 @@
show(initialUrl: string): void {
const url = this.jsMutator.mutateUrl(initialUrl);
- this.debugLog('ImagePreview: show', this.externalPreviewHelper.isVisible(), this.localPreviewHelper.isVisible(),
+ this.debugLog('ImagePreview: show', this.previewManager.getVisibilityStatus(),
this.visible, this.hasMouseMovedSince(), !!this.interval, this.sticky, url);
// console.log('SHOW');
@@ -430,15 +444,7 @@
() => {
this.debugLog('ImagePreview: show.timeout', this.url);
- const isLocal = this.localPreviewHelper.match(this.domain as string);
-
- isLocal
- ? this.localPreviewHelper.show(this.url as string)
- : this.localPreviewHelper.hide();
-
- this.externalPreviewHelper.match(this.domain as string)
- ? this.externalPreviewHelper.show(this.url as string)
- : this.externalPreviewHelper.hide();
+ const helper = this.previewManager.show(this.url || undefined, this.domain);
this.interval = null;
this.visible = true;
@@ -449,7 +455,11 @@
this.reRenderStyles();
- this.setState(isLocal ? 'loaded' : 'loading');
+ if (helper) {
+ this.setState(helper.shouldTrackLoading() ? 'loading' : 'loaded');
+ } else {
+ this.setState('loaded');
+ }
},
due
) as Timer;
@@ -504,8 +514,7 @@
this.debug = !this.debug;
this.jsMutator.setDebug(this.debug);
- this.localPreviewHelper.setDebug(this.debug);
- this.externalPreviewHelper.setDebug(this.debug);
+ this.previewManager.setDebug(this.debug);
if (this.debug) {
const webview = this.getWebview();
@@ -550,26 +559,44 @@
this.hide();
}
+
toggleJsMode(): void {
this.runJs = !this.runJs;
}
- reloadUrl(): void {
- if (this.externalPreviewHelper.isVisible()) {
- const webview = this.getWebview();
- webview.reload();
+ reloadUrl(): void {
+ const helper = this.previewManager.getVisiblePreview();
+
+ if ((!helper) || (!helper.usesWebView())) {
+ return;
}
+
+ // helper.reload();
+ this.getWebview().reload();
}
+
getWebview(): WebviewTag {
return this.$refs.imagePreviewExt as WebviewTag;
}
+ getCharacterPreview(): CharacterPreview {
+ return this.$refs.characterPreview as CharacterPreview;
+ }
+
+
reset(): void {
- this.externalPreviewHelper = new ExternalImagePreviewHelper(this);
- this.localPreviewHelper = new LocalImagePreviewHelper(this);
+ this.previewManager = new PreviewManager(
+ this,
+ [
+ new ExternalImagePreviewHelper(this),
+ new LocalImagePreviewHelper(this),
+ new CharacterPreviewHelper(this)
+ // new ChannelPreviewHelper(this)
+ ]
+ );
this.url = null;
this.domain = undefined;
@@ -612,8 +639,15 @@
: false;
}
+
testError(): boolean {
- return ((this.state === 'error') && (this.externalPreviewHelper.isVisible()));
+ const helper = this.previewManager.getVisiblePreview();
+
+ if ((!helper) || (!helper.usesWebView())) {
+ return false;
+ }
+
+ return (this.state === 'error');
}
}
@@ -693,6 +727,7 @@
border: 1px solid rgba(255, 255, 255, 0.3);
padding: 0.5rem;
box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.2);
+ z-index: 1000;
a i.fa {
font-size: 1.25rem;
diff --git a/chat/preview/MatchTags.vue b/chat/preview/MatchTags.vue
new file mode 100644
index 0000000..db329e6
--- /dev/null
+++ b/chat/preview/MatchTags.vue
@@ -0,0 +1,80 @@
+
+
+ {{getTagDesc(key)}}
+
+
+
+
+
+
diff --git a/chat/preview/helper/character.ts b/chat/preview/helper/character.ts
new file mode 100644
index 0000000..4ba3cba
--- /dev/null
+++ b/chat/preview/helper/character.ts
@@ -0,0 +1,68 @@
+import { ImagePreviewHelper } from './helper';
+
+export class CharacterPreviewHelper extends ImagePreviewHelper {
+ static readonly FLIST_CHARACTER_PROTOCOL_TESTER = /^flist-character:\/\/(.+)/;
+
+ hide(): void {
+ this.visible = false;
+ this.url = undefined;
+ }
+
+
+ show(url: string | undefined): void {
+ this.visible = true;
+ this.url = url;
+
+ if (!url) {
+ return;
+ }
+
+ const match = url.match(CharacterPreviewHelper.FLIST_CHARACTER_PROTOCOL_TESTER);
+
+ if (!match) {
+ return;
+ }
+
+ const characterName = match[1];
+
+ // tslint:disable-next-line no-floating-promises
+ this.parent.getCharacterPreview().load(characterName);
+ }
+
+
+ setRatio(_ratio: number): void {
+ // do nothing
+ }
+
+
+ reactsToSizeUpdates(): boolean {
+ return false;
+ }
+
+
+ shouldTrackLoading(): boolean {
+ return false;
+ }
+
+
+ usesWebView(): boolean {
+ return false;
+ }
+
+
+ match(_domainName: string | undefined, url: string | undefined): boolean {
+ if (!url) {
+ return false;
+ }
+
+ return CharacterPreviewHelper.FLIST_CHARACTER_PROTOCOL_TESTER.test(url);
+ }
+
+
+ renderStyle(): Record {
+ return this.isVisible()
+ ? { display: 'block' }
+ : { display: 'none' };
+ }
+}
+
diff --git a/chat/preview/helper/external.ts b/chat/preview/helper/external.ts
index 3acad17..2f92663 100644
--- a/chat/preview/helper/external.ts
+++ b/chat/preview/helper/external.ts
@@ -3,7 +3,7 @@ import { ImagePreviewHelper } from './helper';
import * as _ from 'lodash';
export class ExternalImagePreviewHelper extends ImagePreviewHelper {
- protected lastExternalUrl: string | null = null;
+ protected lastExternalUrl: string | undefined = undefined;
protected allowCachedUrl = true;
@@ -47,6 +47,21 @@ export class ExternalImagePreviewHelper extends ImagePreviewHelper {
}
+ reactsToSizeUpdates(): boolean {
+ return true;
+ }
+
+
+ shouldTrackLoading(): boolean {
+ return true;
+ }
+
+
+ usesWebView(): boolean {
+ return true;
+ }
+
+
setDebug(debug: boolean): void {
this.debug = debug;
@@ -54,7 +69,7 @@ export class ExternalImagePreviewHelper extends ImagePreviewHelper {
}
- show(url: string): void {
+ show(url: string | undefined): void {
const webview = this.parent.getWebview();
if (!this.parent) {
@@ -65,6 +80,10 @@ export class ExternalImagePreviewHelper extends ImagePreviewHelper {
throw new Error('Empty webview!');
}
+ if (!url) {
+ throw new Error('Empty URL!');
+ }
+
// const oldUrl = this.url;
// const oldLastExternalUrl = this.lastExternalUrl;
@@ -113,8 +132,13 @@ export class ExternalImagePreviewHelper extends ImagePreviewHelper {
}
- match(domainName: string): boolean {
- return !((domainName === 'f-list.net') || (domainName === 'static.f-list.net'));
+ match(domainName: string | undefined, url: string | undefined): boolean {
+ if ((!domainName) || (!url)) {
+ return false;
+ }
+
+ return (ImagePreviewHelper.HTTP_TESTER.test(url))
+ && (!((domainName === 'f-list.net') || (domainName === 'static.f-list.net')));
}
@@ -152,6 +176,7 @@ export class ExternalImagePreviewHelper extends ImagePreviewHelper {
}
}
+
renderStyle(): Record {
return this.isVisible()
? _.merge({ display: 'flex' }, this.determineScalingRatio())
diff --git a/chat/preview/helper/helper.ts b/chat/preview/helper/helper.ts
index 74de924..66f9a24 100644
--- a/chat/preview/helper/helper.ts
+++ b/chat/preview/helper/helper.ts
@@ -1,16 +1,23 @@
import ImagePreview from '../ImagePreview.vue';
export abstract class ImagePreviewHelper {
+ static readonly HTTP_TESTER = /^https?:\/\//;
+
protected visible = false;
- protected url: string | null = 'about:blank';
+ protected url: string | undefined = 'about:blank';
protected parent: ImagePreview;
protected debug: boolean;
- abstract show(url: string): void;
+ abstract show(url: string | undefined): void;
abstract hide(): void;
- abstract match(domainName: string): boolean;
+ abstract match(domainName: string | undefined, url: string | undefined): boolean;
abstract renderStyle(): Record;
+ abstract reactsToSizeUpdates(): boolean;
+ abstract setRatio(ratio: number): void;
+ abstract shouldTrackLoading(): boolean;
+ abstract usesWebView(): boolean;
+
constructor(parent: ImagePreview) {
if (!parent) {
throw new Error('Empty parent!');
@@ -24,7 +31,7 @@ export abstract class ImagePreviewHelper {
return this.visible;
}
- getUrl(): string | null {
+ getUrl(): string | undefined {
return this.url;
}
diff --git a/chat/preview/helper/index.ts b/chat/preview/helper/index.ts
index e70dad5..588b76f 100644
--- a/chat/preview/helper/index.ts
+++ b/chat/preview/helper/index.ts
@@ -1,4 +1,6 @@
-export * from './helper';
+export * from './character';
export * from './external';
+export * from './helper';
export * from './local';
+export * from './manager';
diff --git a/chat/preview/helper/local.ts b/chat/preview/helper/local.ts
index c0e4d0f..e3b2622 100644
--- a/chat/preview/helper/local.ts
+++ b/chat/preview/helper/local.ts
@@ -1,20 +1,46 @@
import { ImagePreviewHelper } from './helper';
+
export class LocalImagePreviewHelper extends ImagePreviewHelper {
hide(): void {
this.visible = false;
- this.url = null;
+ this.url = undefined;
}
- show(url: string): void {
+ show(url: string | undefined): void {
this.visible = true;
this.url = url;
}
- match(domainName: string): boolean {
- return ((domainName === 'f-list.net') || (domainName === 'static.f-list.net'));
+ setRatio(_ratio: number): void {
+ // do nothing
+ }
+
+
+ reactsToSizeUpdates(): boolean {
+ return false;
+ }
+
+
+ shouldTrackLoading(): boolean {
+ return false;
+ }
+
+
+ usesWebView(): boolean {
+ return false;
+ }
+
+
+ match(domainName: string | undefined, url: string | undefined): boolean {
+ if ((!domainName) || (!url)) {
+ return false;
+ }
+
+ return (ImagePreviewHelper.HTTP_TESTER.test(url))
+ && ((domainName === 'f-list.net') || (domainName === 'static.f-list.net'));
}
diff --git a/chat/preview/helper/manager.ts b/chat/preview/helper/manager.ts
new file mode 100644
index 0000000..b16fd47
--- /dev/null
+++ b/chat/preview/helper/manager.ts
@@ -0,0 +1,107 @@
+import _ from 'lodash';
+import { ImagePreviewHelper } from './helper';
+import ImagePreview from '../ImagePreview.vue';
+
+export type RenderStyle = Record;
+
+export interface PreviewManagerHelper {
+ helper: ImagePreviewHelper;
+ renderStyle: RenderStyle;
+}
+
+
+export class PreviewManager {
+ private parent: ImagePreview;
+
+ private helpers: PreviewManagerHelper[];
+
+ private debugMode = false;
+
+ constructor(parent: ImagePreview, helperInstances: ImagePreviewHelper[]) {
+ this.parent = parent;
+ this.helpers = _.map(helperInstances, (helper) => ({ helper, renderStyle: {}}));
+ }
+
+ match(domain: string | undefined, url: string | undefined): PreviewManagerHelper | undefined {
+ return _.find(this.helpers, (h) => h.helper.match(domain, url));
+ }
+
+ matchIndex(domain: string | undefined, url: string | undefined): number {
+ return _.findIndex(this.helpers, (h) => h.helper.match(domain, url));
+ }
+
+ renderStyles(): Record {
+ _.each(
+ this.helpers,
+ (h) => {
+ h.renderStyle = h.helper.renderStyle();
+
+ this.debugLog('ImagePreview: pm.renderStyles()', h.helper.constructor.name, JSON.parse(JSON.stringify(h.renderStyle)));
+ }
+ );
+
+ return _.fromPairs(
+ _.map(
+ this.helpers, (h) => ([h.helper.constructor.name, h.renderStyle])
+ )
+ );
+ }
+
+ getVisiblePreview(): ImagePreviewHelper | undefined {
+ const found = _.find(this.helpers, (h) => h.helper.isVisible());
+
+ return found ? found.helper : undefined;
+ }
+
+
+ show(url: string | undefined, domain: string | undefined): ImagePreviewHelper | undefined {
+ const matchedHelper = this.match(domain, url);
+
+ _.each(
+ _.filter(this.helpers, (h) => (h !== matchedHelper)),
+ (h) => h.helper.hide()
+ );
+
+ if (!matchedHelper) {
+ this.debugLog('ImagePreview: pm.show()', 'Unmatched helper', url, domain);
+ return undefined;
+ }
+
+ matchedHelper.helper.show(url);
+ return matchedHelper.helper;
+ }
+
+
+ hide(): void {
+ _.each(
+ this.helpers,
+ (h) => {
+ this.debugLog('ImagePreview: pm.hide()', h.helper.constructor.name, h.helper.isVisible());
+ h.helper.hide();
+ }
+ );
+ }
+
+
+ getVisibilityStatus(): Record {
+ return _.fromPairs(
+ _.map(
+ this.helpers, (h) => [h.helper.constructor.name, h.helper.isVisible()]
+ )
+ );
+ }
+
+
+ setDebug(debugMode: boolean): void {
+ _.each(this.helpers, (h) => h.helper.setDebug(debugMode));
+
+ this.debugMode = debugMode;
+ }
+
+
+ debugLog(...messages: any[]): void {
+ if (this.debugMode) {
+ this.parent.debugLog(...messages);
+ }
+ }
+}
diff --git a/learn/matcher-types.ts b/learn/matcher-types.ts
index f0a6630..d184aa8 100644
--- a/learn/matcher-types.ts
+++ b/learn/matcher-types.ts
@@ -92,6 +92,15 @@ export enum FurryPreference {
FurriesPreferredHumansOk = 149
}
+export const furryPreferenceMapping = {
+ [FurryPreference.FurriesOnly]: 'furries only',
+ [FurryPreference.FursAndHumans]: 'loves furries and humans',
+ [FurryPreference.HumansOnly]: 'humans only',
+ [FurryPreference.HumansPreferredFurriesOk]: 'loves humans, likes furries',
+ [FurryPreference.FurriesPreferredHumansOk]: 'loves furries, likes humans'
+};
+
+
export interface GenderKinkIdMap {
[key: number]: Kink
}
diff --git a/site/character_page/sidebar.vue b/site/character_page/sidebar.vue
index 4fed9c6..a6ef1f0 100644
--- a/site/character_page/sidebar.vue
+++ b/site/character_page/sidebar.vue
@@ -1,13 +1,12 @@