From dff1f976215d8b8e0aa5816cbcea86e6a140ec2b Mon Sep 17 00:00:00 2001
From: "Mr. Stallion" <mrstallion@nobody.nowhere.fauxdomain.ext>
Date: Fri, 12 Jul 2019 17:11:55 -0500
Subject: [PATCH] Fix to image preview load bug

---
 bbcode/UrlTagView.vue                  | 18 ++++--
 chat/ImagePreview.vue                  | 90 ++++++++++++++++++++++----
 chat/image-preview-mutator.ts          | 20 ++++--
 site/character_page/character_page.vue |  4 +-
 4 files changed, 109 insertions(+), 23 deletions(-)

diff --git a/bbcode/UrlTagView.vue b/bbcode/UrlTagView.vue
index dd0c7cb..b22e25a 100644
--- a/bbcode/UrlTagView.vue
+++ b/bbcode/UrlTagView.vue
@@ -1,15 +1,17 @@
 <template>
     <span>
         <i class="fa fa-link"></i>
+        <!-- No prevent for @click on purpose -->
         <a
             :href="url"
             rel="nofollow noreferrer noopener"
             target="_blank"
             class="user-link"
-            @mouseover="show()"
-            @mouseenter="show()"
-            @mouseleave="dismiss()"
-            @mouseout="dismiss()"
+            @click="handleClick()"
+            @mouseover.prevent="show()"
+            @mouseenter.prevent="show()"
+            @mouseleave.prevent="dismiss()"
+            @mouseout.prevent="dismiss()"
             @click.middle.prevent="toggleStickyness()"
         >{{text}}</a>
         <span
@@ -45,8 +47,8 @@
             this.dismiss();
         }
 
-        dismiss(): void {
-            EventBus.$emit('imagepreview-dismiss', {url: this.url});
+        dismiss(force: boolean = false): void {
+            EventBus.$emit('imagepreview-dismiss', {url: this.url, force});
         }
 
         show(): void {
@@ -57,5 +59,9 @@
         toggleStickyness(): void {
             EventBus.$emit('imagepreview-toggle-stickyness', {url: this.url});
         }
+
+        handleClick(): void {
+            this.dismiss(true);
+        }
     }
 </script>
diff --git a/chat/ImagePreview.vue b/chat/ImagePreview.vue
index 06bcbca..a6b6ba9 100644
--- a/chat/ImagePreview.vue
+++ b/chat/ImagePreview.vue
@@ -18,6 +18,7 @@
 </template>
 
 <script lang="ts">
+    import * as _ from 'lodash';
     import {Component, Hook} from '@f-list/vue-ts';
     import Vue from 'vue';
     import { EventBus, EventBusEvent } from './event-bus';
@@ -50,6 +51,8 @@
         externalUrl: string | null = null;
         internalUrl: string | null = null;
 
+        lastExternalUrl = '';
+
         url: string | null = null;
         domain: string | undefined;
 
@@ -74,7 +77,7 @@
                 (eventData: EventBusEvent) => {
                     // console.log('Event dismiss', eventData.url);
 
-                    this.dismiss(eventData.url as string);
+                    this.dismiss(eventData.url as string, eventData.force as boolean);
                 }
             );
 
@@ -111,6 +114,7 @@
                 }
             );
 
+
             webview.addEventListener(
                 'did-fail-load',
                 (event: Event) => {
@@ -160,19 +164,41 @@
             );
 
 
+            _.each(
+                ['did-start-loading', 'load-commit', '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);
+                        }
+                    );
+                }
+            );
+
+
             setInterval(
                 () => {
                     if (((this.visible) && (!this.exitInterval) && (!this.shouldDismiss)) || (this.interval))
                         this.initialCursorPosition = screen.getCursorScreenPoint();
 
-                    if ((this.visible) && (this.shouldDismiss) && (this.hasMouseMovedSince()) && (!this.exitInterval) && (!this.interval))
+                    if ((this.visible) && (this.shouldDismiss) && (this.hasMouseMovedSince()) && (!this.exitInterval) && (!this.interval)) {
+                        if (this.debug) {
+                            console.log('ImagePreview: call hide from interval');
+                        }
+
                         this.hide();
+                    }
                 },
                 10
             );
         }
 
         hide(): void {
+            if (this.debug)
+                console.log('ImagePreview: hide', this.externalUrlVisible, this.internalUrlVisible);
+
             this.cancelExitTimer();
 
             this.url = null;
@@ -181,6 +207,9 @@
             if (this.externalUrlVisible) {
                 const webview = this.getWebview();
 
+                if (this.debug)
+                    console.log('ImagePreview: exec hide mutator');
+
                 webview.executeJavaScript(this.jsMutator.getHideMutator());
             }
 
@@ -198,7 +227,11 @@
             this.sticky = false;
         }
 
-        dismiss(url: string): void {
+        dismiss(url: string, force: boolean = false): void {
+            if (this.debug) {
+                console.log('ImagePreview: dismiss', url);
+            }
+
             if (this.url !== url)
                 return; // simply ignore
 
@@ -220,9 +253,12 @@
             this.exitUrl = this.url;
             this.shouldDismiss = true;
 
-            if (!this.hasMouseMovedSince())
+            if ((!this.hasMouseMovedSince()) && (!force))
                 return;
 
+            if (this.debug)
+                console.log('ImagePreview: dismiss.exec', this.externalUrlVisible, this.internalUrlVisible, 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
             // from the link
@@ -234,16 +270,37 @@
         }
 
         show(url: string): void {
+            if (this.debug)
+                console.log('ImagePreview: show', this.externalUrlVisible, this.internalUrlVisible,
+                this.visible, this.hasMouseMovedSince(), !!this.interval, this.sticky, url);
+
             // console.log('SHOW');
 
-            if ((this.visible) && (!this.hasMouseMovedSince()))
+            if ((this.visible) && (!this.exitInterval) && (!this.hasMouseMovedSince())) {
+                if (this.debug) {
+                    console.log('ImagePreview: show cancel: visible & not moved');
+                }
                 return;
+            }
 
-            if ((this.url === url) && ((this.visible) || (this.interval)))
-                return;
+            if ((this.url === url) && ((this.visible) || (this.interval))) {
+                if (this.debug) {
+                    console.log('ImagePreview: same url');
+                }
 
-            if ((this.url) && (this.sticky) && (this.visible))
                 return;
+            }
+
+            if ((this.url) && (this.sticky) && (this.visible)) {
+                if (this.debug) {
+                    console.log('ImagePreview: sticky visible');
+                }
+
+                return;
+            }
+
+            if (this.debug)
+                console.log('ImagePreview: show.exec', url);
 
             const due = ((url === this.exitUrl) && (this.exitInterval)) ? 0 : 100;
 
@@ -258,6 +315,9 @@
             // tslint:disable-next-line no-unnecessary-type-assertion
             this.interval = setTimeout(
                 () => {
+                    if (this.debug)
+                        console.log('ImagePreview: show.timeout', this.url);
+
                     const isInternal = this.isInternalUrl();
 
                     this.internalUrlVisible = isInternal;
@@ -269,14 +329,22 @@
                         const webview = this.getWebview();
 
                         try {
-                            if (webview.getURL() === this.url) {
+                            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: skip re-show because urls don\'t match', this.url, webview.getURL());
                             }
+
                         } catch (err) {
-                            console.log('Webview reuse error', err);
+                            console.error('ImagePreview: Webview reuse error', err);
                         }
 
                         this.externalUrl = this.url;
+                        this.lastExternalUrl = this.url as string;
                     }
 
                     this.visible = true;
@@ -297,7 +365,7 @@
 
                 return ((p.x !== this.initialCursorPosition.x) || (p.y !== this.initialCursorPosition.y));
             } catch (err) {
-                console.error(err);
+                console.error('ImagePreview', err);
                 return true;
             }
         }
diff --git a/chat/image-preview-mutator.ts b/chat/image-preview-mutator.ts
index 167f866..e7ba23f 100644
--- a/chat/image-preview-mutator.ts
+++ b/chat/image-preview-mutator.ts
@@ -36,7 +36,7 @@ export class ImagePreviewMutator {
         if (!mutator)
             mutator = this.hostMutators['default'];
 
-        return this.wrapJs(mutator.injectJs);
+        return this.wrapJs(mutator.injectJs) + this.getReShowMutator();
     }
 
     matchMutator(url: string): PreviewMutator | undefined {
@@ -253,15 +253,27 @@ export class ImagePreviewMutator {
                 opacity: 1 !important;
                 text-align: center !important;
             "></div>
-        `);
+        `) + this.wrapJs(
+            `
+                window.__flistUnhide = () => {
+                    const elements = document.querySelectorAll('#flistHider');
+
+                    if (elements) {
+                        elements.forEach( (el) => el.remove() );
+                    }
+                };
+            `
+        );
     }
 
     getReShowMutator(): string {
         return this.wrapJs(
             `
-            const el = document.querySelector('#flistHider');
+            const elements = document.querySelectorAll('#flistHider');
 
-            if (el) { el.remove(); }
+            if (elements) {
+                elements.forEach( (el) => el.remove() );
+            }
             `
         );
     }
diff --git a/site/character_page/character_page.vue b/site/character_page/character_page.vue
index d20e1fe..83abcb8 100644
--- a/site/character_page/character_page.vue
+++ b/site/character_page/character_page.vue
@@ -141,14 +141,14 @@
         beforeMount(): void {
             this.shared.authenticated = this.authenticated;
 
-            console.log('Beforemount');
+            // console.log('Beforemount');
         }
 
         @Hook('mounted')
         async mounted(): Promise<void> {
             await this.load(false);
 
-            console.log('mounted');
+            // console.log('mounted');
         }
 
         @Watch('tab')