aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/WebBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/WebBox.tsx')
-rw-r--r--src/client/views/nodes/WebBox.tsx273
1 files changed, 178 insertions, 95 deletions
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 3c4696df3..0f0008700 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -7,7 +7,7 @@ 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 } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { HtmlField } from '../../../fields/HtmlField';
import { InkTool } from '../../../fields/InkField';
@@ -55,7 +55,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
public static openSidebarWidth = 250;
public static sidebarResizerWidth = 5;
- static webStyleSheet = addStyleSheet();
+ static webStyleSheet = addStyleSheet().sheet;
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _outerRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -454,9 +454,27 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
};
@action
- iframeDown = () => {
- // This is an empty replacement to avoid linter errors
- // The original functionality is no longer needed
+ iframeDown = (e: PointerEvent) => {
+ this._textAnnotationCreator = undefined;
+ const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
+ if (sel?.empty && !(e.target as any).textContent)
+ sel.empty(); // Chrome
+ else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
+
+ this._props.select(false);
+ const theclick = this.props
+ .ScreenToLocalTransform()
+ .inverse()
+ .transformPoint(e.clientX, e.clientY - NumCast(this.layoutDoc.layout_scrollTop));
+ MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+ const target = e.target as HTMLElement;
+ const word = target && getWordAtPoint(target, e.clientX, e.clientY);
+ if (!word && !target?.className?.includes('rangeslider') && !target?.onclick && !target?.parentElement?.onclick) {
+ this.marqueeing = theclick;
+ this._marqueeref.current?.onInitiateSelection(this.marqueeing);
+ this._iframe?.contentDocument?.addEventListener('pointermove', this.iframeMove);
+ e.preventDefault();
+ }
};
isFirefox = () => 'InstallTrigger' in window; // navigator.userAgent.indexOf("Chrome") !== -1;
@@ -482,6 +500,122 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
_iframetimeout: NodeJS.Timeout | undefined = undefined;
@observable _warning = 0;
@action
+ iframeLoaded = () => {
+ const iframe = this._iframe;
+ if (this._initialScroll !== undefined) {
+ this.setScrollPos(this._initialScroll);
+ }
+ this._scrollHeight = this._iframe?.contentDocument?.body?.scrollHeight ?? 0;
+ this.addWebStyleSheetRule(this.addWebStyleSheet(this._iframe?.contentDocument), '::selection', { color: 'white', background: 'orange' }, '');
+
+ let href: Opt<string>;
+ try {
+ href = iframe?.contentWindow?.location.href;
+ } catch {
+ // runInAction(() => this._warning++);
+ href = undefined;
+ }
+ let requrlraw = decodeURIComponent(href?.replace(ClientUtils.prepend('') + '/corsproxy/', '') ?? this._url.toString());
+ if (requrlraw !== this._url.toString()) {
+ if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) {
+ const matches = requrlraw.match(/[^a-zA-z]q=[^&]*/g);
+ const newsearch = matches?.lastElement() || '';
+ if (matches) {
+ requrlraw = requrlraw.substring(0, requrlraw.indexOf(newsearch));
+ for (let i = 1; i < Array.from(matches)?.length; i++) {
+ requrlraw = requrlraw.replace(matches[i], '');
+ }
+ }
+ requrlraw = requrlraw
+ .replace(/q=[^&]*/, newsearch.substring(1))
+ .replace('search&', 'search?')
+ .replace('?gbv=1', '');
+ }
+ this.setData(requrlraw);
+ }
+ const iframeContent = iframe?.contentDocument;
+ if (iframeContent) {
+ iframeContent.addEventListener('pointerup', this.iframeUp);
+ iframeContent.addEventListener('pointerdown', this.iframeDown);
+ // iframeContent.addEventListener(
+ // 'wheel',
+ // e => {
+ // e.ctrlKey && e.preventDefault();
+ // },
+ // { passive: false }
+ // );
+ const initHeights = () => {
+ this._scrollHeight = Math.max(this._scrollHeight, iframeContent.body.scrollHeight || 0);
+ if (this._scrollHeight) {
+ this.Document.nativeHeight = Math.min(NumCast(this.Document.nativeHeight), this._scrollHeight);
+ this.layoutDoc.height = Math.min(NumCast(this.layoutDoc._height), (NumCast(this.layoutDoc._width) * NumCast(this.Document.nativeHeight)) / NumCast(this.Document.nativeWidth));
+ }
+ };
+ const swidth = Math.max(NumCast(this.Document.nativeWidth), iframeContent.body.scrollWidth || 0);
+ if (swidth) {
+ const aspectResize = swidth / NumCast(this.Document.nativeWidth, swidth);
+ this.layoutDoc.height = NumCast(this.layoutDoc._height) * aspectResize;
+ this.Document.nativeWidth = swidth;
+ this.Document.nativeHeight = (swidth * NumCast(this.layoutDoc._height)) / NumCast(this.layoutDoc._width);
+ }
+ initHeights();
+ this._iframetimeout && clearTimeout(this._iframetimeout);
+ this._iframetimeout = setTimeout(
+ action(() => initHeights),
+ 5000
+ );
+ iframeContent.addEventListener(
+ 'click',
+ undoable(
+ action((e: MouseEvent) => {
+ let eleHref = (e.target as any)?.outerHTML?.split('"="')[1]?.split('"')[0];
+ for (let ele = e.target as HTMLElement | Element | null; ele; ele = ele.parentElement) {
+ if ('href' in ele) {
+ eleHref = (typeof ele.href === 'string' ? ele.href : eleHref) || (ele.parentElement && 'href' in ele.parentElement ? (ele.parentElement.href as string) : eleHref);
+ }
+ }
+ const origin = this.webField?.origin;
+ if (eleHref && origin) {
+ const batch = UndoManager.StartBatch('webclick');
+ e.stopPropagation();
+ setTimeout(() => {
+ const url = eleHref.replace(ClientUtils.prepend(''), origin);
+ this.setData(url);
+ batch.end();
+ });
+ if (this._outerRef.current) {
+ this._outerRef.current.scrollTop = NumCast(this.layoutDoc._layout_scrollTop);
+ this._outerRef.current.scrollLeft = 0;
+ }
+ }
+ }),
+ 'follow web link'
+ )
+ );
+ iframe.contentDocument.addEventListener('wheel', this.iframeWheel, { passive: false });
+ }
+ };
+
+ @action
+ iframeWheel = (e: WheelEvent) => {
+ if (!this._scrollTimer) {
+ addStyleSheetRule(WebBox.webStyleSheet, 'webBox-iframe', { 'pointer-events': 'none' });
+ this._scrollTimer = setTimeout(() => {
+ this._scrollTimer = undefined;
+ clearStyleSheetRules(WebBox.webStyleSheet);
+ }, 250); // this turns events off on the iframe which allows scrolling to change direction smoothly
+ }
+ if (e.ctrlKey) {
+ if (this._innerCollectionView) {
+ this._innerCollectionView.zoom(e.screenX, e.screenY, e.deltaY);
+ const offset = e.clientY - NumCast(this.layoutDoc._layout_scrollTop);
+ this.layoutDoc.freeform_panY = offset - offset / NumCast(this.layoutDoc._freeform_scale) + NumCast(this.layoutDoc._layout_scrollTop) - NumCast(this.layoutDoc._layout_scrollTop) / NumCast(this.layoutDoc._freeform_scale);
+ }
+ e.preventDefault();
+ }
+ };
+
+ @action
setDashScrollTop = (scrollTop: number, timeout: number = 250) => {
const iframeHeight = Math.max(scrollTop, this._scrollHeight - this.panelHeight());
if (this._scrollTimer) {
@@ -515,8 +649,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
forward = (checkAvailable?: boolean) => {
- const future = Cast(this.dataDoc[this.fieldKey + '_future'], listSpec('string'), []);
- const history = Cast(this.dataDoc[this.fieldKey + '_history'], listSpec('string'), []);
+ const future = StrListCast(this.dataDoc[this.fieldKey + '_future']);
+ const history = StrListCast(this.dataDoc[this.fieldKey + '_history']);
if (checkAvailable) return future.length;
runInAction(() => {
if (future.length) {
@@ -550,13 +684,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
back = (checkAvailable?: boolean) => {
- const future = Cast(this.dataDoc[this.fieldKey + '_future'], listSpec('string'));
- const history = Cast(this.dataDoc[this.fieldKey + '_history'], listSpec('string'), []);
+ const future = StrListCast(this.dataDoc[this.fieldKey + '_future']);
+ const history = StrListCast(this.dataDoc[this.fieldKey + '_history']);
if (checkAvailable) return history.length;
runInAction(() => {
if (history.length) {
const curUrl = this._url;
- if (future === undefined) this.dataDoc[this.fieldKey + '_future'] = new List<string>([this._url]);
+ if (!future.length) this.dataDoc[this.fieldKey + '_future'] = new List<string>([this._url]);
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;
@@ -568,12 +702,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
if (this._webUrl === this._url) {
this._webUrl = curUrl;
- setTimeout(
- action(() => {
- this._webUrl = this._url;
- this.captureWebScreenshot(); // Capture screenshot for new URL
- })
- );
+ setTimeout(action(() => (this._webUrl = this._url)));
} else {
this._webUrl = this._url;
this.captureWebScreenshot(); // Capture screenshot for new URL
@@ -851,80 +980,36 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// Handle WebField (screenshot of webpage)
if (field instanceof WebField) {
- // Show loading state with spinner
- if (this._isLoadingScreenshot) {
- return (
- <div className="webBox-loading">
- <div className="webBox-loading-message">{this._loadingFromCache ? 'Loading cached webpage preview...' : 'Loading webpage preview...'}</div>
- <div className="webBox-loading-spinner">
- <FontAwesomeIcon className="documentdecorations-icon" icon="spinner" spin />
- </div>
- </div>
- );
- }
-
- // Show error state with retry button
- if (this._screenshotError) {
- return (
- <div className="webBox-error">
- <div className="webBox-error-icon">
- <FontAwesomeIcon icon="exclamation-triangle" size="2x" />
- </div>
- <div className="webBox-error-message">{this._screenshotError}</div>
- <div className="webBox-error-actions">
- <button onClick={() => this.captureWebScreenshot()} className="webBox-retry-button">
- <FontAwesomeIcon icon="sync" style={{ marginRight: '5px' }} />
- Retry
- </button>
- </div>
- </div>
- );
- }
-
- // Show screenshot in scrollable container
- if (this._screenshotUrl) {
- return (
- <div className="webBox-screenshot-container">
- <img
- src={this._screenshotUrl}
- alt="Webpage screenshot"
- className="webBox-screenshot"
- style={{
- width: '100%',
- height: 'auto',
- display: 'block',
- }}
- onError={action((e: React.SyntheticEvent<HTMLImageElement>) => {
- console.error('Error loading screenshot:', e);
- this._screenshotError = 'Failed to load screenshot image';
- this._isLoadingScreenshot = false;
- this.dataDoc[this.fieldKey + '_screenshotUrl'] = undefined;
- this.dataDoc[this.fieldKey + '_screenshotHeight'] = undefined;
- })}
- onLoad={() => {
- this._scrollHeight = this._fullHeight;
- if (this._initialScroll !== undefined) {
- this.setScrollPos(this._initialScroll);
- }
- }}
- />
- </div>
- );
- }
-
- // Fall back to a placeholder if no screenshot yet
+ 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');
+ // if (!scripts) console.log('No scripts for: ' + url);
return (
- <div className="webBox-placeholder">
- <div>Preparing webpage preview...</div>
- </div>
+ <iframe
+ title="web iframe"
+ key={this._warning}
+ className="webBox-iframe"
+ ref={action((r: HTMLIFrameElement | null) => {
+ this._iframe = r;
+ })}
+ style={{ pointerEvents: SnappingManager.IsResizing ? 'none' : undefined }}
+ src={url}
+ onLoad={this.iframeLoaded}
+ scrolling="no" // ugh.. on windows, I get an inner scroll bar for the iframe's body even though the scrollHeight should be set to the full height of the document.
+ // the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page
+ // sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />;
+ sandbox={`${scripts ? 'allow-scripts' : ''} allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin`}
+ />
);
}
-
- // Default placeholder
return (
- <div className="webBox-placeholder">
- <div>No content to display</div>
- </div>
+ <iframe
+ title="web frame"
+ className="webBox-iframe"
+ ref={action((r: HTMLIFrameElement | null) => {
+ this._iframe = r;
+ })}
+ src="https://crossorigin.me/https://cs.brown.edu"
+ />
);
}
@@ -1111,18 +1196,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
childPointerEvents = () => (this._props.isContentActive() ? 'all' : undefined);
@computed get webpage() {
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 | undefined);
-
+ // const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
return (
<div
className="webBox-outerContent"
ref={this._outerRef}
style={{
- width: '100%',
- height: `${containerWidth}px`,
- overflowY: 'auto',
- overflowX: 'hidden',
+ height: '100%', //`${100 / scale}%`,
pointerEvents,
}}
onWheel={this.onZoomWheel}
@@ -1234,8 +1316,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
<div
className="webBox-container"
style={{
- width: `calc(100% - ${this.SidebarShown ? this.sidebarWidth() : 0}px)`,
- height: '100%',
+ width: `calc(${100 / scale}% - ${!this.SidebarShown ? 0 : ((this.sidebarWidth() - WebBox.sidebarResizerWidth) / scale) * (this._previewWidth ? scale : 1)}px)`,
+ height: `${100 / scale}%`,
+ transform: `scale(${scale})`,
pointerEvents,
}}
onContextMenu={this.specificContextMenu}>
@@ -1276,7 +1359,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
{...this._props}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
fieldKey={this.fieldKey + '_' + this._urlHash}
- Document={this.Document}
+ Doc={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
setHeight={emptyFunction}