From 146f8622d5bac2edc6b09f57c173bd057dfbcfad Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 8 Jul 2022 00:17:26 -0400 Subject: restructured currentUserUtils to avoid having import cycles. --- src/client/views/nodes/WebBox.tsx | 818 ++++++++++++++++++++++---------------- 1 file changed, 474 insertions(+), 344 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 d14af49ea..d97277c2b 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1,48 +1,48 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; import * as WebRequest from 'web-request'; -import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { HtmlField } from "../../../fields/HtmlField"; -import { InkTool } from "../../../fields/InkField"; -import { List } from "../../../fields/List"; -import { listSpec } from "../../../fields/Schema"; -import { ComputedField } from "../../../fields/ScriptField"; -import { Cast, ImageCast, NumCast, StrCast } from "../../../fields/Types"; -import { ImageField, WebField } from "../../../fields/URLField"; -import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils"; -import { Docs } from "../../documents/Documents"; -import { CurrentUserUtils } from "../../util/CurrentUserUtils"; -import { ScriptingGlobals } from "../../util/ScriptingGlobals"; -import { SnappingManager } from "../../util/SnappingManager"; -import { undoBatch } from "../../util/UndoManager"; -import { MarqueeOptionsMenu } from "../collections/collectionFreeForm"; -import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; -import { ContextMenu } from "../ContextMenu"; -import { ContextMenuProps } from "../ContextMenuItem"; -import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; -import { DocumentDecorations } from "../DocumentDecorations"; -import { Colors } from "../global/globalEnums"; -import { LightboxView } from "../LightboxView"; -import { MarqueeAnnotator } from "../MarqueeAnnotator"; -import { AnchorMenu } from "../pdf/AnchorMenu"; -import { Annotation } from "../pdf/Annotation"; -import { SidebarAnnos } from "../SidebarAnnos"; -import { StyleProp } from "../StyleProvider"; -import { DocumentViewProps } from "./DocumentView"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { HtmlField } from '../../../fields/HtmlField'; +import { InkTool } from '../../../fields/InkField'; +import { List } from '../../../fields/List'; +import { listSpec } from '../../../fields/Schema'; +import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; +import { ImageField, WebField } from '../../../fields/URLField'; +import { TraceMobx } from '../../../fields/util'; +import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; +import { Docs, DocUtils } from '../../documents/Documents'; +import { ScriptingGlobals } from '../../util/ScriptingGlobals'; +import { SnappingManager } from '../../util/SnappingManager'; +import { undoBatch } from '../../util/UndoManager'; +import { MarqueeOptionsMenu } from '../collections/collectionFreeForm'; +import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; +import { ContextMenu } from '../ContextMenu'; +import { ContextMenuProps } from '../ContextMenuItem'; +import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; +import { DocumentDecorations } from '../DocumentDecorations'; +import { Colors } from '../global/globalEnums'; +import { LightboxView } from '../LightboxView'; +import { MarqueeAnnotator } from '../MarqueeAnnotator'; +import { AnchorMenu } from '../pdf/AnchorMenu'; +import { Annotation } from '../pdf/Annotation'; +import { SidebarAnnos } from '../SidebarAnnos'; +import { StyleProp } from '../StyleProvider'; +import { DocumentViewProps } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; -import { LinkDocPreview } from "./LinkDocPreview"; -import { VideoBox } from "./VideoBox"; -import "./WebBox.scss"; -import React = require("react"); -const { CreateImage } = require("./WebBoxRenderer"); -const _global = (window /* browser */ || global /* node */) as any; -const htmlToText = require("html-to-text"); +import { LinkDocPreview } from './LinkDocPreview'; +import { VideoBox } from './VideoBox'; +import './WebBox.scss'; +import React = require('react'); +const { CreateImage } = require('./WebBoxRenderer'); +const _global = (window /* browser */ || global) /* node */ as any; +const htmlToText = require('html-to-text'); @observer export class WebBox extends ViewBoxAnnotatableComponent() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(WebBox, fieldKey); + } public static openSidebarWidth = 250; public static sidebarResizerWidth = 5; private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void); @@ -55,9 +55,9 @@ export class WebBox extends ViewBoxAnnotatableComponent) => Opt = () => undefined; private _sidebarRef = React.createRef(); private _searchRef = React.createRef(); - private _searchString = ""; - @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 wan 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 + private _searchString = ''; + @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 wan 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 _scrollTimer: any; @@ -69,22 +69,41 @@ export class WebBox extends ViewBoxAnnotatableComponent(); @observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight); - @computed get _url() { return this.webField?.toString() || ""; } - @computed get _urlHash() { return this._url ? WebBox.urlHash(this._url) + "" : ""; } - @computed get scrollHeight() { return Math.max(this.layoutDoc[HeightSym](), this._scrollHeight); } - @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } - @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); } - @computed get webField() { return Cast(this.dataDoc[this.props.fieldKey], WebField)?.url; } + @computed get _url() { + return this.webField?.toString() || ''; + } + @computed get _urlHash() { + return this._url ? WebBox.urlHash(this._url) + '' : ''; + } + @computed get scrollHeight() { + return Math.max(this.layoutDoc[HeightSym](), this._scrollHeight); + } + @computed get allAnnotations() { + return DocListCast(this.dataDoc[this.annotationKey]); + } + @computed get inlineTextAnnotations() { + return this.allAnnotations.filter(a => a.textInlineAnnotations); + } + @computed get webField() { + return Cast(this.dataDoc[this.props.fieldKey], WebField)?.url; + } @computed get webThumb() { - return this.props.thumbShown?.() && - ImageCast(this.layoutDoc["thumb-frozen"], - ImageCast(this.layoutDoc.thumbScrollTop === this.layoutDoc._scrollTop && this.layoutDoc.thumbNativeWidth === NumCast(this.layoutDoc.nativeWidth) && - this.layoutDoc.thumbNativeHeight === NumCast(this.layoutDoc.nativeHeight) ? this.layoutDoc.thumb : undefined))?.url; + return ( + this.props.thumbShown?.() && + ImageCast( + this.layoutDoc['thumb-frozen'], + ImageCast( + this.layoutDoc.thumbScrollTop === this.layoutDoc._scrollTop && this.layoutDoc.thumbNativeWidth === NumCast(this.layoutDoc.nativeWidth) && this.layoutDoc.thumbNativeHeight === NumCast(this.layoutDoc.nativeHeight) + ? this.layoutDoc.thumb + : undefined + ) + )?.url + ); } constructor(props: any) { super(props); - runInAction(() => this._webUrl = this._url); // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it. + runInAction(() => (this._webUrl = this._url)); // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it. } @action @@ -104,7 +123,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { if (!this._outerRef.current || this._outerRef.current.scrollHeight < pos) { @@ -113,14 +132,14 @@ export class WebBox extends ViewBoxAnnotatableComponent { - const imageBitmap = ImageCast(this.layoutDoc["thumb-frozen"])?.url.href; + const imageBitmap = ImageCast(this.layoutDoc['thumb-frozen'])?.url.href; const scrollTop = NumCast(this.layoutDoc._scrollTop); const nativeWidth = NumCast(this.layoutDoc.nativeWidth); - const nativeHeight = nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(); + const nativeHeight = (nativeWidth * this.props.PanelHeight()) / this.props.PanelWidth(); if (!this.lockout && this._iframe && !imageBitmap && (scrollTop !== this.layoutDoc.thumbScrollTop || nativeWidth !== this.layoutDoc.thumbNativeWidth || nativeHeight !== this.layoutDoc.thumbNativeHeight)) { var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument); if (!htmlString) { @@ -128,60 +147,65 @@ export class WebBox extends ViewBoxAnnotatableComponent { - VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then( - returnedfilename => setTimeout(action(() => { - this.lockout = false; - this.layoutDoc.thumb = new ImageField(returnedfilename); - this.layoutDoc.thumbScrollTop = scrollTop; - this.layoutDoc.thumbNativeWidth = nativeWidth; - this.layoutDoc.thumbNativeHeight = nativeHeight; - }), 500)); + CreateImage(this._webUrl.endsWith('/') ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, this._iframe.contentDocument?.styleSheets ?? [], htmlString, nativeWidth, nativeHeight, scrollTop) + .then((data_url: any) => { + VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename => + setTimeout( + action(() => { + this.lockout = false; + this.layoutDoc.thumb = new ImageField(returnedfilename); + this.layoutDoc.thumbScrollTop = scrollTop; + this.layoutDoc.thumbNativeWidth = nativeWidth; + this.layoutDoc.thumbNativeHeight = nativeHeight; + }), + 500 + ) + ); }) .catch(function (error: any) { console.error('oops, something went wrong!', error); }); } - } + }; async componentDidMount() { this.props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document. this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons) runInAction(() => { - this._annotationKeySuffix = () => this._urlHash + "-annotations"; - const reqdFuncs:{[key:string]: string} = {}; + this._annotationKeySuffix = () => this._urlHash + '-annotations'; + const reqdFuncs: { [key: string]: string } = {}; // bcz: need to make sure that doc.data-annotations points to the currently active web page's annotations (this could/should be when the doc is created) - reqdFuncs[this.fieldKey + "-annotations"] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`; - reqdFuncs[this.fieldKey + "-sidebar"] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`; - CurrentUserUtils.AssignScripts(this.dataDoc, {}, reqdFuncs); + reqdFuncs[this.fieldKey + '-annotations'] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`; + reqdFuncs[this.fieldKey + '-sidebar'] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`; + DocUtils.AssignScripts(this.dataDoc, {}, reqdFuncs); }); - reaction(() => this.props.isSelected(true) || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document), - async (selected) => { + reaction( + () => this.props.isSelected(true) || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document), + async selected => { if (selected) { this._webPageHasBeenRendered = true; - } else if ((!this.props.isContentActive(true) || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail) + } else if ( + (!this.props.isContentActive(true) || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail) !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && // don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty - LightboxView.LightboxDoc !== this.rootDoc) { // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty. + LightboxView.LightboxDoc !== this.rootDoc + ) { + // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty. this.updateThumb(); } - }, { fireImmediately: this.props.isSelected(true) || this.isAnyChildContentActive() || (Doc.isBrushedHighlightedDegreeUnmemoized(this.props.Document) ? true : false) }); + }, + { fireImmediately: this.props.isSelected(true) || this.isAnyChildContentActive() || (Doc.isBrushedHighlightedDegreeUnmemoized(this.props.Document) ? true : false) } + ); - this._disposers.autoHeight = reaction(() => this.layoutDoc._autoHeight, + this._disposers.autoHeight = reaction( + () => this.layoutDoc._autoHeight, autoHeight => { if (autoHeight) { - this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]); - this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1)); + this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']); + this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']) * (this.props.scaling?.() || 1)); } - }); + } + ); - if (this.webField?.href.indexOf("youtube") !== -1) { + if (this.webField?.href.indexOf('youtube') !== -1) { const youtubeaspect = 400 / 315; const nativeWidth = Doc.NativeWidth(this.layoutDoc); const nativeHeight = Doc.NativeHeight(this.layoutDoc); @@ -199,8 +223,9 @@ export class WebBox extends ViewBoxAnnotatableComponent NumCast(this.layoutDoc._scrollTop), - (scrollTop) => { + this._disposers.scrollReaction = reaction( + () => NumCast(this.layoutDoc._scrollTop), + scrollTop => { const viewTrans = StrCast(this.Document._viewTransition); const durationMiliStr = viewTrans.match(/([0-9]*)ms/); const durationSecStr = viewTrans.match(/([0-9.]*)s/); @@ -223,13 +248,13 @@ export class WebBox extends ViewBoxAnnotatableComponent this.urlEditor; // controls to be added to the top bar when a document of this type is selected scrollFocus = (doc: Doc, smooth: boolean) => { if (StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), !smooth); - if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) { + if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) { this.toggleSidebar(!smooth); } if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1; if (doc !== this.rootDoc && this._outerRef.current) { const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1); - const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * .1, - Math.max(NumCast(doc.y) + doc[HeightSym](), this.getScrollHeight())); + const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(doc.y) + doc[HeightSym](), this.getScrollHeight())); if (scrollTo !== undefined && this._initialScroll === undefined) { const focusSpeed = smooth ? 500 : 0; this.goTo(scrollTo, focusSpeed); @@ -263,22 +287,22 @@ export class WebBox extends ViewBoxAnnotatableComponent { const anchor = this._getAnchor(this._savedAnnotations) ?? Docs.Create.WebanchorDocument(this._url, { - title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop), + title: StrCast(this.rootDoc.title + ' ' + this.layoutDoc._scrollTop), y: NumCast(this.layoutDoc._scrollTop), - unrendered: true + unrendered: true, }); this.addDocumentWrapper(anchor); return anchor; - } + }; _textAnnotationCreator: (() => ObservableMap) | undefined; - savedAnnotationsCreator: (() => ObservableMap) = () => this._textAnnotationCreator?.() || this._savedAnnotations; + savedAnnotationsCreator: () => ObservableMap = () => this._textAnnotationCreator?.() || this._savedAnnotations; @action iframeUp = (e: PointerEvent) => { @@ -290,11 +314,10 @@ export class WebBox extends ViewBoxAnnotatableComponent 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._scrollTop) * scale); + AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale); } } - } + }; @action iframeDown = (e: PointerEvent) => { const sel = this._iframe?.contentWindow?.getSelection?.(); @@ -303,10 +326,12 @@ export class WebBox extends ViewBoxAnnotatableComponent this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it. + this._marqueeing = [e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale]; + if (word || ((e.target as any) || '').className.includes('rangeslider') || (e.target as any)?.onclick || (e.target as any)?.parentNode?.onclick) { + setTimeout( + action(() => (this._marqueeing = undefined)), + 100 + ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it. } else { this._iframeClick = this._iframe ?? undefined; this._isAnnotating = true; @@ -322,13 +347,13 @@ export class WebBox extends ViewBoxAnnotatableComponent this._scrollHeight; isFirefox = () => { - return "InstallTrigger" in window; // navigator.userAgent.indexOf("Chrome") !== -1; - } + return 'InstallTrigger' in window; // navigator.userAgent.indexOf("Chrome") !== -1; + }; iframeClick = () => this._iframeClick; iframeScaling = () => 1 / this.props.ScreenToLocalTransform().Scale; @@ -338,7 +363,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000); - iframe.setAttribute("enable-annotation", "true"); - iframe.contentDocument.addEventListener("click", undoBatch(action((e: MouseEvent) => { - let href = ""; - for (let ele = e.target as any; ele; ele = ele.parentElement) { - href = (typeof (ele.href) === "string" ? ele.href : ele.href?.baseVal) || ele.parentElement?.href || href; - } - const origin = this.webField?.origin; - if (href && origin) { - e.stopPropagation(); - setTimeout(() => this.submitURL(href.replace(Utils.prepend(""), origin))); - if (this._outerRef.current) { - this._outerRef.current.scrollTop = NumCast(this.layoutDoc._scrollTop); - this._outerRef.current.scrollLeft = 0; - } - } - }))); + setTimeout( + action(() => (this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0))), + 5000 + ); + iframe.setAttribute('enable-annotation', 'true'); + iframe.contentDocument.addEventListener( + 'click', + undoBatch( + action((e: MouseEvent) => { + let href = ''; + for (let ele = e.target as any; ele; ele = ele.parentElement) { + href = (typeof ele.href === 'string' ? ele.href : ele.href?.baseVal) || ele.parentElement?.href || href; + } + const origin = this.webField?.origin; + if (href && origin) { + e.stopPropagation(); + setTimeout(() => this.submitURL(href.replace(Utils.prepend(''), origin))); + if (this._outerRef.current) { + this._outerRef.current.scrollTop = NumCast(this.layoutDoc._scrollTop); + this._outerRef.current.scrollLeft = 0; + } + } + }) + ) + ); iframe.contentDocument.addEventListener('wheel', this.iframeWheel, false); //iframe.contentDocument.addEventListener('scroll', () => !this.active() && this._iframe && (this._iframe.scrollTop = NumCast(this.layoutDoc._scrollTop), false)); } - } + }; @action iframeWheel = (e: any) => { if (!this._scrollTimer) { - this._scrollTimer = setTimeout(action(() => this._scrollTimer = undefined), 250); // this turns events off on the iframe which allows scrolling to change direction smoothly + this._scrollTimer = setTimeout( + action(() => (this._scrollTimer = undefined)), + 250 + ); // this turns events off on the iframe which allows scrolling to change direction smoothly } - } + }; @action setDashScrollTop = (scrollTop: number, timeout: number = 250) => { const iframeHeight = Math.max(scrollTop, this._scrollHeight - this.panelHeight()); this._scrollTimer && clearTimeout(this._scrollTimer); - this._scrollTimer = setTimeout(action(() => { - this._scrollTimer = undefined; - const newScrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop; - if (!LinkDocPreview.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && - (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) { - this.layoutDoc.thumb = undefined; - this.layoutDoc.thumbScrollTop = undefined; - this.layoutDoc.thumbNativeWidth = undefined; - this.layoutDoc.thumbNativeHeight = undefined; - this.layoutDoc.scrollTop = this._outerRef.current.scrollTop = newScrollTop; - } else if (this._outerRef.current) this._outerRef.current.scrollTop = newScrollTop; - }), timeout); - } + this._scrollTimer = setTimeout( + action(() => { + this._scrollTimer = undefined; + const newScrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop; + if (!LinkDocPreview.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) { + this.layoutDoc.thumb = undefined; + this.layoutDoc.thumbScrollTop = undefined; + this.layoutDoc.thumbNativeWidth = undefined; + this.layoutDoc.thumbNativeHeight = undefined; + this.layoutDoc.scrollTop = this._outerRef.current.scrollTop = newScrollTop; + } else if (this._outerRef.current) this._outerRef.current.scrollTop = newScrollTop; + }), + timeout + ); + }; goTo = (scrollTop: number, duration: number) => { if (this._outerRef.current) { @@ -414,20 +455,20 @@ export class WebBox extends ViewBoxAnnotatableComponent { - const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), []); - const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []); + const future = Cast(this.dataDoc[this.fieldKey + '-future'], listSpec('string'), []); + const history = Cast(this.dataDoc[this.fieldKey + '-history'], listSpec('string'), []); if (checkAvailable) return future.length; runInAction(() => { if (future.length) { const curUrl = this._url; - this.dataDoc[this.fieldKey + "-history"] = new List([...history, this._url]); + this.dataDoc[this.fieldKey + '-history'] = new List([...history, this._url]); this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!)); if (this._webUrl === this._url) { this._webUrl = curUrl; - setTimeout(action(() => this._webUrl = this._url)); + setTimeout(action(() => (this._webUrl = this._url))); } else { this._webUrl = this._url; } @@ -435,21 +476,21 @@ export class WebBox extends ViewBoxAnnotatableComponent { - const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string")); - const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []); + const future = Cast(this.dataDoc[this.fieldKey + '-future'], listSpec('string')); + const history = Cast(this.dataDoc[this.fieldKey + '-history'], listSpec('string'), []); if (checkAvailable) return history.length; runInAction(() => { if (history.length) { const curUrl = this._url; - if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List([this._url]); - else this.dataDoc[this.fieldKey + "-future"] = new List([...future, this._url]); + if (future === undefined) this.dataDoc[this.fieldKey + '-future'] = new List([this._url]); + else this.dataDoc[this.fieldKey + '-future'] = new List([...future, this._url]); this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!)); if (this._webUrl === this._url) { this._webUrl = curUrl; - setTimeout(action(() => this._webUrl = this._url)); + setTimeout(action(() => (this._webUrl = this._url))); } else { this._webUrl = this._url; } @@ -457,21 +498,26 @@ export class WebBox extends ViewBoxAnnotatableComponent { - return Math.abs(s.split('').reduce((a: any, b: any) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0)); - } + return Math.abs( + s.split('').reduce((a: any, b: any) => { + a = (a << 5) - a + b.charCodeAt(0); + return a & a; + }, 0) + ); + }; @action submitURL = (newUrl?: string, preview?: boolean, dontUpdateIframe?: boolean) => { if (!newUrl) return; - if (!newUrl.startsWith("http")) newUrl = "http://" + newUrl; + if (!newUrl.startsWith('http')) newUrl = 'http://' + newUrl; try { - const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string")); - const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string")); + const future = Cast(this.dataDoc[this.fieldKey + '-future'], listSpec('string')); + const history = Cast(this.dataDoc[this.fieldKey + '-history'], listSpec('string')); const url = this.webField?.toString(); if (url && !preview) { - this.dataDoc[this.fieldKey + "-history"] = new List([...(history || []), url]); + this.dataDoc[this.fieldKey + '-history'] = new List([...(history || []), url]); this.layoutDoc._scrollTop = 0; if (this._webPageHasBeenRendered) { this.layoutDoc.thumb = undefined; @@ -486,47 +532,54 @@ export class WebBox extends ViewBoxAnnotatableComponent { const { dataTransfer } = e; - const html = dataTransfer.getData("text/html"); - const uri = dataTransfer.getData("text/uri-list"); - const url = uri || html || this._url || ""; - const newurl = url.startsWith(window.location.origin) ? - url.replace(window.location.origin, this._url?.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url; + const html = dataTransfer.getData('text/html'); + const uri = dataTransfer.getData('text/uri-list'); + const url = uri || html || this._url || ''; + const newurl = url.startsWith(window.location.origin) ? url.replace(window.location.origin, this._url?.match(/http[s]?:\/\/[^\/]*/)?.[0] || '') : url; this.submitURL(newurl); e.stopPropagation(); - } + }; onWebUrlValueKeyDown = (e: React.KeyboardEvent) => { - e.key === "Enter" && this.submitURL(this._keyInput.current!.value); + e.key === 'Enter' && this.submitURL(this._keyInput.current!.value); e.stopPropagation(); - } + }; @computed get urlEditor() { return ( -
e.preventDefault()} > - e.preventDefault()}> + e.preventDefault()} onKeyDown={this.onWebUrlValueKeyDown} - onClick={(e) => { + onClick={e => { this._keyInput.current!.select(); e.stopPropagation(); }} ref={this._keyInput} /> -
+
- - + +
); @@ -535,48 +588,59 @@ export class WebBox extends ViewBoxAnnotatableComponent { const cm = ContextMenu.Instance; const funcs: ContextMenuProps[] = []; - if (!cm.findByDescription("Options...")) { - !Doc.noviceMode && funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" }); + if (!cm.findByDescription('Options...')) { + !Doc.noviceMode && funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : 'Use') + ' Cors', event: () => (this.layoutDoc.useCors = !this.layoutDoc.useCors), icon: 'snowflake' }); funcs.push({ - description: (this.layoutDoc.allowScripts ? "Prevent" : "Allow") + " Scripts", event: () => { + description: (this.layoutDoc.allowScripts ? 'Prevent' : 'Allow') + ' Scripts', + event: () => { this.layoutDoc.allowScripts = !this.layoutDoc.allowScripts; if (this._iframe) { - runInAction(() => this._hackHide = true); - setTimeout(action(() => this._hackHide = false)); + runInAction(() => (this._hackHide = true)); + setTimeout(action(() => (this._hackHide = false))); } - }, icon: "snowflake" + }, + icon: 'snowflake', }); funcs.push({ - description: (!this.layoutDoc.forceReflow ? "Force" : "Prevent") + " Reflow", event: () => { + description: (!this.layoutDoc.forceReflow ? 'Force' : 'Prevent') + ' Reflow', + event: () => { const nw = !this.layoutDoc.forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1); this.layoutDoc.forceReflow = !nw; if (nw) { - Doc.SetInPlace(this.layoutDoc, this.fieldKey + "-nativeWidth", nw, true); + Doc.SetInPlace(this.layoutDoc, this.fieldKey + '-nativeWidth', nw, true); } - }, icon: "snowflake" + }, + icon: 'snowflake', }); - cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); + cm.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' }); } - } + }; @action onMarqueeDown = (e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { - setupMoveUpEvents(this, e, action(e => { - MarqueeAnnotator.clearAnnotations(this._savedAnnotations); - this._marqueeing = [e.clientX, e.clientY]; - return true; - }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false); + if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { + setupMoveUpEvents( + this, + e, + action(e => { + MarqueeAnnotator.clearAnnotations(this._savedAnnotations); + this._marqueeing = [e.clientX, e.clientY]; + return true; + }), + returnFalse, + () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), + false + ); } - } + }; @action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => { this._getAnchor = AnchorMenu.Instance?.GetAnchor; this._marqueeing = undefined; this._isAnnotating = false; this._iframeClick = undefined; const sel = this._iframe?.contentDocument?.getSelection(); - if (sel?.empty) sel.empty();// Chrome - else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox + if (sel?.empty) sel.empty(); // Chrome + else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox if (x !== undefined && y !== undefined) { this._setPreviewCursor?.(x, y, false, false); ContextMenu.Instance.closeMenu(); @@ -586,10 +650,10 @@ export class WebBox extends ViewBoxAnnotatableComponent e.stopPropagation()} dangerouslySetInnerHTML={{ __html: field.html }} />; } else if (field instanceof WebField) { const url = this.layoutDoc.useCors ? Utils.CorsProxy(this._webUrl) : this._webUrl; - view =