diff --git a/chat/ImagePreview.vue b/chat/ImagePreview.vue
index 3882151..3895467 100644
--- a/chat/ImagePreview.vue
+++ b/chat/ImagePreview.vue
@@ -8,11 +8,11 @@
-
+
@@ -40,19 +40,112 @@
}
+ abstract class ImagePreviewHelper {
+ protected visible = false;
+ protected url: string | null = 'about:blank';
+ protected parent: ImagePreview;
+
+ abstract show(url: string): void;
+ abstract hide(): void;
+ abstract match(domainName: string): boolean;
+
+ constructor(parent: ImagePreview) {
+ this.parent = parent;
+ }
+
+ isVisible(): boolean {
+ return this.visible;
+ }
+
+ getUrl(): string | null {
+ return this.url;
+ }
+ }
+
+
+ class LocalImagePreviewHelper extends ImagePreviewHelper {
+ hide(): void {
+ this.visible = false;
+ this.url = null;
+ }
+
+
+ show(url: string): void {
+ this.visible = true;
+ this.url = url;
+ }
+
+
+ match(domainName: string): boolean {
+ return ((domainName === 'f-list.net') || (domainName === 'static.f-list.net'));
+ }
+ }
+
+
+ class ExternalImagePreviewHelper extends ImagePreviewHelper {
+ protected lastExternalUrl: string | null = null;
+
+ protected allowCachedUrl = true;
+
+ hide(): void {
+ const wasVisible = this.visible;
+
+ if (this.parent.debug)
+ console.log('ImagePreview: exec hide mutator');
+
+ if (wasVisible) {
+ const webview = this.parent.getWebview();
+
+ if (this.allowCachedUrl) {
+ webview.executeJavaScript(this.parent.jsMutator.getHideMutator());
+ } else {
+ webview.loadURL('about:blank');
+ }
+
+ this.visible = false;
+ }
+ }
+
+
+ show(url: string): void {
+ const webview = this.parent.getWebview();
+
+ try {
+ if ((this.allowCachedUrl) && ((webview.getURL() === url) || (url === this.lastExternalUrl))) {
+ if (this.parent.debug)
+ console.log('ImagePreview: exec re-show mutator');
+
+ webview.executeJavaScript(this.parent.jsMutator.getReShowMutator());
+ } else {
+ if (this.parent.debug)
+ console.log('ImagePreview: must load; skip re-show because urls don\'t match', this.url, webview.getURL());
+
+ webview.loadURL(url);
+ }
+
+ } catch (err) {
+ console.error('ImagePreview: Webview reuse error', err);
+ }
+
+ this.url = url;
+ this.lastExternalUrl = url;
+ this.visible = true;
+ }
+
+ match(domainName: string): boolean {
+ return !((domainName === 'f-list.net') || (domainName === 'static.f-list.net'));
+ }
+ }
+
+
@Component
export default class ImagePreview extends Vue {
private readonly MinTimePreviewVisible = 100;
visible = false;
- externalUrlVisible = false;
- internalUrlVisible = false;
-
- externalUrl: string | null = null;
- internalUrl: string | null = null;
-
- lastExternalUrl = '';
+ externalPreviewHelper = new ExternalImagePreviewHelper(this);
+ localPreviewHelper = new LocalImagePreviewHelper(this);
url: string | null = null;
domain: string | undefined;
@@ -61,7 +154,8 @@
runJs = true;
debug = false;
- private jsMutator = new ImagePreviewMutator(this.debug);
+ jsMutator = new ImagePreviewMutator(this.debug);
+
private interval: Timer | null = null;
private exitInterval: Timer | null = null;
@@ -104,16 +198,31 @@
const webview = this.getWebview();
webview.addEventListener(
- 'dom-ready',
+ 'update-target-url', // 'did-navigate', // 'dom-ready',
(event: EventBusEvent) => {
const url = webview.getURL();
- const js = this.jsMutator.getMutatorJsForSite(url);
+ const js = this.jsMutator.getMutatorJsForSite(url, 'update-target-url');
+
+ if (this.debug)
+ console.log('ImagePreview update-target', event, js);
+
+ if ((js) && (this.runJs))
+ webview.executeJavaScript(js);
+ }
+ );
+
+
+ webview.addEventListener(
+ 'dom-ready', // 'did-navigate', // 'dom-ready',
+ (event: EventBusEvent) => {
+ const url = webview.getURL();
+ const js = this.jsMutator.getMutatorJsForSite(url, 'dom-ready');
if (this.debug)
console.log('ImagePreview dom-ready', event, js);
if ((js) && (this.runJs))
- webview.executeJavaScript(js);
+ webview.executeJavaScript(js, true);
}
);
@@ -168,13 +277,13 @@
_.each(
- ['did-start-loading', 'load-commit', 'will-navigate', 'did-navigate', 'did-navigate-in-page', 'update-target-url'],
+ ['did-start-loading', 'load-commit', 'dom-ready', 'will-navigate', 'did-navigate', 'did-navigate-in-page', 'update-target-url'],
(en: string) => {
webview.addEventListener(
en,
(event: Event) => {
if (this.debug)
- console.log(`ImagePreview ${en}`, event);
+ console.log(`ImagePreview ${en} ${Date.now()}`, event);
}
);
}
@@ -200,27 +309,15 @@
hide(): void {
if (this.debug)
- console.log('ImagePreview: hide', this.externalUrlVisible, this.internalUrlVisible);
+ console.log('ImagePreview: hide', this.externalPreviewHelper.isVisible(), this.localPreviewHelper.isVisible());
this.cancelExitTimer();
this.url = null;
this.visible = false;
- if (this.externalUrlVisible) {
- const webview = this.getWebview();
-
- if (this.debug)
- console.log('ImagePreview: exec hide mutator');
-
- webview.executeJavaScript(this.jsMutator.getHideMutator());
- }
-
- this.internalUrlVisible = false;
- this.externalUrlVisible = false;
-
- // this.externalUrl = null; // 'about:blank';
- this.internalUrl = null; // 'about:blank';
+ this.localPreviewHelper.hide();
+ this.externalPreviewHelper.hide();
this.exitUrl = null;
this.exitInterval = null;
@@ -262,7 +359,7 @@
return;
if (this.debug)
- console.log('ImagePreview: dismiss.exec', this.externalUrlVisible, this.internalUrlVisible, url);
+ console.log('ImagePreview: dismiss.exec', this.externalPreviewHelper.isVisible(), this.localPreviewHelper.isVisible(), 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
@@ -278,7 +375,7 @@
const url = this.jsMutator.mutateUrl(initialUrl);
if (this.debug)
- console.log('ImagePreview: show', this.externalUrlVisible, this.internalUrlVisible,
+ console.log('ImagePreview: show', this.externalPreviewHelper.isVisible(), this.localPreviewHelper.isVisible(),
this.visible, this.hasMouseMovedSince(), !!this.interval, this.sticky, url);
// console.log('SHOW');
@@ -325,36 +422,13 @@
if (this.debug)
console.log('ImagePreview: show.timeout', this.url);
- const isInternal = this.isInternalUrl();
+ this.localPreviewHelper.match(this.domain as string)
+ ? this.localPreviewHelper.show(this.url as string)
+ : this.localPreviewHelper.hide();
- this.internalUrlVisible = isInternal;
- this.externalUrlVisible = !isInternal;
-
- if (isInternal) {
- this.internalUrl = this.url;
- } else {
- const webview = this.getWebview();
-
- try {
- if ((webview.getURL() === this.url) || (this.url === this.lastExternalUrl)) {
- if (this.debug)
- console.log('ImagePreview: exec re-show mutator');
-
- webview.executeJavaScript(this.jsMutator.getReShowMutator());
- } else {
- if (this.debug)
- console.log('ImagePreview: must load; skip re-show because urls don\'t match', this.url, webview.getURL());
-
- webview.loadURL(this.url as string);
- }
-
- } catch (err) {
- console.error('ImagePreview: Webview reuse error', err);
- }
-
- this.externalUrl = this.url;
- this.lastExternalUrl = this.url as string;
- }
+ this.externalPreviewHelper.match(this.domain as string)
+ ? this.externalPreviewHelper.show(this.url as string)
+ : this.externalPreviewHelper.hide();
this.visible = true;
this.visibleSince = Date.now();
@@ -401,14 +475,14 @@
return this.url;
}
- isExternalUrl(): boolean {
+ /* isExternalUrl(): boolean {
// 'f-list.net' is tested here on purpose, because keeps the character URLs from being previewed
return !((this.domain === 'f-list.net') || (this.domain === 'static.f-list.net'));
}
isInternalUrl(): boolean {
return !this.isExternalUrl();
- }
+ }*/
toggleDevMode(): void {
this.debug = !this.debug;
@@ -434,14 +508,13 @@
}
reloadUrl(): void {
- if (this.externalUrlVisible) {
+ if (this.externalPreviewHelper.isVisible()) {
const webview = this.getWebview();
webview.reload();
}
}
-
getWebview(): WebviewTag {
return this.$refs.imagePreviewExt as WebviewTag;
}
diff --git a/chat/image-preview-mutator.ts b/chat/image-preview-mutator.ts
index 3e75190..d80bbd8 100644
--- a/chat/image-preview-mutator.ts
+++ b/chat/image-preview-mutator.ts
@@ -9,6 +9,7 @@ import { domain as extractDomain } from '../bbcode/core';
export interface PreviewMutator {
match: string | RegExp;
injectJs: string;
+ eventName: string;
urlMutator?(url: string): string;
}
@@ -45,12 +46,15 @@ export class ImagePreviewMutator {
}
- getMutatorJsForSite(url: string): string | undefined {
+ getMutatorJsForSite(url: string, eventName: string): string | undefined {
let mutator = this.matchMutator(url);
if (!mutator)
mutator = this.hostMutators['default'];
+ if (mutator.eventName !== eventName)
+ return;
+
return this.wrapJs(mutator.injectJs) + this.getReShowMutator();
}
@@ -77,13 +81,19 @@ export class ImagePreviewMutator {
return `(() => { try { ${mutatorJs} } catch (err) { console.error('Mutator error', err); } })();`;
}
- protected add(domain: string | RegExp, mutatorJs: string, urlMutator?: (url: string) => string): void {
+ protected add(
+ domain: string | RegExp,
+ mutatorJs: string,
+ urlMutator?: (url: string) => string,
+ eventName: string = 'update-target-url'
+ ): void {
if (domain instanceof RegExp) {
this.regexMutators.push(
{
match: domain,
injectJs: mutatorJs,
- urlMutator
+ urlMutator,
+ eventName
}
);
@@ -93,30 +103,34 @@ export class ImagePreviewMutator {
this.hostMutators[domain] = {
match: domain,
injectJs: mutatorJs,
- urlMutator
+ urlMutator,
+ eventName
};
}
protected init(): void {
- this.add('default', this.getBaseJsMutatorScript('#video, #image, video, img'));
- this.add('e621.net', this.getBaseJsMutatorScript('#image, video'));
- this.add('e-hentai.org', this.getBaseJsMutatorScript('#img, video'));
- this.add('gelbooru.com', this.getBaseJsMutatorScript('#image, video'));
- this.add('chan.sankakucomplex.com', this.getBaseJsMutatorScript('#image, video'));
- this.add('danbooru.donmai.us', this.getBaseJsMutatorScript('#image, video'));
- this.add('gfycat.com', this.getBaseJsMutatorScript('video'));
- this.add('gfycatporn.com', this.getBaseJsMutatorScript('video'));
- this.add('youtube.com', this.getBaseJsMutatorScript('video'));
- this.add('instantfap.com', this.getBaseJsMutatorScript('#post img, #post video'));
- this.add('webmshare.com', this.getBaseJsMutatorScript('video'));
- this.add('pornhub.com', this.getBaseJsMutatorScript('.mainPlayerDiv video, .photoImageSection img'));
- this.add('sex.com', this.getBaseJsMutatorScript('.image_frame img, .image_frame video'));
- this.add('redirect.media.tumblr.com', this.getBaseJsMutatorScript('picture img, picture video'));
- this.add('i.imgur.com', this.getBaseJsMutatorScript('video, img'));
- this.add('postimg.cc', this.getBaseJsMutatorScript('#main-image, video'));
- this.add('gifsauce.com', this.getBaseJsMutatorScript('video'));
- this.add('motherless.com', this.getBaseJsMutatorScript('.content video, .content img'));
- this.add(/^media[0-9]\.giphy\.com$/, this.getBaseJsMutatorScript('video, img[alt]'));
+ this.add('default', this.getBaseJsMutatorScript(['#video, video', '#image, img']));
+ this.add('e621.net', this.getBaseJsMutatorScript(['video', '#image']));
+ this.add('e-hentai.org', this.getBaseJsMutatorScript(['video', '#img']));
+ this.add('gelbooru.com', this.getBaseJsMutatorScript(['video', '#image']));
+ this.add('chan.sankakucomplex.com', this.getBaseJsMutatorScript(['video', '#image']));
+ this.add('danbooru.donmai.us', this.getBaseJsMutatorScript(['video', '#image']));
+ this.add('gfycat.com', this.getBaseJsMutatorScript(['video']), undefined, 'dom-ready');
+ this.add('gfycatporn.com', this.getBaseJsMutatorScript(['video']), undefined, 'dom-ready');
+ this.add('youtube.com', this.getBaseJsMutatorScript(['video']), undefined, 'dom-ready');
+ this.add('instantfap.com', this.getBaseJsMutatorScript(['#post video', '#post img']));
+ this.add('webmshare.com', this.getBaseJsMutatorScript(['video']));
+ this.add('pornhub.com', this.getBaseJsMutatorScript(['.mainPlayerDiv video', '.photoImageSection img']));
+ this.add('sex.com', this.getBaseJsMutatorScript(['.image_frame video', '.image_frame img']));
+ this.add('redirect.media.tumblr.com', this.getBaseJsMutatorScript(['picture video', 'picture img']));
+ this.add('i.imgur.com', this.getBaseJsMutatorScript(['video', 'img']));
+ this.add('postimg.cc', this.getBaseJsMutatorScript(['video', '#main-image']));
+ this.add('gifsauce.com', this.getBaseJsMutatorScript(['video']));
+ this.add('motherless.com', this.getBaseJsMutatorScript(['.content video', '.content img']));
+ this.add(/^media[0-9]\.giphy\.com$/, this.getBaseJsMutatorScript(['video', 'img[alt]']));
+ this.add('giphy.com', this.getBaseJsMutatorScript(['video', 'a > div > img']));
+ this.add(/^media[0-9]\.tenor\.com$/, this.getBaseJsMutatorScript(['#view .file video', '#view .file img']));
+ this.add('tenor.com', this.getBaseJsMutatorScript(['#view video', '#view img']));
// tslint:disable max-line-length
this.add(
@@ -124,7 +138,7 @@ export class ImagePreviewMutator {
`
const imageCount = $('.post-container video, .post-container img').length;
- ${this.getBaseJsMutatorScript('.post-container video, .post-container img', true)}
+ ${this.getBaseJsMutatorScript(['.post-container video', '.post-container img'], true)}
if(imageCount > 1)
$('#flistWrapper').append('+' + (imageCount - 1) + '
');
@@ -133,16 +147,19 @@ export class ImagePreviewMutator {
this.add(
'rule34.xxx',
- `${this.getBaseJsMutatorScript('#image, video')}
+ `${this.getBaseJsMutatorScript(['video', '#image'])}
const content = document.querySelector('#content');
- content.remove();
- `
+
+ if (content) content.remove();
+ `,
+ undefined,
+ 'dom-ready'
);
this.add(
'hentai-foundry.com',
- this.getBaseJsMutatorScript('main video, main img'),
+ this.getBaseJsMutatorScript(['main video', 'main img']),
(url: string): string => {
const u = urlHelper.parse(url, true);
@@ -159,14 +176,23 @@ export class ImagePreviewMutator {
);
}
- getBaseJsMutatorScript(elSelector: string, skipElementRemove: boolean = false): string {
+ getBaseJsMutatorScript(elSelector: string[], skipElementRemove: boolean = false): string {
return `const body = document.querySelector('body');
- let selected = Array.from(document.querySelectorAll('${elSelector}'))
- .filter((i) => ((i.width !== 1) && (i.height !== 1)));
+ const selectors = ${JSON.stringify(elSelector)};
+
+ // writing this out because sometimes .map and .reduce are overridden
+ let selected = [];
+
+ for (selector of selectors) {
+ const selectedElements = (Array.from(document.querySelectorAll(selector)).filter((i) => ((i.width !== 1) && (i.height !== 1))));
+ selected = selected.concat(selectedElements);
+ }
+
+ ${true /*this.debug*/ ? `console.log('Selector', '${elSelector}'); console.log('Selected', selected);` : ''}
const img = selected.shift();
- ${this.debug ? `console.log('Selector', '${elSelector}'); console.log('Selected', selected); console.log('Img', img);` : ''}
+ ${true /*this.debug*/ ? `console.log('Img', img);` : ''}
if (!img) { return; }
@@ -179,7 +205,18 @@ export class ImagePreviewMutator {
+ 'background-repeat: no-repeat !important; background-position: top left !important;'
+ 'opacity: 1 !important; padding: 0 !important; border: 0 !important; margin: 0 !important;';
- img.remove();
+ try {
+ img.remove();
+ } catch(err) {
+ console.error('Failed remove()', err);
+
+ try {
+ img.parentNode.removeChild(img);
+ } catch(err2) {
+ console.error('Failed removeChild()', err2);
+ }
+ }
+
el.append(img);
body.append(el);
body.class = '';
@@ -197,7 +234,33 @@ export class ImagePreviewMutator {
${this.debug ? "console.log('Wrapper', el);" : ''}
- if (img.play) { img.muted = true; img.play(); }
+ document.addEventListener('DOMContentLoaded', (event) => {
+ ${true /*this.debug*/ ? "console.log('on DOMContentLoaded');" : ''}
+
+ if (
+ (img.play)
+ && ((!img.paused) && (!img.ended) && (!(img.currentTime > 0)))
+ )
+ { img.muted = true; img.play(); }
+ });
+
+ document.addEventListener('load', (event) => {
+ ${true /*this.debug*/ ? "console.log('on load');" : ''}
+
+ if (
+ (img.play)
+ && ((!img.paused) && (!img.ended) && (!(img.currentTime > 0)))
+ )
+ { img.muted = true; img.play(); }
+ });
+
+
+ try {
+ if (img.play) { img.muted = true; img.play(); }
+ } catch (err) {
+ console.error('Failed img.play()', err);
+ }
+
let removeList = [];
const safeIds = ['flistWrapper', 'flistError', 'flistHider'];
diff --git a/site/character_page/character_page.vue b/site/character_page/character_page.vue
index 8df90c6..064a516 100644
--- a/site/character_page/character_page.vue
+++ b/site/character_page/character_page.vue
@@ -666,6 +666,10 @@
.vs, .scores {
display: none;
}
+
+ .minimize-btn {
+ opacity: 0.6;
+ }
}
h3 {
@@ -689,12 +693,19 @@
.scores {
float: left;
flex: 1;
- margin-right: 1rem;
+ margin: 0;
max-width: 25rem;
+ &.you {
+ margin-right: 1rem;
+ }
+
+ &.them {
+ margin-left: 1rem;
+ }
+
ul {
padding: 0;
- padding-left: 0rem;
list-style: none;
}
diff --git a/site/character_page/match-report.vue b/site/character_page/match-report.vue
index 645ed63..92f5728 100644
--- a/site/character_page/match-report.vue
+++ b/site/character_page/match-report.vue
@@ -1,6 +1,6 @@
-
+
@@ -54,7 +54,7 @@
isMinimized = false;
- @Watch('isMinimized')
+ @Watch('minimized')
onMinimizedChange(): void {
this.isMinimized = this.minimized;
}