From a5d7f5c38192b91b7df3bd6ecace5ba7365449a6 Mon Sep 17 00:00:00 2001
From: "A.J. Shulman" <Shulman.aj@gmail.com>
Date: Sun, 11 May 2025 13:42:00 -0400
Subject: Made it so chunk Ids are seperately managed and made sure the doc id
 is sonsistent and not created in python spawn

---
 src/client/views/nodes/WebBox.tsx | 605 +++++++++++++-------------------------
 1 file changed, 200 insertions(+), 405 deletions(-)

(limited to 'src/client/views/nodes/WebBox.tsx')

diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 045af7ecd..1e158f484 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -4,7 +4,6 @@ import { htmlToText } from 'html-to-text';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import axios from 'axios';
 import * as WebRequest from 'web-request';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivHeight, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils';
 import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc';
@@ -70,20 +69,23 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     private _scrollTimer: NodeJS.Timeout | undefined;
     private _getAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
 
-    @observable private _webUrl = ''; // url of the page we want to display
-    @observable private _hackHide = false;
+    @observable private _webUrl = ''; // url of the src parameter of the embedded iframe but not necessarily the rendered page - eg, when following a link, the rendered page changes but we don't want the src parameter to also change as that would cause an unnecessary re-render.
+    @observable private _hackHide = false; // apparently changing the value of the 'sandbox' prop doesn't necessarily apply it to the active iframe.  so thisforces the ifrmae to be rebuilt when allowScripts is toggled
     @observable private _searching: boolean = false;
     @observable private _showSidebar = false;
     @observable private _webPageHasBeenRendered = false;
     @observable private _marqueeing: number[] | undefined = undefined;
-    @observable private _screenshotUrl: string | null = null; // URL to the screenshot image
-    @observable private _fullHeight: number = 0; // Full height of the webpage screenshot
-    @observable private _isLoadingScreenshot: boolean = false; // Loading state for the screenshot
+    get marqueeing() {
+        return this._marqueeing;
+    }
+    set marqueeing(val) {
+        val && this._marqueeref.current?.onInitiateSelection(val);
+        !val && this._marqueeref.current?.onTerminateSelection();
+        this._marqueeing = val;
+    }
     @observable private _iframe: HTMLIFrameElement | null = null;
     @observable private _savedAnnotations = new ObservableMap<number, (HTMLDivElement & { marqueeing?: boolean })[]>();
     @observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight);
-    @observable private _screenshotError: string | null = null; // Error message if screenshot fails
-    @observable private _loadingFromCache: boolean = false;
     @computed get _url() {
         return this.webField?.toString() || '';
     }
@@ -143,38 +145,31 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     updateIcon = async () => {
-        if (!this._screenshotUrl) {
-            // If we don't have a screenshot yet, capture one first
-            await this.captureWebScreenshot();
-        }
-
+        if (!this._iframe) return new Promise<void>(res => res());
         const scrollTop = NumCast(this.layoutDoc._layout_scrollTop);
         const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
         const nativeHeight = (nativeWidth * this._props.PanelHeight()) / this._props.PanelWidth();
-
+        let htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
+        if (!htmlString) {
+            htmlString = await fetch(ClientUtils.CorsProxy(this.webField!.href)).then(response => response.text());
+        }
         this.layoutDoc.thumb = undefined;
         this.Document.thumbLockout = true; // lock to prevent multiple thumb updates.
-
-        try {
-            // If we have a screenshot, use it directly for the thumbnail
-            if (this._screenshotUrl) {
-                return ClientUtils.convertDataUri(this._screenshotUrl, this.layoutDoc[Id] + '_icon_' + new Date().getTime(), true, this.layoutDoc[Id] + '_icon_').then(returnedfilename => {
+        return (CreateImage(this._webUrl.endsWith('/') ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, this._iframe.contentDocument?.styleSheets ?? [], htmlString, nativeWidth, nativeHeight, scrollTop) as Promise<string>)
+            .then((dataUrl: string) => {
+                if (dataUrl.includes('<!DOCTYPE')) {
+                    console.log('BAD DATA IN THUMB CREATION');
+                    return;
+                }
+                return ClientUtils.convertDataUri(dataUrl, this.layoutDoc[Id] + '_icon_' + new Date().getTime(), true, this.layoutDoc[Id] + '_icon_').then(returnedfilename => {
                     this.Document.thumbLockout = false;
                     this.layoutDoc.thumb = new ImageField(returnedfilename);
                     this.layoutDoc.thumbScrollTop = scrollTop;
                     this.layoutDoc.thumbNativeWidth = nativeWidth;
                     this.layoutDoc.thumbNativeHeight = nativeHeight;
                 });
-            } else {
-                console.log('No screenshot available for thumbnail generation');
-                this.Document.thumbLockout = false;
-                return Promise.resolve();
-            }
-        } catch (error) {
-            console.error('Error creating thumbnail:', error);
-            this.Document.thumbLockout = false;
-            return Promise.reject(error);
-        }
+            })
+            .catch((error: object) => console.error('oops, something went wrong!', error));
     };
 
     componentDidMount() {
@@ -243,64 +238,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             },
             { fireImmediately: true }
         );
-
-        // Check if we have a cached screenshot URL in metadata
-        if (this._url) {
-            this._webUrl = this._url;
-            const cachedScreenshotUrl = StrCast(this.dataDoc[this.fieldKey + '_screenshotUrl']);
-            const cachedHeight = NumCast(this.dataDoc[this.fieldKey + '_screenshotHeight']);
-
-            if (cachedScreenshotUrl && cachedHeight) {
-                // Use cached screenshot
-                this._loadingFromCache = true;
-                this._isLoadingScreenshot = true;
-
-                // Verify the cached screenshot exists by loading the image
-                const img = new Image();
-                img.onload = action(() => {
-                    this._screenshotUrl = cachedScreenshotUrl;
-                    this._fullHeight = cachedHeight;
-                    this._scrollHeight = cachedHeight;
-                    this._webPageHasBeenRendered = true;
-                    this._isLoadingScreenshot = false;
-                    this._loadingFromCache = false;
-
-                    // Apply dimensions and initial scroll
-                    if (this.layoutDoc._layout_autoHeight) {
-                        this.layoutDoc._nativeHeight = this._fullHeight;
-                        this._props.setHeight?.(this._fullHeight * (this._props.NativeDimScaling?.() || 1));
-                    }
-
-                    if (this._initialScroll !== undefined) {
-                        this.setScrollPos(this._initialScroll);
-                    }
-
-                    console.log(`Loaded cached screenshot: ${this._screenshotUrl}`);
-                });
-
-                img.onerror = action(() => {
-                    // If image fails to load, capture a new screenshot
-                    console.log('Cached screenshot not found, capturing new one');
-                    this._loadingFromCache = false;
-                    this.captureWebScreenshot();
-                });
-
-                img.src = cachedScreenshotUrl;
-            } else {
-                // No cached screenshot, capture a new one
-                this.captureWebScreenshot();
-            }
-        }
     }
     componentWillUnmount() {
-        // Clean up timers
-        if (this._scrollTimer) {
-            clearTimeout(this._scrollTimer);
-            this._scrollTimer = undefined;
-        }
-
-        // Clean up reaction disposers
+        this._iframetimeout && clearTimeout(this._iframetimeout);
+        this._iframetimeout = undefined;
         Object.values(this._disposers).forEach(disposer => disposer?.());
+        // this._iframe?.removeEventListener('wheel', this.iframeWheel, true);
+        // this._iframe?.contentDocument?.removeEventListener("pointerup", this.iframeUp);
     }
 
     private _selectionText: string = '';
@@ -414,6 +358,59 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     _textAnnotationCreator: (() => ObservableMap<number, (HTMLDivElement & { marqueeing?: boolean })[]>) | undefined;
     savedAnnotationsCreator: () => ObservableMap<number, (HTMLDivElement & { marqueeing?: boolean })[]> = () => this._textAnnotationCreator?.() || this._savedAnnotations;
 
+    @action
+    iframeMove = (e: PointerEvent) => {
+        const theclick = this.props
+            .ScreenToLocalTransform()
+            .inverse()
+            .transformPoint(e.clientX, e.clientY - NumCast(this.layoutDoc.layout_scrollTop));
+        this._marqueeref.current?.onMove(theclick);
+    };
+    @action
+    iframeUp = (e: PointerEvent) => {
+        this._iframe?.contentDocument?.removeEventListener('pointermove', this.iframeMove);
+        this.marqueeing = undefined;
+        this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
+        this._textAnnotationCreator = undefined;
+        this.DocumentView?.()?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
+        if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
+            const mainContBounds = ClientUtils.GetScreenTransform(this._mainCont.current!);
+            const scale = (this._props.NativeDimScaling?.() || 1) * mainContBounds.scale;
+            const sel = this._iframe.contentWindow.getSelection();
+            if (sel) {
+                this._selectionText = sel.toString();
+                AnchorMenu.Instance.setSelectedText(sel.toString());
+                this._textAnnotationCreator = () => this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined);
+                AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._layout_scrollTop) * scale);
+                // Changing which document to add the annotation to (the currently selected WebBox)
+                GPTPopup.Instance.setSidebarFieldKey(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
+                GPTPopup.Instance.addDoc = this.sidebarAddDocument;
+            }
+        } else {
+            const theclick = this.props
+                .ScreenToLocalTransform()
+                .inverse()
+                .transformPoint(e.clientX, e.clientY - NumCast(this.layoutDoc.layout_scrollTop));
+            if (!this._marqueeref.current?.isEmpty) this._marqueeref.current?.onEnd(theclick[0], theclick[1]);
+            else {
+                if (!(e.target as HTMLElement)?.tagName?.includes('INPUT')) this.finishMarquee(theclick[0], theclick[1]);
+                this._getAnchor = AnchorMenu.Instance?.GetAnchor;
+                this.marqueeing = undefined;
+            }
+
+            ContextMenu.Instance.closeMenu();
+            ContextMenu.Instance.setIgnoreEvents(false);
+            if (e?.button === 2 || e?.altKey) {
+                e?.preventDefault();
+                e?.stopPropagation();
+                setTimeout(() => {
+                    // if menu comes up right away, the down event can still be active causing a menu item to be selected
+                    this.specificContextMenu();
+                    this.DocumentView?.().onContextMenu(undefined, theclick[0], theclick[1]);
+                });
+            }
+        }
+    };
     @action
     webClipDown = (e: React.PointerEvent) => {
         e.stopPropagation();
@@ -508,6 +505,98 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this._scrollHeight = this._iframe?.contentDocument?.body?.scrollHeight ?? 0;
         this.addWebStyleSheetRule(this.addWebStyleSheet(this._iframe?.contentDocument), '::selection', { color: 'white', background: 'orange' }, '');
 
+        // Add error handler to suppress font CORS errors
+        if (this._iframe?.contentWindow) {
+            try {
+                // Track if any resource errors occurred
+                let hasResourceErrors = false;
+
+                // Override the console.error to filter out font CORS errors
+                const win = this._iframe.contentWindow as Window & { console: Console };
+                const originalConsoleError = win.console.error;
+                win.console.error = (...args: unknown[]) => {
+                    const errorMsg = args.map(arg => String(arg)).join(' ');
+                    if (errorMsg.includes('Access to font') && errorMsg.includes('has been blocked by CORS policy')) {
+                        // Mark that we have font errors
+                        hasResourceErrors = true;
+                        // Ignore font CORS errors
+                        return;
+                    }
+                    // Also catch other resource loading errors
+                    if (errorMsg.includes('ERR_FAILED') || errorMsg.includes('ERR_BLOCKED_BY_CLIENT')) {
+                        hasResourceErrors = true;
+                    }
+                    originalConsoleError.apply(win.console, args);
+                };
+
+                // Listen for resource loading errors
+                this._iframe.contentWindow.addEventListener(
+                    'error',
+                    (e: Event) => {
+                        const target = e.target as HTMLElement;
+                        if (target instanceof HTMLElement) {
+                            // If it's a resource that failed to load
+                            if (target.tagName === 'LINK' || target.tagName === 'IMG' || target.tagName === 'SCRIPT') {
+                                hasResourceErrors = true;
+                                // Apply error class after a short delay to allow initial content to load
+                                setTimeout(() => {
+                                    if (this._iframe && hasResourceErrors) {
+                                        this._iframe.classList.add('loading-error');
+                                    }
+                                }, 1000);
+                            }
+                        }
+                    },
+                    true
+                );
+
+                // Add fallback CSS for fonts that fail to load
+                const style = this._iframe.contentDocument?.createElement('style');
+                if (style) {
+                    style.textContent = `
+                        @font-face {
+                            font-family: 'CORS-fallback-serif';
+                            src: local('Times New Roman'), local('Georgia'), serif;
+                        }
+                        @font-face {
+                            font-family: 'CORS-fallback-sans';
+                            src: local('Arial'), local('Helvetica'), sans-serif;
+                        }
+                        /* Fallback for all fonts that fail to load */
+                        @font-face {
+                            font-display: swap !important;
+                        }
+                        
+                        /* Add a script to find and fix elements with failed fonts */
+                        @font-face {
+                            font-family: '__failed_font__';
+                            src: local('Arial');
+                            unicode-range: U+0000;
+                        }
+                    `;
+                    this._iframe.contentDocument?.head.appendChild(style);
+
+                    // Add a script to detect and fix font loading issues
+                    const script = this._iframe.contentDocument?.createElement('script');
+                    if (script) {
+                        script.textContent = `
+                            // Fix font loading issues with fallbacks
+                            setTimeout(function() {
+                                document.querySelectorAll('*').forEach(function(el) {
+                                    if (window.getComputedStyle(el).fontFamily.includes('__failed_font__')) {
+                                        el.classList.add('font-error-hidden');
+                                    }
+                                });
+                            }, 1000);
+                        `;
+                        this._iframe.contentDocument?.head.appendChild(script);
+                    }
+                }
+            } catch (e) {
+                console.log('Error setting up font error handling:', e);
+            }
+        }
+
         let href: Opt<string>;
         try {
             href = iframe?.contentWindow?.location.href;
@@ -658,23 +747,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 this.dataDoc[this.fieldKey + '_history'] = new List<string>([...history, this._url]);
                 this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!));
                 this._scrollHeight = 0;
-
-                // Reset screenshot state for new URL
-                this._screenshotUrl = null;
-                this._fullHeight = 0;
-                this._isLoadingScreenshot = false;
-
                 if (this._webUrl === this._url) {
                     this._webUrl = curUrl;
                     setTimeout(
                         action(() => {
                             this._webUrl = this._url;
-                            this.captureWebScreenshot(); // Capture screenshot for new URL
                         })
                     );
                 } else {
                     this._webUrl = this._url;
-                    this.captureWebScreenshot(); // Capture screenshot for new URL
                 }
                 return true;
             }
@@ -694,18 +775,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 else this.dataDoc[this.fieldKey + '_future'] = new List<string>([...future, this._url]);
                 this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!));
                 this._scrollHeight = 0;
-
-                // Reset screenshot state for new URL
-                this._screenshotUrl = null;
-                this._fullHeight = 0;
-                this._isLoadingScreenshot = false;
-
                 if (this._webUrl === this._url) {
                     this._webUrl = curUrl;
                     setTimeout(action(() => (this._webUrl = this._url)));
                 } else {
                     this._webUrl = this._url;
-                    this.captureWebScreenshot(); // Capture screenshot for new URL
                 }
                 return true;
             }
@@ -724,11 +798,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     this.layoutDoc.thumbNativeWidth = undefined;
                     this.layoutDoc.thumbNativeHeight = undefined;
                 }
-
+            }
+            if (!preview) {
                 if (!dontUpdateIframe) {
                     this._webUrl = this._url;
-                    // Capture screenshot when URL changes
-                    this.captureWebScreenshot();
                 }
             }
         } catch {
@@ -737,85 +810,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return true;
     };
 
-    @action
-    captureWebScreenshot = async () => {
-        if (!this._url || this._loadingFromCache) return;
-
-        try {
-            this._isLoadingScreenshot = true;
-            this._screenshotError = null;
-
-            console.log(`Capturing screenshot for URL: ${this._url}`);
-
-            try {
-                const response = await axios.post('/captureWebScreenshot', {
-                    url: this._url,
-                    width: NumCast(this.Document.nativeWidth, 1200),
-                    height: NumCast(this.Document.nativeHeight, 800),
-                    fullPage: true, // Request a full page screenshot
-                });
-
-                runInAction(() => {
-                    this._screenshotUrl = response.data.screenshotUrl;
-                    this._fullHeight = response.data.fullHeight;
-                    this._scrollHeight = response.data.fullHeight;
-                    this._webPageHasBeenRendered = true;
-                    this._isLoadingScreenshot = false;
-
-                    // Store screenshot URL and height in document metadata
-                    this.dataDoc[this.fieldKey + '_screenshotUrl'] = response.data.screenshotUrl;
-                    this.dataDoc[this.fieldKey + '_screenshotHeight'] = response.data.fullHeight;
-
-                    // Update native dimensions to match the screenshot
-                    if (!this.dataDoc[this.fieldKey + '_nativeWidth']) {
-                        this.dataDoc[this.fieldKey + '_nativeWidth'] = 1200; // Default width
-                    }
-
-                    if (!this.dataDoc[this.fieldKey + '_nativeHeight']) {
-                        this.dataDoc[this.fieldKey + '_nativeHeight'] = this._fullHeight;
-                    }
-
-                    // Set document height if needed
-                    if (this.layoutDoc._layout_autoHeight) {
-                        this.layoutDoc._nativeHeight = this._fullHeight;
-                        this._props.setHeight?.(this._fullHeight * (this._props.NativeDimScaling?.() || 1));
-                    }
-
-                    // Apply initial scroll if needed
-                    if (this._initialScroll !== undefined) {
-                        this.setScrollPos(this._initialScroll);
-                    }
-
-                    console.log(`Screenshot captured successfully: ${this._screenshotUrl} with height: ${this._fullHeight}px`);
-                });
-            } catch (error: any) {
-                // Handle error from the API
-                console.error('Error capturing screenshot:', error);
-                let errorMessage = 'Failed to capture webpage screenshot';
-
-                // Try to extract detailed error message from response
-                if (error.response && error.response.data && error.response.data.error) {
-                    errorMessage = error.response.data.error;
-                } else if (error.message) {
-                    errorMessage = error.message;
-                }
-
-                runInAction(() => {
-                    this._screenshotError = errorMessage;
-                    this._isLoadingScreenshot = false;
-                });
-            }
-        } catch (error: any) {
-            // Handle unexpected errors
-            runInAction(() => {
-                console.error('Unexpected error in captureWebScreenshot:', error);
-                this._screenshotError = 'An unexpected error occurred';
-                this._isLoadingScreenshot = false;
-            });
-        }
-    };
-
-    @action
     onWebUrlDrop = (e: React.DragEvent) => {
         const { dataTransfer } = e;
         const html = dataTransfer.getData('text/html');
@@ -830,28 +824,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     setData = (data: FieldType | Promise<RefField | undefined>) => {
         if (!(typeof data === 'string') && !(data instanceof WebField)) return false;
         if (Field.toString(data) === this._url) return false;
-
-        // Reset state for new URL
         this._scrollHeight = 0;
-        this._screenshotUrl = null;
-        this._fullHeight = 0;
-        this._isLoadingScreenshot = false;
-
-        // Clear stored screenshot metadata for the previous URL
-        this.dataDoc[this.fieldKey + '_screenshotUrl'] = undefined;
-        this.dataDoc[this.fieldKey + '_screenshotHeight'] = undefined;
-
         const oldUrl = this._url;
         const history = Cast(this.dataDoc[this.fieldKey + '_history'], listSpec('string'), []);
         const weburl = new WebField(Field.toString(data));
         this.dataDoc[this.fieldKey + '_future'] = new List<string>([]);
         this.dataDoc[this.fieldKey + '_history'] = new List<string>([...(history || []), oldUrl]);
         this.dataDoc[this.fieldKey] = weburl;
-
-        // Capture screenshot for the new URL
-        this._webUrl = weburl.toString();
-        this.captureWebScreenshot();
-
         return true;
     };
     onWebUrlValueKeyDown = (e: React.KeyboardEvent) => {
@@ -868,14 +847,26 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     description: (this.layoutDoc[this.fieldKey + '_useCors'] ? "Don't Use" : 'Use') + ' Cors',
                     event: () => {
                         this.layoutDoc[this.fieldKey + '_useCors'] = !this.layoutDoc[this.fieldKey + '_useCors'];
-                        // Re-capture screenshot with the new setting
-                        this.captureWebScreenshot();
                     },
                     icon: 'snowflake',
                 });
-
-            // Remove the "Allow Scripts" option since it's not relevant for screenshots
-
+            funcs.push({
+                description: (this.dataDoc[this.fieldKey + '_allowScripts'] ? 'Prevent' : 'Allow') + ' Scripts',
+                event: () => {
+                    this.dataDoc[this.fieldKey + '_allowScripts'] = !this.dataDoc[this.fieldKey + '_allowScripts'];
+                    if (this._iframe) {
+                        runInAction(() => {
+                            this._hackHide = true;
+                        });
+                        setTimeout(
+                            action(() => {
+                                this._hackHide = false;
+                            })
+                        );
+                    }
+                },
+                icon: 'snowflake',
+            });
             funcs.push({
                 description: (!this.layoutDoc.layout_reflowHorizontal ? 'Force' : 'Prevent') + ' Reflow',
                 event: () => {
@@ -887,21 +878,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 },
                 icon: 'snowflake',
             });
-
-            // Add a refresh option to re-capture the screenshot
-            funcs.push({
-                description: 'Refresh Screenshot',
-                event: () => this.captureWebScreenshot(),
-                icon: 'sync-alt',
-            });
-
-            !Doc.noviceMode &&
-                funcs.push({
-                    description: 'Update Icon',
-                    event: () => this.updateIcon(),
-                    icon: 'portrait',
-                });
-
+            !Doc.noviceMode && funcs.push({ description: 'Update Icon', event: () => this.updateIcon(), icon: 'portrait' });
             cm.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
         }
     };
@@ -913,7 +890,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
      */
     @action
     onMarqueeDown = (e: React.PointerEvent) => {
-        const sel = window.document.getSelection();
+        const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
         this._textAnnotationCreator = undefined;
         if (sel?.empty)
             sel.empty(); // Chrome
@@ -948,7 +925,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     @computed get urlContent() {
         if (this.ScreenToLocalBoxXf().Scale > 25) return <div />;
-
         setTimeout(
             action(() => {
                 if (this._initialScroll === undefined && !this._webPageHasBeenRendered) {
@@ -957,10 +933,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 this._webPageHasBeenRendered = true;
             })
         );
-
         const field = this.dataDoc[this._props.fieldKey];
-
-        // Handle HTML field (text content)
         if (field instanceof HtmlField) {
             return (
                 <span
@@ -977,8 +950,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 />
             );
         }
-
-        // Handle WebField (screenshot of webpage)
         if (field instanceof WebField) {
             const url = this.layoutDoc[this.fieldKey + '_useCors'] ? '/corsproxy/' + this._webUrl : this._webUrl;
             const scripts = this.dataDoc[this.fieldKey + '_allowScripts'] || this._webUrl.includes('wikipedia.org') || this._webUrl.includes('google.com') || this._webUrl.startsWith('https://bing');
@@ -1198,7 +1169,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         TraceMobx();
         // const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as Property.PointerEvents | undefined);
-        const scale = this._props.NativeDimScaling?.() || 1;
+        // const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
         return (
             <div
                 className="webBox-outerContent"
@@ -1207,16 +1178,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     height: '100%', //`${100 / scale}%`,
                     pointerEvents,
                 }}
+                // when active, block wheel events from propagating since they're handled by the iframe
                 onWheel={this.onZoomWheel}
                 onScroll={() => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
                 onPointerDown={this.onMarqueeDown}>
-                <div
-                    className="webBox-innerContent"
-                    style={{
-                        width: '100%',
-                        pointerEvents,
-                        backgroundColor: '#f5f5f5',
-                    }}>
+                <div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight > this._props.PanelHeight() && this._scrollHeight) || '100%', pointerEvents }}>
                     {this.content}
                     <div style={{ display: SnappingManager.CanEmbed ? 'none' : undefined, mixBlendMode: 'multiply' }}>{this.renderTransparentAnnotations}</div>
                     {this.renderOpaqueAnnotations}
@@ -1258,13 +1224,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         <FontAwesomeIcon icon={this._searching ? 'times' : 'search'} size="lg" />
                     </div>
                 </button>
-
-                {/* Refresh button */}
-                <button type="button" className="webBox-overlayButton webBox-refreshButton" title="Refresh webpage" onClick={() => this.captureWebScreenshot()}>
-                    <div className="webBox-overlayButton-iconCont" onPointerDown={e => e.stopPropagation()}>
-                        <FontAwesomeIcon icon="sync" size="lg" />
-                    </div>
-                </button>
             </div>
         );
     }
@@ -1293,25 +1252,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     annotationPointerEvents = () => (this._props.isContentActive() && (SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
     render() {
         TraceMobx();
-        const containerWidth = NumCast(this.layoutDoc._width) || this._props.PanelWidth();
+        const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as Property.PointerEvents);
-        const scale = this._props.NativeDimScaling?.() || 1;
-
-        // Force the component to be square
-        this.layoutDoc._height = containerWidth;
-        this.layoutDoc._width = containerWidth;
-        this.layoutDoc._forceActive = true;
-
+        const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
         return (
             <div
                 className="webBox"
                 ref={this._mainCont}
                 style={{
-                    pointerEvents: this.pointerEvents(),
+                    pointerEvents: this.pointerEvents(), //
                     position: SnappingManager.IsDragging ? 'absolute' : undefined,
-                    width: `${containerWidth}px`,
-                    height: `${containerWidth}px`,
-                    aspectRatio: '1 / 1', // Explicitly enforce square aspect ratio
                 }}>
                 <div className="webBox-background" style={{ backgroundColor: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string }} />
                 <div
@@ -1376,15 +1326,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             </div>
         );
     }
-
-    get marqueeing() {
-        return this._marqueeing;
-    }
-    set marqueeing(val) {
-        val && this._marqueeref.current?.onInitiateSelection(val);
-        !val && this._marqueeref.current?.onTerminateSelection();
-        this._marqueeing = val;
-    }
 }
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function urlHash(url: string) {
@@ -1395,149 +1336,3 @@ Docs.Prototypes.TemplateMap.set(DocumentType.WEB, {
     layout: { view: WebBox, dataField: 'data' },
     options: { acl: '', _height: 300, _layout_fitWidth: true, _layout_nativeDimEditable: true, _layout_reflowVertical: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' },
 });
-
-// Add CSS styles for screenshot mode
-const webBoxStyles = `
-.webBox-screenshot-container {
-    width: 100%;
-    position: relative;
-    overflow: visible;
-    display: flex;
-    align-items: flex-start;
-    justify-content: center;
-    background-color: #f5f5f5;
-}
-
-.webBox-screenshot {
-    width: 100%;
-    pointer-events: none;
-    display: block;
-    user-select: none;
-    object-fit: contain;
-    transition: opacity 0.3s ease;
-}
-
-.webBox-loading {
-    padding: 20px;
-    text-align: center;
-    color: #666;
-    background-color: #f5f5f5;
-    border-radius: 4px;
-    min-height: 200px;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-}
-
-.webBox-loading-message {
-    font-size: 16px;
-    margin-bottom: 15px;
-    color: #555;
-}
-
-.webBox-loading-spinner {
-    margin-top: 10px;
-    color: #1976d2;
-}
-
-.webBox-error {
-    padding: 20px;
-    color: #d32f2f;
-    text-align: center;
-    background-color: #ffebee;
-    border-radius: 4px;
-    min-height: 200px;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    gap: 15px;
-}
-
-.webBox-error-icon {
-    color: #d32f2f;
-    margin-bottom: 10px;
-}
-
-.webBox-error-message {
-    color: #d32f2f;
-    font-size: 14px;
-    max-width: 80%;
-    line-height: 1.5;
-}
-
-.webBox-error-actions {
-    margin-top: 10px;
-}
-
-.webBox-retry-button {
-    background-color: #f44336;
-    color: white;
-    border: none;
-    padding: 8px 16px;
-    border-radius: 4px;
-    cursor: pointer;
-    font-size: 14px;
-    transition: background-color 0.3s;
-}
-
-.webBox-retry-button:hover {
-    background-color: #d32f2f;
-}
-
-.webBox-placeholder {
-    padding: 20px;
-    text-align: center;
-    color: #757575;
-    background-color: #fafafa;
-    border-radius: 4px;
-    min-height: 200px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-}
-
-.webBox-refreshButton {
-    margin-right: 5px;
-}
-
-.webBox-innerContent {
-    position: relative;
-    width: 100%;
-    background-color: #f5f5f5;
-    overflow: visible;
-}
-
-.webBox-outerContent {
-    overflow: auto;
-    width: 100%;
-    background-color: #f5f5f5;
-    position: relative;
-}
-
-.webBox-container {
-    position: relative;
-    display: flex;
-    flex-direction: column;
-    height: 100%;
-    background-color: white;
-    border-radius: 4px;
-    overflow: hidden;
-}
-
-.webBox {
-    position: relative;
-    height: 100%;
-    width: 100%;
-    overflow: hidden;
-    background-color: white;
-    border-radius: 4px;
-    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
-}
-`;
-
-// Add the styles to the document
-const styleEl = document.createElement('style');
-styleEl.textContent = webBoxStyles;
-document.head.appendChild(styleEl);
-- 
cgit v1.2.3-70-g09d2

