aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
authorusodhi <61431818+usodhi@users.noreply.github.com>2021-02-11 16:43:46 -0500
committerusodhi <61431818+usodhi@users.noreply.github.com>2021-02-11 16:43:46 -0500
commit546540013de0a7cb647f30f1fcb513ce52048b72 (patch)
tree12b78ea0e29fba23b8557864540984daf9680942 /src/client/views/nodes
parent77b7c3927c454a829d7dbb2748ad322b146265a7 (diff)
parent890337b525ea460f9986562c047135bc5ca203a6 (diff)
merging
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/AudioBox.tsx4
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx3
-rw-r--r--src/client/views/nodes/DocHolderBox.tsx4
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx23
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx24
-rw-r--r--src/client/views/nodes/DocumentView.tsx97
-rw-r--r--src/client/views/nodes/FilterBox.tsx4
-rw-r--r--src/client/views/nodes/ImageBox.tsx5
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx6
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx11
-rw-r--r--src/client/views/nodes/LinkDocPreview.scss72
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx247
-rw-r--r--src/client/views/nodes/PDFBox.tsx6
-rw-r--r--src/client/views/nodes/PresBox.tsx2
-rw-r--r--src/client/views/nodes/VideoBox.scss5
-rw-r--r--src/client/views/nodes/VideoBox.tsx10
-rw-r--r--src/client/views/nodes/WebBox.scss2
-rw-r--r--src/client/views/nodes/WebBox.tsx262
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx4
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx292
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.scss84
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx327
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx24
-rw-r--r--src/client/views/nodes/formattedText/RichTextSchema.tsx1
-rw-r--r--src/client/views/nodes/formattedText/TooltipTextMenu.scss2
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts21
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts4
-rw-r--r--src/client/views/nodes/formattedText/prosemirrorPatches.js18
29 files changed, 717 insertions, 849 deletions
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 692eaae66..e24a671d0 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -107,7 +107,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
//this._disposers.scrubbing = reaction(() => AudioBox._scrubTime, (time) => this.layoutDoc.playOnSelect && this.playFromTime(AudioBox._scrubTime));
this._disposers.triggerAudio = reaction(
- () => !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerAudio, null) : undefined,
+ () => !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerAudio, null) : undefined,
start => start !== undefined && setTimeout(() => {
this.playFrom(start);
setTimeout(() => {
@@ -119,7 +119,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
);
this._disposers.audioStop = reaction(
- () => this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc ? Cast(this.Document._audioStop, "number", null) : undefined,
+ () => this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo ? Cast(this.Document._audioStop, "number", null) : undefined,
audioStop => audioStop !== undefined && setTimeout(() => {
this.Pause();
setTimeout(() => this.Document._audioStop = undefined, 10);
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 09d89170c..4b0422ed3 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -23,7 +23,7 @@ import React = require("react");
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined;
- layerProvider?: (doc: Doc, assign?: boolean) => boolean;
+ layerProvider: ((doc: Doc, assign?: boolean) => boolean) | undefined;
zIndex?: number;
highlight?: boolean;
jitterRotation: number;
@@ -162,6 +162,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
...this.props,
CollectionFreeFormDocumentView: this.returnThis,
styleProvider: this.styleProvider,
+ layerProvider: this.props.layerProvider,
ScreenToLocalTransform: this.screenToLocalTransform,
PanelWidth: this.panelWidth,
PanelHeight: this.panelHeight,
diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx
index 533a4931a..765751a65 100644
--- a/src/client/views/nodes/DocHolderBox.tsx
+++ b/src/client/views/nodes/DocHolderBox.tsx
@@ -124,6 +124,8 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
ContainingCollectionView={this as any} // bcz: hack! need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked. currently, the up selector allows only a containing collection to be selected
ContainingCollectionDoc={undefined}
styleProvider={this.props.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={this.props.docViewPath}
LayoutTemplateString={layoutTemplate}
LayoutTemplate={this.layoutTemplateDoc}
rootSelected={this.props.isSelected}
@@ -150,6 +152,8 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
ContainingCollectionView={this as any} // bcz: hack! need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked. currently, the up selector allows only a containing collection to be selected
ContainingCollectionDoc={undefined}
styleProvider={this.props.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={this.props.docViewPath}
ignoreAutoHeight={true}
LayoutTemplateString={layoutTemplate}
LayoutTemplate={this.layoutTemplateDoc}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f969bee85..4b4720d58 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -111,7 +111,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
scaling?: () => number,
layoutKey: string,
hideOnLeave?: boolean,
- makeLink?: () => Opt<Doc>, // function to call when a link is made
}> {
@computed get layout(): string {
TraceMobx();
@@ -141,16 +140,17 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
const docOnlyProps = [ // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
"freezeDimensions",
+ "hideResizeHandles",
"hideTitle",
"treeViewDoc",
- "dragDivName",
"contentPointerEvents",
"radialMenu",
"LayoutTemplateString",
"LayoutTemplate",
+ "dontCenter",
"ContentScaling",
- "contentFittingScaling",
"contextMenuItems",
+ "onClick",
"onDoubleClick",
"onPointerDown",
"onPointerUp",
@@ -166,7 +166,11 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
return { props: list };
}
- render() {
+ componentWillUpdate(oldProps: any, newState: any) {
+ // console.log("willupdate", oldProps, this.props); // bcz: if you get a message saying something invalidated because reactive props changed, then this method allows you to figure out which prop changed
+ }
+
+ @computed get renderData() {
TraceMobx();
let layoutFrame = this.layout;
@@ -201,13 +205,18 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
};
const onClick = makeFuncProp("onClick");
const onInput = makeFuncProp("onInput");
-
const bindings = this.CreateBindings(onClick, onInput);
- // layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
+ return { bindings, layoutFrame };
+ }
+
+ render() {
+ TraceMobx();
+ const { bindings, layoutFrame } = this.renderData;
+
return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) :
<ObserverJsxParser
key={42}
- blacklistedAttrs={[]}
+ blacklistedAttrs={emptyPath}
renderInWrapper={false}
components={{
FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView,
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 2cac2d0b8..13a6c9df8 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -33,16 +33,16 @@ interface DocumentLinksButtonProps {
@observer
export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
private _linkButton = React.createRef<HTMLDivElement>();
-
@observable public static StartLink: Doc | undefined;
@observable public static StartLinkView: DocumentView | undefined;
@observable public static AnnotationId: string | undefined;
@observable public static AnnotationUri: string | undefined;
- @observable public static EditLink: DocumentView | undefined;
+ @observable public static LinkEditorDocView: DocumentView | undefined;
@observable public static invisibleWebDoc: Opt<Doc>;
public static invisibleWebRef = React.createRef<HTMLDivElement>();
+ @action public static ClearLinkEditor() { DocumentLinksButton.LinkEditorDocView = undefined; }
@action @undoBatch
onLinkButtonMoved = (e: PointerEvent) => {
if (this.props.InMenu && this.props.StartLink) {
@@ -58,8 +58,6 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
// however, the dropped document isn't so accessible. What we do is set the newly created link document on the documentView
// The documentView passes a function prop returning this link doc to its descendants who can react to changes to it.
dropEv.linkDragData?.linkDropCallback?.(dropEv as { linkDocument: Doc }); // bcz: typescript can't figure out that this is valid even though we tested dropEv.linkDocument above
- runInAction(() => this.props.View.LinkBeingCreated = dropEv.linkDocument);
- setTimeout(action(() => this.props.View.LinkBeingCreated = undefined), 0);
}
linkDrag?.end();
},
@@ -85,7 +83,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
DocumentLinksButton.StartLinkView = this.props.View;
}
} else if (!this.props.InMenu) {
- DocumentLinksButton.EditLink = this.props.View;
+ DocumentLinksButton.LinkEditorDocView = this.props.View;
}
}));
}
@@ -105,7 +103,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
//action(() => Doc.BrushDoc(this.props.View.Document));
} else if (!this.props.InMenu) {
- DocumentLinksButton.EditLink = this.props.View;
+ DocumentLinksButton.LinkEditorDocView = this.props.View;
}
}
@@ -118,7 +116,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
DocumentLinksButton.AnnotationId = undefined;
} else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
const sourceDoc = DocumentLinksButton.StartLink;
- const targetDoc = this.props.View.props.Document;
+ const targetDoc = this.props.View.ComponentView?.getAnchor?.() || this.props.View.Document;
const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, "long drag");
LinkManager.currentLink = linkDoc;
@@ -163,15 +161,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
endLink = endLinkView?.docView?._componentView?.getAnchor?.() || endLink;
startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.() || startLink;
const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag", undefined, undefined, true);
- // this notifies any of the subviews that a document is made so that they can make finer-grained hyperlinks (). see note above in onLInkButtonMoved
- if (endLinkView) {
- endLinkView.LinkBeingCreated = linkDoc;
- DocumentLinksButton.StartLinkView && (DocumentLinksButton.StartLinkView.LinkBeingCreated = linkDoc);
- setTimeout(action(() => {
- DocumentLinksButton.StartLinkView && (DocumentLinksButton.StartLinkView.LinkBeingCreated = undefined);
- endLinkView.LinkBeingCreated = undefined;
- }), 0);
- }
+
LinkManager.currentLink = linkDoc;
if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) { // if linking from a Hypothes.is annotation
@@ -274,7 +264,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
{this.linkButtonInner}
</Tooltip>
:
- !DocumentLinksButton.EditLink && !this.props.InMenu ?
+ !DocumentLinksButton.LinkEditorDocView && !this.props.InMenu ?
<Tooltip title={<><div className="dash-tooltip">{title}</div></>}>
{this.linkButtonInner}
</Tooltip>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b653b35bc..2c418e499 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc";
+import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, HeightSym } from "../../../fields/Doc";
import { Document } from '../../../fields/documentSchemas';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
@@ -43,10 +43,19 @@ import React = require("react");
import { List } from '../../../fields/List';
import { Tooltip } from '@material-ui/core';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { LinkDocPreview } from "./LinkDocPreview";
-export type DocAfterFocusFunc = (notFocused: boolean) => boolean;
-export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => void;
+export type DocAfterFocusFunc = (notFocused: boolean) => Promise<boolean>;
+export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => void;
export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any;
+export interface DocComponentView {
+ getAnchor: () => Doc;
+ scrollFocus?: (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => void;
+ back?: () => boolean;
+ forward?: () => boolean;
+ url?: () => string;
+ submitURL?: (url: string) => boolean;
+}
export interface DocumentViewSharedProps {
renderDepth: number;
Document: Doc;
@@ -54,12 +63,13 @@ export interface DocumentViewSharedProps {
fitContentsToDoc?: boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document
ContainingCollectionView: Opt<CollectionView>;
ContainingCollectionDoc: Opt<Doc>;
- setContentView?: (view: { getAnchor: () => Doc }) => any;
+ setContentView?: (view: DocComponentView) => any;
CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
PanelWidth: () => number;
PanelHeight: () => number;
- layerProvider?: (doc: Doc, assign?: boolean) => boolean;
- styleProvider?: StyleProviderFunc;
+ docViewPath: () => DocumentView[];
+ layerProvider: undefined | ((doc: Doc, assign?: boolean) => boolean);
+ styleProvider: Opt<StyleProviderFunc>;
focus: DocFocusFunc;
docFilters: () => string[];
docRangeFilters: () => string[];
@@ -108,7 +118,8 @@ export interface DocumentViewInternalProps extends DocumentViewProps {
NativeHeight: () => number;
isSelected: (outsideReaction?: boolean) => boolean;
select: (ctrlPressed: boolean) => void;
- DocumentView: any;
+ DocumentView: () => DocumentView;
+ viewPath: () => DocumentView[];
}
@observer
@@ -125,6 +136,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
private _timeout: NodeJS.Timeout | undefined;
private _dropDisposer?: DragManager.DragDropDisposer;
private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ _componentView: Opt<DocComponentView>;
protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
private get topMost() { return this.props.renderDepth === 0; }
@@ -144,7 +156,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@computed get docContents() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents); }
@computed get headerMargin() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; }
@computed get pointerEvents() { return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.props.isSelected() ? ":selected" : "")); }
- @computed get finalLayoutKey() { return StrCast(this.props.Document.layoutKey, "layout"); }
+ @computed get finalLayoutKey() { return StrCast(this.Document.layoutKey, "layout"); }
@computed get nativeWidth() { return this.props.NativeWidth(); }
@computed get nativeHeight() { return this.props.NativeHeight(); }
@computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); }
@@ -336,7 +348,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
startDragging(x: number, y: number, dropAction: dropActionType) {
if (this._mainCont.current) {
const ffview = this.props.CollectionFreeFormDocumentView;
- ffview && runInAction(() => (ffview().props.CollectionFreeFormView.ChildDrag = this.props.DocumentView));
+ ffview && runInAction(() => (ffview().props.CollectionFreeFormView.ChildDrag = this.props.DocumentView()));
const dragData = new DragManager.DocumentDragData([this.props.Document]);
const [left, top] = this.props.ScreenToLocalTransform().scale(this.ContentScale).inverse().transformPoint(0, 0);
dragData.offset = this.props.ScreenToLocalTransform().scale(this.ContentScale).transformDirection(x - left, y - top);
@@ -366,6 +378,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
}
+ focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
+ if (this._componentView?.scrollFocus) {
+ return this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo, afterFocus); // bcz: smooth parameter should really be passed into focus() instead of inferred here
+ }
+ return this.props.focus(doc, willZoom, scale, afterFocus, docTransform);
+ }
onClick = action((e: React.MouseEvent | React.PointerEvent) => {
if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 &&
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
@@ -384,7 +402,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
self: this.rootDoc,
scriptContext: this.props.scriptContext,
thisContainer: this.props.ContainingCollectionDoc,
- documentView: this.props.DocumentView,
+ documentView: this.props.DocumentView(),
clientX: e.clientX,
clientY: e.clientY,
shiftKey: e.shiftKey
@@ -394,7 +412,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (this.props.Document.type !== DocumentType.LABEL) {
UndoManager.RunInBatch(() => {
const fullScreenDoc = Cast(this.props.Document._fullScreenView, Doc, null) || this.props.Document;
- this.props.addDocTab(fullScreenDoc, "add");
+ this.props.addDocTab(fullScreenDoc, "lightbox");
}, "double tap");
SelectionManager.DeselectAll();
}
@@ -410,7 +428,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
self: this.rootDoc,
scriptContext: this.props.scriptContext,
thisContainer: this.props.ContainingCollectionDoc,
- documentView: this.props.DocumentView,
+ documentView: this.props.DocumentView(),
clientX: clientX,
clientY: clientY,
shiftKey
@@ -447,7 +465,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
e.stopPropagation();
- if (SelectionManager.IsSelected(this.props.DocumentView, true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
+ if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
// TODO: check here for panning/inking
}
return;
@@ -462,7 +480,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
(e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
!CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
e.stopPropagation();
- if (SelectionManager.IsSelected(this.props.DocumentView, true) && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
+ if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
}
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -585,7 +603,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (e && this.rootDoc._hideContextMenu && Doc.UserDoc().noviceMode) {
e.preventDefault();
e.stopPropagation();
- !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView, false);
+ !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
}
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
if (e) {
@@ -664,7 +682,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const more = cm.findByDescription("More...");
const moreItems = more && "subitems" in more ? more.subitems : [];
if (!Doc.IsSystem(this.rootDoc)) {
- (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView), icon: "users" });
+ (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" });
if (!Doc.UserDoc().noviceMode) {
moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
@@ -694,7 +712,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
- !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView, false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
+ !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView(), false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
}
rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
@@ -703,11 +721,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
contentScaling = () => this.ContentScale;
onClickFunc = () => this.onClickHandler;
- makeLink = () => this.props.DocumentView.LinkBeingCreated; // pass the link placeholde to child views so they can react to make a specialized anchor. This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
- setContentView = (view: { getAnchor: () => Doc }) => this._componentView = view;
+ setContentView = (view: { getAnchor: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view;
@observable contentsActive: () => boolean = returnFalse;
@action setContentsActive = (setActive: () => boolean) => this.contentsActive = setActive;
- _componentView: { getAnchor: () => Doc } | undefined;
@computed get contents() {
TraceMobx();
return <div className="documentView-contentsView"
@@ -716,29 +732,20 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
}}>
<DocumentContentsView key={1} {...this.props}
+ docViewPath={this.props.viewPath}
setContentView={this.setContentView}
scaling={this.contentScaling}
PanelHeight={this.panelHeight}
contentsActive={this.setContentsActive}
parentActive={this.parentActive}
ScreenToLocalTransform={this.screenToLocal}
- makeLink={this.makeLink}
rootSelected={this.rootSelected}
onClick={this.onClickFunc}
+ focus={this.focus}
layoutKey={this.finalLayoutKey} />
{this.layoutDoc.hideAllLinks ? (null) : this.allAnchors}
{this.hideLinkButton ? (null) :
- <DocumentLinksButton View={this.props.DocumentView} links={this.allLinks} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />}
- {!this.props.Document.numUsersShared && !this.props.Document.numGroupsShared ? (null) :
- <Tooltip title={<> <div className="dash-tooltip">Tap to open sharing menu</div></>}>
- <div className="sharingIndicator"
- onPointerDown={() => SharingManager.Instance.open(undefined, this.props.Document)}
- style={{ backgroundColor: GetEffectiveAcl(this.props.Document[DataSym]) === AclAdmin ? "#9dca96" : "lightgrey" }}
- >
- <FontAwesomeIcon size="lg" icon={this.indicatorIcon} />
- </div>
- </Tooltip >
- }
+ <DocumentLinksButton View={this.props.DocumentView()} links={this.allLinks} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />}
</div>;
}
@@ -762,18 +769,19 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
if (this.layoutDoc.presBox || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return (null);
- const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden);
- return filtered.map((d, i) =>
+ // need to use allLinks for RTF since embedded linked text anchors are not rendered with DocumentViews. All other documents render their anchors with nested DocumentViews so we just need to render the directLinks here
+ const filtered = DocUtils.FilterDocs(this.rootDoc.type === DocumentType.RTF ? this.allLinks : this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden);
+ return filtered.map((link, i) =>
<div className="documentView-anchorCont" key={i + 1}>
<DocumentView {...this.props}
- Document={d}
+ Document={link}
PanelWidth={this.anchorPanelWidth}
PanelHeight={this.anchorPanelHeight}
dontRegisterView={false}
styleProvider={this.anchorStyleProvider}
removeDocument={this.hideLinkAnchor}
LayoutTemplate={undefined}
- LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(d, this.props.Document)}`)} />
+ LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(link, this.rootDoc)}`)} />
</div >);
}
@@ -784,7 +792,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const showTitleHover = StrCast(this.layoutDoc._showTitleHover);
const showCaption = StrCast(this.layoutDoc._showCaption);
const captionView = !showCaption ? (null) :
- <div className="documentView-captionWrapper" style={{ backgroundColor: StrCast(this.layoutDoc["caption-backgroundColor"]), color: StrCast(this.layoutDoc["caption-color"]) }}>
+ <div className="documentView-captionWrapper"
+ style={{
+ backgroundColor: StrCast(this.layoutDoc["caption-backgroundColor"]),
+ color: StrCast(this.layoutDoc["caption-color"])
+ }}>
<DocumentContentsView {...OmitKeys(this.props, ['children']).omit}
yMargin={10}
xMargin={10}
@@ -874,7 +886,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
public ContentRef = React.createRef<HTMLDivElement>();
- @observable LinkBeingCreated: Opt<Doc>; // see DocumentLinksButton for explanation of how this works
@observable public docView: DocumentViewInternal | undefined | null;
get Document() { return this.props.Document; }
@@ -883,9 +894,11 @@ export class DocumentView extends React.Component<DocumentViewProps> {
get dataDoc() { return this.docView?.dataDoc || this.Document; }
get finalLayoutKey() { return this.docView?.finalLayoutKey || "layout"; }
get ContentDiv() { return this.docView?.ContentDiv; }
+ get ComponentView() { return this.docView?._componentView; }
get allLinks() { return this.docView?.allLinks || []; }
get LayoutFieldKey() { return this.docView?.LayoutFieldKey || "layout"; }
+ @computed get docViewPath() { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; }
@computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); }
@computed get nativeWidth() { return returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); }
@computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions) || 0); }
@@ -914,6 +927,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight());
contentsActive = () => this.docView?.contentsActive();
+ focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => {
+ return this.docView?.focus(doc, willZoom, scale, afterFocus);
+ }
getBounds = () => {
if (!this.docView || !this.docView.ContentDiv || this.docView.props.renderDepth === 0 || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
return undefined;
@@ -953,6 +969,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}), 400);
});
+ docViewPathFunc = () => this.docViewPath;
isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
select = (ctrlPressed: boolean) => SelectionManager.SelectView(this, ctrlPressed);
NativeWidth = () => this.nativeWidth;
@@ -960,6 +977,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
PanelWidth = () => this.panelWidth;
PanelHeight = () => this.panelHeight;
ContentScale = () => this.nativeScaling;
+ selfView = () => this;
screenToLocalTransform = () => {
return this.props.ScreenToLocalTransform().translate(-this.centeringX, -this.centeringY).scale(1 / this.nativeScaling);
}
@@ -975,7 +993,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
TraceMobx();
const internalProps = {
...this.props,
- DocumentView: this,
+ DocumentView: this.selfView,
+ viewPath: this.docViewPathFunc,
PanelWidth: this.PanelWidth,
PanelHeight: this.PanelHeight,
NativeWidth: this.NativeWidth,
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index dfa81d3bb..87e49df61 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -374,7 +374,9 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
treeViewHideHeaderFields={true}
onCheckedClick={this.scriptField}
dontRegisterView={true}
- styleProvider={this.FilteringStyleProvider}
+ styleProvider={this.props.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={this.props.docViewPath}
scriptContext={this.props.scriptContext}
moveDocument={returnFalse}
removeDocument={returnFalse}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 92d6e2612..286b3bf5f 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -222,7 +222,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return url.href; //Why is this here
const ext = path.extname(url.href);
- this._curSuffix = this.props.renderDepth < 1 ? "_o" : this.props.PanelWidth() < 100 ? "_s" : "_m";
+ const scrSize = this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight);
+ this._curSuffix = this.props.renderDepth < 1 ? "_o" : scrSize[0] < 100 ? "_s" : scrSize[0] < 400 || !this.props.isSelected() ? "_m" : "_o";
return url.href.replace(ext, this._curSuffix + ext);
}
@@ -466,7 +467,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
</CollectionFreeFormView>
{this.annotationLayer}
{!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
- <MarqueeAnnotator rootDoc={this.rootDoc} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
+ <MarqueeAnnotator rootDoc={this.rootDoc} scrollTop={0} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
</div >);
}
}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 3c10cc5fe..ce9d8bed5 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,7 +1,7 @@
import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import { Doc, Field, Opt } from '../../../fields/Doc';
-import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter, returnEmptyDoclist } from '../../../Utils';
+import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter, returnEmptyDoclist, emptyPath } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
@@ -12,6 +12,7 @@ import { KeyValueBox } from './KeyValueBox';
import "./KeyValueBox.scss";
import "./KeyValuePair.scss";
import React = require("react");
+import { DefaultStyleProvider } from '../StyleProvider';
// Represents one row in a key value plane
@@ -58,6 +59,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
docFilters: returnEmptyFilter,
docRangeFilters: returnEmptyFilter,
searchFilterDocs: returnEmptyDoclist,
+ styleProvider: DefaultStyleProvider,
+ layerProvider: undefined,
+ docViewPath: returnEmptyDoclist,
ContainingCollectionView: undefined,
ContainingCollectionDoc: undefined,
fieldKey: this.props.keyName,
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index d86dfd7b1..db5414069 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -134,13 +134,14 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
</div>}
</div>
);
- return <div className={`linkAnchorBox-cont${small ? "-small" : ""} ${this.rootDoc[Id]}`}
- onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
- onPointerEnter={action(e => LinkDocPreview.LinkInfo = {
- docprops: this.props,
+ return <div className={`linkAnchorBox-cont${small ? "-small" : ""}`}
+ onPointerLeave={LinkDocPreview.Clear}
+ onPointerEnter={e => LinkDocPreview.SetLinkInfo({
+ docProps: this.props,
linkSrc: linkSource,
linkDoc: this.rootDoc,
- Location: [e.clientX, e.clientY + 20]
+ showHeader: true,
+ location: [e.clientX, e.clientY + 20]
})}
onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu}
ref={this._ref}
diff --git a/src/client/views/nodes/LinkDocPreview.scss b/src/client/views/nodes/LinkDocPreview.scss
new file mode 100644
index 000000000..b7aeaa072
--- /dev/null
+++ b/src/client/views/nodes/LinkDocPreview.scss
@@ -0,0 +1,72 @@
+ .linkDocPreview {
+ position: absolute;
+ pointer-events: all;
+ background-color: lightblue;
+ border: 8px solid white;
+ border-radius: 7px;
+ box-shadow: 3px 3px 1.5px grey;
+ border-bottom: 8px solid white;
+ border-right: 8px solid white;
+ z-index: 2004;
+ .linkDocPreview-inner {
+ background-color: white;
+ border: 8px solid white;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+
+ .linkDocPreview-info {
+ height: 37px;
+ white-space: pre;
+
+ .linkDocPreview-buttonBar {
+ float: right;
+ }
+ .linkDocPreview-title {
+ padding-right: 4px;
+ float: left;
+ width: calc(100% - 48px);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ height: 25px;
+
+ .linkDocPreview-description {
+ text-decoration: none;
+ font-style: italic;
+ color: rgb(95, 97, 102);
+ font-size: 10px;
+ }
+ }
+
+ .linkDocPreview-button {
+ display: inline-flex;
+ margin: 0;
+ margin-right: 3px;
+ border-radius: 50%;
+ pointer-events: auto;
+ background-color: black;
+ color: white;
+ transition: transform 0.2s;
+ text-align: center;
+ position: relative;
+ font-size: 12px;
+ width: 20px;
+ height: 20px;
+ align-items: center;
+ justify-content: center;
+
+ &:hover {
+ background-color: rgb(77, 77, 77);
+ cursor: pointer;
+ }
+ }
+
+ .linkDocPreview-preview-wrapper {
+ overflow: hidden;
+ align-content: center;
+ justify-content: center;
+ background-color: rgb(160, 160, 160);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 7934dba8e..6155ed493 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -1,131 +1,180 @@
-import { action, computed, observable, runInAction } from 'mobx';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, computed, observable } from 'mobx';
import { observer } from "mobx-react";
import wiki from "wikijs";
-import { Doc, DocCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
-import { Id } from '../../../fields/FieldSymbols';
-import { Cast, FieldValue, NumCast } from "../../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse } from "../../../Utils";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { NumCast, StrCast } from "../../../fields/Types";
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, setupMoveUpEvents, Utils } from "../../../Utils";
+import { DocServer } from '../../DocServer';
import { Docs } from "../../documents/Documents";
import { LinkManager } from '../../util/LinkManager';
import { Transform } from "../../util/Transform";
-import { ContextMenu } from '../ContextMenu';
-import { DocumentLinksButton } from './DocumentLinksButton';
-import { DocumentView, StyleProviderFunc, DocumentViewSharedProps } from "./DocumentView";
+import { undoBatch } from '../../util/UndoManager';
+import { DocumentView, DocumentViewSharedProps } from "./DocumentView";
+import './LinkDocPreview.scss';
import React = require("react");
-interface Props {
+interface LinkDocPreviewProps {
linkDoc?: Doc;
linkSrc?: Doc;
- href?: string;
- docprops: DocumentViewSharedProps;
+ docProps: DocumentViewSharedProps;
location: number[];
+ hrefs?: string[];
+ showHeader?: boolean;
}
@observer
-export class LinkDocPreview extends React.Component<Props> {
- static TargetDoc: Doc | undefined;
- @observable public static LinkInfo: Opt<{ linkDoc?: Doc; linkSrc: Doc; href?: string; Location: number[], docprops: DocumentViewSharedProps }>;
+export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
+ @action public static Clear() { LinkDocPreview.LinkInfo = undefined; }
+ @action public static SetLinkInfo(info?: LinkDocPreviewProps) { LinkDocPreview.LinkInfo != info && (LinkDocPreview.LinkInfo = info); }
+
+ _infoRef = React.createRef<HTMLDivElement>();
+ @observable public static LinkInfo: Opt<LinkDocPreviewProps>;
@observable _targetDoc: Opt<Doc>;
+ @observable _linkDoc: Opt<Doc>;
+ @observable _linkSrc: Opt<Doc>;
@observable _toolTipText = "";
- _editRef = React.createRef<HTMLDivElement>();
+ @observable _hrefInd = 0;
- @action
- onContextMenu = (e: React.MouseEvent) => {
- DocumentLinksButton.EditLink = undefined;
- LinkDocPreview.LinkInfo = undefined;
- e.preventDefault();
- ContextMenu.Instance.addItem({ description: "Follow Default Link", event: () => this.followDefault(), icon: "arrow-right" });
- ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
+ @action init() {
+ var linkTarget = this.props.linkDoc;
+ this._linkSrc = this.props.linkSrc;
+ this._linkDoc = this.props.linkDoc;
+ const anchor1 = this._linkDoc?.anchor1 as Doc;
+ const anchor2 = this._linkDoc?.anchor2 as Doc;
+ if (anchor1 && anchor2) {
+ linkTarget = Doc.AreProtosEqual(anchor1, this._linkSrc) || Doc.AreProtosEqual(anchor1?.annotationOn as Doc, this._linkSrc) ? anchor2 : anchor1;
+ }
+ this._targetDoc = linkTarget?.annotationOn as Doc ?? linkTarget;
+ this._toolTipText = "";
+ }
+ componentDidUpdate(props: any) {
+ if (props.linkSrc !== this.props.linkSrc || props.linkDoc !== this.props.linkDoc || props.hrefs !== this.props.hrefs) this.init();
+ }
+ componentDidMount() {
+ this.init();
+ document.addEventListener("pointerdown", this.onPointerDown);
}
- @action.bound
- async followDefault() {
- DocumentLinksButton.EditLink = undefined;
- LinkDocPreview.LinkInfo = undefined;
- this._targetDoc && LinkManager.FollowLink(this.props.linkDoc, this._targetDoc, this.props.docprops, false);
+ componentWillUnmount() {
+ LinkDocPreview.SetLinkInfo(undefined);
+ document.removeEventListener("pointerdown", this.onPointerDown);
}
- componentWillUnmount() { LinkDocPreview.TargetDoc = undefined; }
- componentDidUpdate() { this.updatePreview(); }
- componentDidMount() { this.updatePreview(); }
- async updatePreview() {
- const linkDoc = this.props.linkDoc;
- const linkSrc = this.props.linkSrc;
- LinkDocPreview.TargetDoc = undefined;
- if (this.props.href) {
- if (this.props.href.startsWith("https://en.wikipedia.org/wiki/")) {
- wiki().page(this.props.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500))));
- } else {
- runInAction(() => this._toolTipText = "external => " + this.props.href);
- }
- } else if (linkDoc && linkSrc) {
- const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), linkSrc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
- const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
- runInAction(() => {
- this._toolTipText = "";
- LinkDocPreview.TargetDoc = this._targetDoc = target;
- if (this._targetDoc) {
- this._targetDoc._scrollToPreviewLinkID = linkDoc?.[Id];
- if (anchor !== this._targetDoc && anchor) {
- this._targetDoc._scrollPreviewY = NumCast(anchor?.y);
- }
+ onPointerDown = (e: PointerEvent) => {
+ !this._infoRef.current?.contains(e.target as any) && LinkDocPreview.Clear(); // close preview when not clicking anywhere other than the info bar of the preview
+ }
+
+ @computed get href() {
+ if (this.props.hrefs?.length) {
+ const href = this.props.hrefs[this._hrefInd];
+ if (href.indexOf(Utils.prepend("/doc/")) !== 0) { // link to a web page URL -- try to show a preview
+ if (href.startsWith("https://en.wikipedia.org/wiki/")) {
+ wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500))));
+ } else {
+ setTimeout(action(() => this._toolTipText = "url => " + href));
}
- });
+ } else { // hyperlink to a document .. decode doc id and retrieve from the server. this will trigger vals() being invalidated
+ const anchorDoc = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => {
+ if (anchor instanceof Doc && DocListCast(anchor.links).length) {
+ this._linkDoc = DocListCast(anchor.links)[0];
+ this._linkSrc = anchor;
+ const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
+ this._targetDoc = linkTarget?.annotationOn as Doc ?? linkTarget;
+ this._toolTipText = "";
+ }
+ }));
+ }
+ return href;
}
+ return undefined;
}
- pointerDown = (e: React.PointerEvent) => {
- if (this.props.linkDoc && this.props.linkSrc) {
- LinkManager.FollowLink(this.props.linkDoc, this.props.linkSrc, this.props.docprops, false);
- } else if (this.props.href) {
- this.props.docprops?.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, useCors: true }), "add:right");
+ deleteLink = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(() => this._linkDoc && LinkManager.Instance.deleteLink(this._linkDoc)));
+ }
+ nextHref = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1)));
+ }
+
+ followLink = (e: React.PointerEvent) => {
+ if (this._linkDoc && this._linkSrc) {
+ LinkManager.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false);
+ } else if (this.props.hrefs?.length) {
+ this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _width: 200, _height: 400, useCors: true }), "add:right");
}
}
width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225));
height = () => Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225));
- @computed get targetDocView() {
- return !this._targetDoc ?
- <div style={{ pointerEvents: "all", maxWidth: 225, maxHeight: 225, width: "100%", height: "100%", overflow: "hidden" }}>
- <div style={{ width: "100%", height: "100%", textOverflow: "ellipsis", }} onPointerDown={this.pointerDown}>
- {this._toolTipText}
+ @computed get previewHeader() {
+ return !this._linkDoc || !this._targetDoc || !this._linkSrc ? (null) :
+ <div className="linkDocPreview-info" ref={this._infoRef}>
+ <div className="linkDocPreview-title">
+ {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + "..." : this._targetDoc.title}
+ <p className="linkDocPreview-description"> {StrCast(this._linkDoc.description)}</p>
+ </div>
+ <div className="linkDocPreview-buttonBar" >
+ <Tooltip title={<div className="dash-tooltip">Next Link</div>} placement="top">
+ <div className="linkDocPreview-button" style={{ background: (this.props.hrefs?.length || 0) <= 1 ? "gray" : "green" }} onPointerDown={this.nextHref}>
+ <FontAwesomeIcon className="linkDocPreview-fa-icon" icon="chevron-right" color="white" size="sm" />
+ </div>
+ </Tooltip>
+
+ <Tooltip title={<div className="dash-tooltip">Delete Link</div>} placement="top">
+ <div className="linkDocPreview-button" onPointerDown={this.deleteLink}>
+ <FontAwesomeIcon className="linkDocPreview-fa-icon" icon="trash" color="white" size="sm" />
+ </div>
+ </Tooltip>
</div>
- </div>
- :
- <DocumentView
- Document={this._targetDoc}
- moveDocument={returnFalse}
- rootSelected={returnFalse}
- ScreenToLocalTransform={Transform.Identity}
- parentActive={returnFalse}
- addDocument={returnFalse}
- removeDocument={returnFalse}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- dontRegisterView={true}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionDoc={undefined}
- ContainingCollectionView={undefined}
- renderDepth={-1}
- PanelWidth={this.width}
- PanelHeight={this.height}
- focus={emptyFunction}
- whenActiveChanged={returnFalse}
- bringToFront={returnFalse}
- styleProvider={this.props.docprops?.styleProvider} />;
+ </div>;
+ }
+
+ @computed get docPreview() {
+ const href = this.href; // needs to be here to trigger lookup of web pages and docs on server
+ return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? (null) :
+ <div className="linkDocPreview-inner">
+ {!this.props.showHeader ? (null) : this.previewHeader}
+ <div className="linkDocPreview-preview-wrapper">
+ {this._toolTipText ? this._toolTipText :
+ <DocumentView ref={(r) => {
+ const targetanchor = LinkManager.getOppositeAnchor(this._linkDoc!, this._linkSrc!);
+ targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor);
+ }}
+ Document={this._targetDoc!}
+ moveDocument={returnFalse}
+ rootSelected={returnFalse}
+ styleProvider={this.props.docProps?.styleProvider}
+ layerProvider={this.props.docProps?.layerProvider}
+ docViewPath={returnEmptyDoclist}
+ ScreenToLocalTransform={Transform.Identity}
+ parentActive={returnFalse}
+ addDocument={returnFalse}
+ removeDocument={returnFalse}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ dontRegisterView={true}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionDoc={undefined}
+ ContainingCollectionView={undefined}
+ renderDepth={-1}
+ PanelWidth={this.width}
+ PanelHeight={this.height}
+ focus={emptyFunction}
+ whenActiveChanged={returnFalse}
+ bringToFront={returnFalse}
+ NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined}
+ NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined}
+ />}
+ </div>
+ </div>;
}
render() {
- return <div className="linkDocPreview"
- style={{
- position: "absolute", left: this.props.location[0],
- top: this.props.location[1], width: this.width() + 16, height: this.height() + 16,
- zIndex: 1000,
- backgroundColor: "lightblue",
- border: "8px solid white", borderRadius: "7px",
- boxShadow: "3px 3px 1.5px grey",
- borderBottom: "8px solid white", borderRight: "8px solid white"
- }}>
- {this.targetDocView}
+ return <div className="linkDocPreview" onPointerDown={this.followLink}
+ style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + 16 }}>
+ {this.docPreview}
</div>;
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index ec9a75302..989be1ab9 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -22,6 +22,7 @@ import { FieldView, FieldViewProps } from './FieldView';
import { pageSchema } from "./ImageBox";
import "./PDFBox.scss";
import React = require("react");
+import { DocAfterFocusFunc } from './DocumentView';
type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
@@ -79,8 +80,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
}
}
+ scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => this._pdfViewer?.scrollFocus(doc, smooth, afterFocus);
+ getAnchor = () => this.rootDoc;
componentWillUnmount() { this._selectReactionDisposer?.(); }
componentDidMount() {
+ this.props.setContentView?.(this);
this._selectReactionDisposer = reaction(() => this.props.isSelected(),
() => {
document.removeEventListener("keydown", this.onKeyDown);
@@ -218,7 +222,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
static pdfpromise = new Map<string, Pdfjs.PDFPromise<Pdfjs.PDFDocumentProxy>>();
render() {
TraceMobx();
- if (true) {//this.props.isSelected() || (this.props.active() && this.props.renderDepth === 0) || this.props.Document._scrollY !== undefined) {
+ if (true) {//this.props.isSelected() || (this.props.active() && this.props.renderDepth === 0)) {
this._displayPdfLive = true;
}
if (this._displayPdfLive) {
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index b6feace12..589a1c2ae 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -364,7 +364,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
bestTarget && runInAction(() => {
if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) {
bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
- bestTarget._scrollY = activeItem.presPinViewScroll;
+ bestTarget._scrollTop = activeItem.presPinViewScroll;
} else if (bestTarget.type === DocumentType.COMPARISON) {
bestTarget._clipWidth = activeItem.presPinClipWidth;
} else if (bestTarget.type === DocumentType.VID) {
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index b9123587b..dd8d77603 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -13,6 +13,11 @@
.collectionStackedTimeline {
background: beige;
}
+ .videoBox-stackPanel {
+ z-index: -1;
+ width: 100%;
+ position: absolute;
+ }
}
.videoBox-content-YouTube, .videoBox-content-YouTube-fullScreen,
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 8a1cefbd9..c0247c226 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -70,7 +70,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
}
getAnchor = () => {
- return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey + "-timeline", "videoStart", "videoEnd", Cast(this.layoutDoc._currentTimecode, "number", null)) || this.rootDoc;
+ const timecode = Cast(this.layoutDoc._currentTimecode, "number", null);
+ return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey + "-timeline", "videoStart", "videoEnd", timecode ? timecode : undefined) || this.rootDoc;
}
choosePath(url: string) {
@@ -211,7 +212,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
},
{ fireImmediately: true });
this._disposers.triggerVideo = reaction(
- () => !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerVideo, null) : undefined,
+ () => !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerVideo, null) : undefined,
time => time !== undefined && setTimeout(() => {
this.player && this.Play();
setTimeout(() => this.Document._triggerVideo = undefined, 10);
@@ -219,7 +220,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
{ fireImmediately: true }
);
this._disposers.triggerStop = reaction(
- () => this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc ? NumCast(this.Document._triggerVideoStop, null) : undefined,
+ () => this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo ? NumCast(this.Document._triggerVideoStop, null) : undefined,
stop => stop !== undefined && setTimeout(() => {
this.player && this.Pause();
setTimeout(() => this.Document._triggerVideoStop = undefined, 10);
@@ -489,7 +490,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
setAnchorTime = (time: number) => this.player!.currentTime = this.layoutDoc._currentTimecode = time;
timelineHeight = () => this.props.PanelHeight() * (100 - this.heightPercent) / 100;
@computed get renderTimeline() {
- return <div style={{ width: "100%", transition: this.transition, height: `${100 - this.heightPercent}%`, position: "absolute" }}>
+ return <div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%` }}>
<CollectionStackedTimeline ref={this._stackedTimeline} {...this.props}
fieldKey={this.annotationKey}
renderDepth={this.props.renderDepth + 1}
@@ -574,6 +575,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
{this.renderTimeline}
{!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
<MarqueeAnnotator
+ scrollTop={0}
rootDoc={this.rootDoc}
down={this._marqueeing}
scaling={this.marqueeFitScaling}
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 5a7c1c904..ca6611a6b 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -96,7 +96,7 @@
}
.webBox-annotationToggle {
- z-index: 9001;
+ z-index: 901;
position: absolute;
top: 2;
left: 2;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 4b7f0bf77..ef0df25b6 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -24,12 +24,12 @@ import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
+import { MarqueeAnnotator } from "../MarqueeAnnotator";
import { Annotation } from "../pdf/Annotation";
-import { AnchorMenu } from "../pdf/AnchorMenu";
+import { DocAfterFocusFunc } from "./DocumentView";
import { FieldView, FieldViewProps } from './FieldView';
import "./WebBox.scss";
import React = require("react");
-import { MarqueeAnnotator } from "../MarqueeAnnotator";
const htmlToText = require("html-to-text");
type WebDocument = makeInterface<[typeof documentSchema]>;
@@ -37,25 +37,23 @@ const WebDocument = makeInterface(documentSchema);
@observer
export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
- private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
- private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
private _disposers: { [name: string]: IReactionDisposer } = {};
private _longPressSecondsHack?: NodeJS.Timeout;
private _outerRef = React.createRef<HTMLDivElement>();
+ private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _iframeIndicatorRef = React.createRef<HTMLDivElement>();
private _iframeDragRef = React.createRef<HTMLDivElement>();
+ private _ignoreScroll = false;
+ private _initialScroll: Opt<number>;
@observable private _marqueeing: number[] | undefined;
@observable private _url: string = "hello";
@observable private _pressX: number = 0;
@observable private _pressY: number = 0;
@observable private _iframe: HTMLIFrameElement | null = null;
@observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
-
get scrollHeight() { return this.webpage?.scrollHeight || 1000; }
- get _collapsed() { return StrCast(this.layoutDoc._chromeStatus) !== "enabled"; }
- set _collapsed(value) { this.layoutDoc._chromeStatus = !value ? "enabled" : "disabled"; }
get webpage() { return this._iframe?.contentDocument?.children[0]; }
constructor(props: any) {
@@ -64,11 +62,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || 850);
Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850);
}
+ this._annotationKey = this._annotationKey + "-" + this.urlHash(this._url);
}
- iframeLoaded = action((e: any) => {
+ iframeLoaded = (e: any) => {
const iframe = this._iframe;
if (iframe?.contentDocument) {
+ if (this._initialScroll !== undefined && this._outerRef.current && this.webpage) {
+ this.webpage.scrollTop = this._initialScroll;
+ this._outerRef.current.scrollTop = this._initialScroll;
+ this._initialScroll = undefined;
+ }
iframe.setAttribute("enable-annotation", "true");
iframe.contentDocument.addEventListener("click", undoBatch(action(e => {
let href = "";
@@ -76,86 +80,60 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
href = (typeof (ele.href) === "string" ? ele.href : ele.href?.baseVal) || ele.parentElement?.href || href;
}
if (href) {
- this._url = href.replace(Utils.prepend(""), Cast(this.dataDoc[this.fieldKey], WebField, null)?.url.origin);
- this.submitURL();
+ this.submitURL(href.replace(Utils.prepend(""), Cast(this.dataDoc[this.fieldKey], WebField, null)?.url.origin));
+ if (this.webpage) {
+ this.webpage.scrollTop = NumCast(this.layoutDoc._scrollTop);
+ this.webpage.scrollLeft = 0;
+ }
}
})));
iframe.contentDocument.addEventListener('wheel', this.iframeWheel, false);
- if (this.webpage) {
- this.webpage.scrollTop = NumCast(this.layoutDoc._scrollTop);
- this.webpage.scrollLeft = NumCast(this.layoutDoc._scrollLeft);
- }
}
- this._disposers.scrollReaction?.();
- this._disposers.scrollReaction = reaction(() => ({ scrollY: this.layoutDoc._scrollY, scrollX: this.layoutDoc._scrollX }),
- ({ scrollY, scrollX }) => {
- const delay = this._outerRef.current ? 0 : 250; // wait for mainCont and try again to scroll
- const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/);
- const duration = durationStr ? Number(durationStr[1]) : 1000;
- if (scrollY !== undefined) {
- this._forceSmoothScrollUpdate = true;
- this.layoutDoc._scrollY = undefined;
- setTimeout(() => this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollY || 0), () => this.layoutDoc._scrollTop = scrollY), delay);
- }
- if (scrollX !== undefined) {
- this._forceSmoothScrollUpdate = true;
- this.layoutDoc._scrollX = undefined;
- setTimeout(() => this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollX || 0), () => this.layoutDoc._scrollLeft = scrollX), delay);
- }
- },
- { fireImmediately: true }
- );
- this._disposers.scrollTop = reaction(() => this.layoutDoc._scrollTop,
- scrollTop => {
- const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/);
- const duration = durationStr ? Number(durationStr[1]) : 1000;
- if (scrollTop !== this._outerRef.current?.scrollTop && scrollTop !== undefined && this._forceSmoothScrollUpdate) {
- this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollTop || 0), () => this._forceSmoothScrollUpdate = true);
- } else this._forceSmoothScrollUpdate = true;
- },
- { fireImmediately: true }
- );
- });
- _forceSmoothScrollUpdate = true;
+ }
- updateScroll = (x: Opt<number>, y: Opt<number>) => {
- if (y !== undefined) {
- this._outerRef.current!.scrollTop = y;
- this.layoutDoc._scrollY = undefined;
- }
- if (x !== undefined) {
- this._outerRef.current!.scrollLeft = x;
- this.layoutDoc.scrollX = undefined;
+ @action
+ onWheelScroll = (scrollTop: number) => {
+ if (this.webpage && this._outerRef.current) {
+ this.webpage.scrollLeft = 0;
+ this._outerRef.current.scrollTop = scrollTop;
+ this._outerRef.current.scrollLeft = 0;
+ this._ignoreScroll = true;
+ if (this.layoutDoc._scrollTop !== scrollTop) {
+ this.layoutDoc._scrollTop = scrollTop;
+ }
+ this._ignoreScroll = false;
}
}
+ iframeWheel = (e: any) => this.webpage && e.target?.children && this.onWheelScroll(this.webpage.scrollTop);
+ onWheel = (e: React.WheelEvent) => {
+ this._outerRef.current && this.onWheelScroll(this._outerRef.current.scrollTop);
+ e.stopPropagation();
+ }
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
- iframeWheel = (e: any) => {
- if (this._forceSmoothScrollUpdate && e.target?.children) {
- this.webpage && setTimeout(action(() => {
- this.webpage!.scrollLeft = 0;
- const scrollTop = this.webpage!.scrollTop;
- const scrollLeft = this.webpage!.scrollLeft;
- this._outerRef.current!.scrollTop = scrollTop;
- this._outerRef.current!.scrollLeft = scrollLeft;
- if (this.layoutDoc._scrollTop !== scrollTop) {
- this._forceSmoothScrollUpdate = false;
- this.layoutDoc._scrollTop = scrollTop;
- }
- if (this.layoutDoc._scrollLeft !== scrollLeft) {
- this._forceSmoothScrollUpdate = false;
- this.layoutDoc._scrollLeft = scrollLeft;
- }
- }));
+ getAnchor = () => this.rootDoc;
+ scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => {
+ if (doc !== this.rootDoc && this.webpage && this._outerRef.current) {
+ const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1));
+ if (scrollTo !== undefined) {
+ this._initialScroll !== undefined && (this._initialScroll = scrollTo);
+ this._ignoreScroll = true;
+ this.goTo(scrollTo, smooth ? 500 : 0);
+ this.layoutDoc._scrollTop = scrollTo;
+ this._ignoreScroll = false;
+ return afterFocus?.(true);
+ }
+ } else {
+ this._initialScroll = NumCast(doc.y);
}
+ afterFocus?.(false);
}
+
async componentDidMount() {
+ this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
+
const urlField = Cast(this.dataDoc[this.props.fieldKey], WebField);
runInAction(() => this._url = urlField?.url.toString() || "");
- this._disposers.scrollMove = reaction(() => this.layoutDoc.x || this.layoutDoc.y,
- () => this.updateScroll(this.layoutDoc._scrollLeft, this.layoutDoc._scrollTop));
-
this._disposers.selection = reaction(() => this.props.isSelected(),
selected => {
if (!selected) {
@@ -185,6 +163,36 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this.dataDoc.text = htmlToText.fromString(result.content);
}
}
+
+ var quickScroll = true;
+ this._disposers.scrollReaction = reaction(() => NumCast(this.layoutDoc._scrollTop),
+ (scrollTop) => {
+ if (quickScroll) {
+ this._initialScroll = scrollTop;
+ }
+ else if (!this._ignoreScroll) {
+ const viewTrans = StrCast(this.Document._viewTransition);
+ const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
+ const durationSecStr = viewTrans.match(/([0-9.]*)s/);
+ const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
+ this.goTo(scrollTop, duration);
+ }
+ },
+ { fireImmediately: true }
+ );
+ quickScroll = false;
+ }
+
+ goTo = (scrollTop: number, duration: number) => {
+ if (this._outerRef.current && this.webpage) {
+ if (duration) {
+ smoothScroll(duration, this.webpage as any as HTMLElement, scrollTop);
+ smoothScroll(duration, this._outerRef.current, scrollTop);
+ } else {
+ this.webpage.scrollTop = scrollTop;
+ this._outerRef.current.scrollTop = scrollTop;
+ }
+ }
}
componentWillUnmount() {
@@ -194,31 +202,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this._iframe?.removeEventListener('wheel', this.iframeWheel);
}
- onUrlDragover = (e: React.DragEvent) => { e.preventDefault(); };
-
- @undoBatch
- @action
- onUrlDrop = (e: React.DragEvent) => {
- const { dataTransfer } = e;
- const html = dataTransfer.getData("text/html");
- const uri = dataTransfer.getData("text/uri-list");
- const url = uri || html || this._url;
- this._url = url.startsWith(window.location.origin) ?
- url.replace(window.location.origin, this._url.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url;
- this.submitURL();
- e.stopPropagation();
- }
-
@action
forward = () => {
const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null);
const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), null);
if (future.length) {
history.push(this._url);
- this.dataDoc[this.annotationKey + "-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(this.dataDoc[this.annotationKey]));
this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = future.pop()!));
- this.dataDoc[this.annotationKey] = new List<Doc>(DocListCast(this.dataDoc[this.annotationKey + "-" + this.urlHash(this._url)]));
+ this._annotationKey = this.fieldKey + "-annotations-" + this.urlHash(this._url);
+ return true;
}
+ return false;
}
@action
@@ -228,10 +222,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
if (history.length) {
if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]);
else future.push(this._url);
- this.dataDoc[this.annotationKey + "-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(this.dataDoc[this.annotationKey]));
this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = history.pop()!));
- this.dataDoc[this.annotationKey] = new List<Doc>(DocListCast(this.dataDoc[this.annotationKey + "-" + this.urlHash(this._url)]));
+ this._annotationKey = this.fieldKey + "-annotations-" + this.urlHash(this._url);
+ return true;
}
+ return false;
}
urlHash(s: string) {
@@ -239,34 +234,28 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
@action
- submitURL = () => {
- if (!this._url.startsWith("http")) this._url = "http://" + this._url;
+ submitURL = (newUrl: string) => {
+ if (!newUrl.startsWith("http")) newUrl = "http://" + newUrl;
try {
- const URLy = new URL(this._url);
const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null);
const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), null);
- const annos = DocListCast(this.dataDoc[this.annotationKey]);
const url = Cast(this.dataDoc[this.fieldKey], WebField, null)?.url.toString();
if (url) {
if (history === undefined) {
this.dataDoc[this.fieldKey + "-history"] = new List<string>([url]);
-
} else {
history.push(url);
}
+ this.layoutDoc._scrollTop = 0;
future && (future.length = 0);
- this.dataDoc[this.annotationKey + "-" + this.urlHash(url)] = new List<Doc>(annos);
}
- this.dataDoc[this.fieldKey] = new WebField(URLy);
- this.dataDoc[this.annotationKey] = new List<Doc>([]);
+ this._url = newUrl;
+ this._annotationKey = this.fieldKey + "-annotations-" + this.urlHash(this._url);
+ this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl));
} catch (e) {
console.log("WebBox URL error:" + this._url);
}
- }
-
- onValueKeyDown = async (e: React.KeyboardEvent) => {
- if (e.key === "Enter") this.submitURL();
- e.stopPropagation();
+ return true;
}
editToggleBtn() {
@@ -280,15 +269,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
_ignore = 0;
- onPreWheel = (e: React.WheelEvent) => { this._ignore = e.timeStamp; };
- onPrePointer = (e: React.PointerEvent) => { this._ignore = e.timeStamp; };
- onPostPointer = (e: React.PointerEvent) => {
- if (this._ignore !== e.timeStamp) e.stopPropagation();
- }
-
- onPostWheel = (e: React.WheelEvent) => {
- if (this._ignore !== e.timeStamp) e.stopPropagation();
- }
+ onPreWheel = (e: React.WheelEvent) => this._ignore = e.timeStamp;
+ onPrePointer = (e: React.PointerEvent) => this._ignore = e.timeStamp;
+ onPostPointer = (e: React.PointerEvent) => this._ignore !== e.timeStamp && e.stopPropagation();
+ onPostWheel = (e: React.WheelEvent) => this._ignore !== e.timeStamp && e.stopPropagation();
onLongPressDown = (e: React.PointerEvent) => {
this._pressX = e.clientX;
@@ -372,22 +356,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
}, 1500);
}
-
onLongPressMove = (e: PointerEvent) => {
// this._pressX = e.clientX;
// this._pressY = e.clientY;
}
-
onLongPressUp = (e: PointerEvent) => {
- if (this._longPressSecondsHack) {
- clearTimeout(this._longPressSecondsHack);
- }
- if (this._iframeIndicatorRef.current) {
- this._iframeIndicatorRef.current.classList.remove("active");
- }
- if (this._iframeDragRef.current) {
- while (this._iframeDragRef.current.firstChild) this._iframeDragRef.current.removeChild(this._iframeDragRef.current.firstChild);
- }
+ this._longPressSecondsHack && clearTimeout(this._longPressSecondsHack);
+ this._iframeIndicatorRef.current?.classList.remove("active");
+ while (this._iframeDragRef.current?.firstChild) this._iframeDragRef.current.removeChild(this._iframeDragRef.current.firstChild);
}
specificContextMenu = (e: React.MouseEvent): void => {
@@ -396,11 +372,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
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[this.fieldKey + "-contentWidth"] ? "Unfreeze" : "Freeze") + " Content Width", event: () => this.layoutDoc[this.fieldKey + "-contentWidth"] = this.layoutDoc[this.fieldKey + "-contentWidth"] ? undefined : Doc.NativeWidth(this.layoutDoc), icon: "snowflake" });
cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
-
}
- //const href = "https://brown365-my.sharepoint.com/personal/bcz_ad_brown_edu/_layouts/15/Doc.aspx?sourcedoc={31aa3178-4c21-4474-b367-877d0a7135e4}&action=embedview&wdStartOn=1";
-
@computed
get urlContent() {
const field = this.dataDoc[this.props.fieldKey];
@@ -410,7 +383,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
} else if (field instanceof WebField) {
const url = this.layoutDoc.useCors ? Utils.CorsProxy(field.url.href) : field.url.href;
// view = <iframe className="webBox-iframe" src={url} onLoad={e => { e.currentTarget.before((e.currentTarget.contentDocument?.body || e.currentTarget.contentDocument)?.children[0]!); e.currentTarget.remove(); }}
-
view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={url} onLoad={this.iframeLoaded}
// 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"} />;
@@ -423,10 +395,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
@computed
get content() {
- const view = this.urlContent;
const frozen = !this.props.isSelected() || DocumentDecorations.Instance?.Interacting;
const scale = this.props.scaling?.() || 1;
-
return (<>
<div className={"webBox-cont" + (this.props.isSelected() && Doc.GetSelectedTool() === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
style={{
@@ -435,7 +405,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
transform: `scale(${scale})`
}}
onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
- {view}
+ {this.urlContent}
</div>
{!frozen ? (null) :
<div className="webBox-overlay" style={{ pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? undefined : "all" }}
@@ -450,7 +420,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
@computed get allAnnotations() { return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]); }
@computed get nonDocAnnotations() { return this.allAnnotations.filter(a => a.annotations); }
-
@computed get annotationLayer() {
TraceMobx();
return <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
@@ -471,7 +440,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this.props.select(true);
}
- scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop));
+ panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
+ panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
+ scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
render() {
const inactiveLayer = this.props.layerProvider?.(this.layoutDoc) === false;
const scale = this.props.scaling?.() || 1;
@@ -486,21 +457,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
width: `${100 / scale}%`, height: `${100 / scale}%`, transform: `scale(${scale})`,
pointerEvents: this.layoutDoc.isAnnotating && !inactiveLayer ? "all" : "none"
}}
- onWheel={e => {
- const target = this._outerRef.current;
- if (this._forceSmoothScrollUpdate && target && this.webpage) {
- setTimeout(action(() => {
- target.scrollLeft = 0;
- const scrollTop = target.scrollTop;
- const scrollLeft = target.scrollLeft;
- this.webpage!.scrollTop = scrollTop;
- this.webpage!.scrollLeft = scrollLeft;
- if (this.layoutDoc._scrollTop !== scrollTop) this.layoutDoc._scrollTop = scrollTop;
- if (this.layoutDoc._scrollLeft !== scrollLeft) this.layoutDoc._scrollLeft = scrollLeft;
- }));
- }
- e.stopPropagation();
- }}
+ onWheel={this.onWheel}
onPointerDown={this.onMarqueeDown}
onScroll={e => e.stopPropagation()}
>
@@ -514,11 +471,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
fieldKey={this.annotationKey}
isAnnotationOverlay={true}
scaling={returnOne}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
ScreenToLocalTransform={this.scrollXf}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
addDocument={this.addDocument}
- setPreviewCursor={this.setPreviewCursor}
select={emptyFunction}
active={this.active}
whenActiveChanged={this.whenActiveChanged}>
@@ -527,7 +485,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
</div>
{this.annotationLayer}
{!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
- <MarqueeAnnotator rootDoc={this.rootDoc} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
+ <MarqueeAnnotator rootDoc={this.rootDoc} scrollTop={NumCast(this.rootDoc._scrollTop)} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
</div >
{this.props.isSelected() ? this.editToggleBtn() : null}
</div>);
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 1fbd3af5c..91d123efe 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -236,7 +236,9 @@ export class DashDocView extends React.Component<IDashDocView> {
PanelWidth={finalLayout[WidthSym]}
PanelHeight={finalLayout[HeightSym]}
focus={this.outerFocus}
- styleProvider={returnEmptyString}
+ styleProvider={self._textBox.props.styleProvider}
+ layerProvider={self._textBox.props.layerProvider}
+ docViewPath={self._textBox.props.docViewPath}
parentActive={returnFalse}
whenActiveChanged={returnFalse}
bringToFront={emptyFunction}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 81bca4c00..0f2f9cdb7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -41,6 +41,7 @@ audiotag:hover {
display: flex;
flex-direction: row;
transition: opacity 1s;
+ width: 100%;
.formattedTextBox-dictation {
height: 12px;
@@ -389,6 +390,7 @@ footnote::after {
overflow-x: hidden;
color: initial;
max-height: 100%;
+ width: 100%;
display: flex;
flex-direction: row;
transition: opacity 1s;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d24ccd9ad..183719e31 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -55,9 +55,8 @@ import { DocumentButtonBar } from '../../DocumentButtonBar';
import { AudioBox } from '../AudioBox';
import { FieldView, FieldViewProps } from "../FieldView";
import "./FormattedTextBox.scss";
-import { FormattedTextBoxComment, formattedTextBoxCommentPlugin, findLinkMark } from './FormattedTextBoxComment';
+import { FormattedTextBoxComment, findLinkMark } from './FormattedTextBoxComment';
import React = require("react");
-import { LinkManager } from '../../../util/LinkManager';
import { CollectionStackingView } from '../../collections/CollectionStackingView';
import { CollectionViewType } from '../../collections/CollectionView';
import { SnappingManager } from '../../../util/SnappingManager';
@@ -67,12 +66,16 @@ import { StyleProp } from '../../StyleProvider';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
import { DocumentManager } from '../../../util/DocumentManager';
+import { LightboxView } from '../../LightboxView';
+import { DocAfterFocusFunc } from '../DocumentView';
+const translateGoogleApi = require("translate-google-api");
export interface FormattedTextBoxProps {
makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text
hideOnLeave?: boolean; // used by DocumentView for setting caption's hide on leave (bcz: would prefer to have caption-hideOnLeave field set or something similar)
xMargin?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
yMargin?: number;
+ noSidebar?: boolean;
dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded
}
export const GoogleRef = "googleDocId";
@@ -90,11 +93,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
public static Instance: FormattedTextBox;
public ProseRef?: HTMLDivElement;
public get EditorView() { return this._editorView; }
+ public get SidebarKey() { return this.fieldKey + "-sidebar"; }
private _boxRef: React.RefObject<HTMLDivElement> = React.createRef();
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
private _editorView: Opt<EditorView>;
- private _applyingChange: boolean = false;
+ private _applyingChange: string = "";
private _searchIndex = 0;
private _cachedLinks: Doc[] = [];
private _undoTyping?: UndoManager.Batch;
@@ -102,15 +106,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private _dropDisposer?: DragManager.DragDropDisposer;
private _recordingStart: number = 0;
private _pause: boolean = false;
- private _animatingScroll: number = 0; // hack to prevent scroll values from being written to document when scroll is animating
+ private _ignoreScroll = false;
@computed get _recording() { return this.dataDoc?.audioState === "recording"; }
set _recording(value) {
this.dataDoc.audioState = value ? "recording" : undefined;
}
- @observable private _entered = false;
-
public static FocusedBox: FormattedTextBox | undefined;
public static SelectOnLoad = "";
public static PasteOnLoad: ClipboardEvent | undefined;
@@ -162,29 +164,38 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
// TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one.
// but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing.
public RemoveLinkFromDoc(linkDoc?: Doc) {
+ this.unhighlightSearchTerms();
const state = this._editorView?.state;
- if (state && linkDoc && this._editorView) {
- var allLinks: any[] = [];
+ const a1 = linkDoc?.anchor1 as Doc;
+ const a2 = linkDoc?.anchor2 as Doc;
+ if (state && a1 && a2 && this._editorView) {
+ this.removeDocument(a1);
+ this.removeDocument(a2);
+ var allFoundLinkAnchors: any[] = [];
state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any, pos: number, parent: any) => {
- const foundMark = findLinkMark(node.marks);
- const newHrefs = foundMark?.attrs.allLinks.filter((a: any) => a.href.includes(linkDoc[Id])) || [];
- allLinks = newHrefs.length ? newHrefs : allLinks;
+ const foundLinkAnchors = findLinkMark(node.marks)?.attrs.allAnchors.filter((a: any) => a.anchorId === a1[Id] || a.anchorId === a2[Id]) || [];
+ allFoundLinkAnchors = foundLinkAnchors.length ? foundLinkAnchors : allFoundLinkAnchors;
return true;
});
- if (allLinks.length) {
- this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.linkAnchor, { allLinks }));
+ if (allFoundLinkAnchors.length) {
+ this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.linkAnchor, { allAnchors: allFoundLinkAnchors }));
+
+ this.setupEditor(this.config, this.fieldKey);
}
}
}
- // removes all the specified link referneces from the selection.
+ // removes all the specified link references from the selection.
// NOTE: as above, this won't work correctly if there are marks with overlapping but not exact sets of link references.
- public RemoveLinkFromSelection(allLinks: { href: string, title: string, linkId: string, targetId: string }[]) {
+ public RemoveAnchorFromSelection(allAnchors: { href: string, title: string, linkId: string, targetId: string }[]) {
const state = this._editorView?.state;
if (state && this._editorView) {
- this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allLinks }));
+ this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allAnchors }));
+ this.setupEditor(this.config, this.fieldKey);
}
}
+ getAnchor = () => this.makeLinkAnchor(undefined, "add:right", undefined, "Anchored Selection");
+
linkOnDeselect: Map<string, string> = new Map();
doLinkOnDeselect() {
@@ -232,11 +243,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, () => this.rootDoc, targetCreator), e.pageX, e.pageY, {
dragComplete: e => {
+ const anchor = this.makeLinkAnchor(undefined, "add:right", undefined, "a link");
if (!e.aborted && e.annoDragData && e.annoDragData.annotationDocument && e.annoDragData.dropDocument && !e.linkDocument) {
- e.linkDocument = DocUtils.MakeLink({ doc: e.annoDragData.annotationDocument }, { doc: e.annoDragData.dropDocument }, "hyperlink", "link to note");
+ e.linkDocument = DocUtils.MakeLink({ doc: anchor }, { doc: e.annoDragData.dropDocument }, "hyperlink", "link to note");
e.annoDragData.annotationDocument.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.rootDoc;
}
- e.linkDocument && e.annoDragData?.dropDocument && this.makeLinkToSelection(e.linkDocument[Id], "a link", "add:right", e.annoDragData.dropDocument[Id]);
e.linkDocument && e.annoDragData?.linkDropCallback?.(e as { linkDocument: Doc });// bcz: typescript can't figure out that this is valid even though we tested e.linkDocument
}
});
@@ -246,6 +257,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(Math.min(coordsT.left, coordsB.left), Math.max(coordsT.bottom, coordsB.bottom));
}
+ _lastText = "";
dispatchTransaction = (tx: Transaction) => {
let timeStamp;
clearTimeout(timeStamp);
@@ -267,8 +279,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.linkOnDeselect.set(key, value);
const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
- const allLinks = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }];
- const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, location: "add:right", title: value });
+ const allAnchors = [{ href: Utils.prepend("/doc/" + id), title: value, anchorId: id }];
+ const link = this._editorView.state.schema.marks.linkAnchor.create({ allAnchors, location: "add:right", title: value });
const mval = this._editorView.state.schema.marks.metadataVal.create();
const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
@@ -296,8 +308,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
};
if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) {
- if (!this._applyingChange && removeSelection(json) !== removeSelection(curProto?.Data)) {
- this._applyingChange = true;
+ if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) {
+ this._applyingChange = this.fieldKey;
(curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
if ((!curTemp && !curProto) || curText || json.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
if (removeSelection(json) !== removeSelection(curLayout?.Data)) {
@@ -325,7 +337,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
unchanged = false;
}
- this._applyingChange = false;
+ this._applyingChange = "";
if (!unchanged) {
this.updateTitle();
this.tryUpdateHeight();
@@ -404,9 +416,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
res.map(r => r.map(h => flattened.push(h)));
const lastSel = Math.min(flattened.length - 1, this._searchIndex);
this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
- const alink = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: target }, "automatic")!;
- const allLinks = [{ href: Utils.prepend("/doc/" + alink[Id]), title: "a link", targetId: target[Id], linkId: alink[Id] }];
- const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, title: "a link", location });
+ const anchor = new Doc();
+ const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!;
+ const allAnchors = [{ href: Utils.prepend("/doc/" + anchor[Id]), title: "a link", anchorId: anchor[Id] }];
+ const link = this._editorView.state.schema.marks.linkAnchor.create({ allAnchors, title: "a link", location });
this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link));
}
}
@@ -504,8 +517,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
linkDrop = (data: { linkDocument: Doc }) => {
const anchor1Title = data.linkDocument.anchor1 instanceof Doc ? StrCast(data.linkDocument.anchor1.title) : "-untitled-";
- const anchor1Id = data.linkDocument.anchor1 instanceof Doc ? data.linkDocument.anchor1[Id] : "";
- this.makeLinkToSelection(data.linkDocument[Id], anchor1Title, "add:right", anchor1Id);
+ const anchor1 = data.linkDocument.anchor1 instanceof Doc ? data.linkDocument.anchor1 : undefined;
+ this.makeLinkAnchor(anchor1, "add:right", undefined, anchor1Title);
}
getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null {
@@ -602,6 +615,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const bounds = this.CurrentDiv.getBoundingClientRect();
this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%";
this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%";
+ e.preventDefault();
return false;
}
@undoBatch
@@ -816,43 +830,51 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
props: {
attributes: { class: "ProseMirror-example-setup-style" }
}
- }),
- formattedTextBoxCommentPlugin
+ }), new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } })
]
};
}
- makeLinkToSelection(linkId: string, title: string, location: string, targetId: string, targetHref?: string) {
+ makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string) {
const state = this._editorView?.state;
if (state) {
- const href = targetHref ?? Utils.prepend("/doc/" + linkId);
const sel = state.selection;
const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
let tr = state.tr.addMark(sel.from, sel.to, splitter);
- sel.from !== sel.to && tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
- if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
- const allLinks = [{ href, title, targetId, linkId }];
- allLinks.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allLinks ?? []));
- const link = state.schema.marks.linkAnchor.create({ allLinks, title, location, linkId });
- tr = tr.addMark(pos, pos + node.nodeSize, link);
- }
- });
- this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
- this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
- this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
+ if (sel.from !== sel.to) {
+ const anchor = anchorDoc ?? Docs.Create.TextanchorDocument();
+ const href = targetHref ?? Utils.prepend("/doc/" + anchor[Id]);
+ if (anchor !== anchorDoc) this.addDocument(anchor);
+ tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
+ if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
+ const allAnchors = [{ href, title, anchorId: anchor[Id] }];
+ allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allAnchors ?? []));
+ const link = state.schema.marks.linkAnchor.create({ allAnchors, title, location });
+ tr = tr.addMark(pos, pos + node.nodeSize, link);
+ }
+ });
+ this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
+ this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
+ this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
+ Doc.GetProto(anchor).title = this._editorView?.state.doc.textBetween(sel.from, sel.to);
+ return anchor;
+ }
+ return anchorDoc ?? this.rootDoc;
}
+ return anchorDoc ?? this.rootDoc;
}
IsActive = () => {
return this.active();//this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
}
- scrollToLinkId = (linkId: string) => {
- const findLinkFrag = (frag: Fragment, editor: EditorView) => {
+ scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => {
+ const anchorId = doc[Id];
+ const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
const nodes: Node[] = [];
let hadStart = start !== 0;
frag.forEach((node, index) => {
- const examinedNode = findLinkNode(node, editor);
+ const examinedNode = findAnchorNode(node, editor);
if (examinedNode?.node.textContent) {
nodes.push(examinedNode.node);
!hadStart && (start = index + examinedNode.start);
@@ -861,20 +883,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
});
return { frag: Fragment.fromArray(nodes), start };
};
- const findLinkNode = (node: Node, editor: EditorView) => {
+ const findAnchorNode = (node: Node, editor: EditorView) => {
if (!node.isText) {
- const content = findLinkFrag(node.content, editor);
+ const content = findAnchorFrag(node.content, editor);
return { node: node.copy(content.frag), start: content.start };
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor);
- return linkIndex !== -1 && marks[linkIndex].attrs.allLinks.find((item: { href: string }) => linkId === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined;
+ return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => anchorId === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined;
};
let start = 0;
- if (this._editorView && linkId) {
+ if (this._editorView && anchorId) {
const editor = this._editorView;
- const ret = findLinkFrag(editor.state.doc.content, editor);
+ const ret = findAnchorFrag(editor.state.doc.content, editor);
if (ret.frag.size > 2 && ret.start >= 0) {
let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
@@ -882,16 +904,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
}
editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
- const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
- setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0);
- setTimeout(() => this.unhighlightSearchTerms(), 2000);
+ const escAnchorId = anchorId[0] > '0' && anchorId[0] <= '9' ? `\\3${anchorId[0]} ${anchorId.substr(1)}` : anchorId;
+ addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow" });
+ setTimeout(() => {
+ clearStyleSheetRules(FormattedTextBox._highlightStyleSheet);
+ afterFocus?.(true);
+ }, 1500);
}
- Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false);
- Doc.SetInPlace(this.layoutDoc, "_scrollToPreviewLinkID", undefined, false);
+ } else {
+ afterFocus?.(false);
}
}
componentDidMount() {
+ this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
this.props.contentsActive?.(this.IsActive);
this._cachedLinks = DocListCast(this.Document.links);
this._disposers.sidebarheight = reaction(
@@ -913,34 +939,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
);
- this._disposers.linkMaker = reaction(
- () => this.props.makeLink?.(),
- (linkDoc: Opt<Doc>) => {
- if (linkDoc) {
- const a1 = Cast(linkDoc.anchor1, Doc, null);
- const a2 = Cast(linkDoc.anchor2, Doc, null);
- const otherAnchor = Doc.AreProtosEqual(a1, this.rootDoc) ? a2 : a1;
- const anchor2Title = StrCast(otherAnchor.title, "-untitled-");
- const anchor2Id = otherAnchor?.[Id] || "";
- this.makeLinkToSelection(linkDoc[Id], anchor2Title, "add:right", anchor2Id);
- }
- },
- { fireImmediately: true }
- );
this._disposers.editorState = reaction(
() => {
- if (!this.dataDoc || !this.layoutDoc) return undefined;
- if (this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey]) {
- return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
- }
- return Cast(this.layoutDoc[this.props.fieldKey], RichTextField, null)?.Data;
+ const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined :
+ this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey] ?
+ this.dataDoc : this.layoutDoc;
+ return !whichDoc ? undefined : { data: Cast(whichDoc[this.props.fieldKey], RichTextField, null), str: StrCast(whichDoc[this.props.fieldKey]) };
},
incomingValue => {
- if (incomingValue !== undefined && this._editorView && !this._applyingChange) {
- const updatedState = JSON.parse(incomingValue);
- if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) {
- this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
- this.tryUpdateHeight();
+ if (this._editorView && this._applyingChange !== this.fieldKey) {
+ if (incomingValue?.data) {
+ const updatedState = JSON.parse(incomingValue.data.Data);
+ if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) {
+ this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
+ this.tryUpdateHeight();
+ }
+ } else if (incomingValue?.str) {
+ selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str)));
}
}
},
@@ -1006,38 +1021,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
);
}
- this._disposers.previewScrollToRegion = reaction(() => StrCast(this.layoutDoc._scrollToPreviewLinkID),
- scrollToLinkID => this.props.renderDepth === -1 && scrollToLinkID && this.scrollToLinkId(scrollToLinkID), { fireImmediately: true }
- );
- this._disposers.scrollToRegion = reaction(() => StrCast(this.layoutDoc.scrollToLinkID),
- scrollToLinkID => scrollToLinkID && this.scrollToLinkId(scrollToLinkID), { fireImmediately: true }
- );
+ var quickScroll: string | undefined = "";
this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop),
pos => {
- const durationMiliStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/);
- const durationSecStr = StrCast(this.Document._viewTransition).match(/([0-9.]*)s/);
- const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
- if (duration) {
- this._animatingScroll++;
- this._scrollRef.current && smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0));
- setTimeout(() => this._animatingScroll--, duration);
- } else {
- this._scrollRef.current?.scrollTo({ top: pos });
+ if (!this._ignoreScroll && this._scrollRef.current) {
+ const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
+ const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
+ const durationSecStr = viewTrans.match(/([0-9.]*)s/);
+ const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
+ if (duration) {
+ smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0));
+ } else {
+ this._scrollRef.current.scrollTo({ top: pos });
+ }
}
}, { fireImmediately: true }
);
- this._disposers.scrollY = reaction(() => Cast(this.layoutDoc._scrollY, "number", null),
- pos => {
- this.Document._scrollY = undefined;
- pos !== undefined && setTimeout(() => this.layoutDoc._scrollTop = pos, this._scrollRef.current ? 0 : 250);
- }, { fireImmediately: true }
- );
- this._disposers.scrollPreviewY = reaction(() => Cast(this.layoutDoc.scrollPreviewY, "number", null),
- pos => {
- this.Document.scrollPreviewY = undefined;
- pos !== undefined && setTimeout(() => this.layoutDoc._scrollTop = pos, this._scrollRef.current ? 0 : 250);
- }, { fireImmediately: true }
- );
+ quickScroll = undefined;
setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight)));
}
@@ -1221,8 +1221,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
handleScrollToSelection: (editorView) => {
const docPos = editorView.coordsAtPos(editorView.state.selection.from);
const viewRect = self._ref.current!.getBoundingClientRect();
- if (docPos.top < viewRect.top || docPos.top > viewRect.bottom) {
- docPos && (self._scrollRef.current!.scrollTop += (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale);
+ const scrollRef = self._scrollRef.current;
+ if ((docPos.top < viewRect.top || docPos.top > viewRect.bottom) && scrollRef) {
+ const scrollPos = scrollRef.scrollTop + (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale;
+ if (!LinkDocPreview.LinkInfo) {
+ scrollPos && smoothScroll(500, scrollRef, scrollPos);
+ } else {
+ scrollRef.scrollTo({ top: scrollPos });
+ }
}
return true;
},
@@ -1238,7 +1244,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
});
- // !Doc.UserDoc().noviceMode && applyDevTools.applyDevTools(this._editorView);
const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
if (startupText) {
const { state: { tr }, dispatch } = this._editorView;
@@ -1247,7 +1252,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
(this._editorView as any).TextView = this;
}
- const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad;
+ const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()));
if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
FormattedTextBox.SelectOnLoad = "";
this.props.select(false);
@@ -1341,7 +1346,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
onPointerUp = (e: React.PointerEvent): void => {
if (!this._downEvent) return;
this._downEvent = false;
- if (!(e.nativeEvent as any).formattedHandled) {
+ if (!(e.nativeEvent as any).formattedHandled && this.active(true)) {
const editor = this._editorView!;
FormattedTextBoxComment.textBox = this;
const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
@@ -1369,13 +1374,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
e.preventDefault();
}
FormattedTextBoxComment.Hide();
- if (FormattedTextBoxComment.linkDoc) {
- if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
- this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right");
- } else {
- LinkManager.FollowLink(FormattedTextBoxComment.linkDoc, this.props.Document, this.props, false);
- }
- }
(e.nativeEvent as any).formattedHandled = true;
@@ -1388,6 +1386,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
onFocused = (e: React.FocusEvent): void => {
FormattedTextBox.FocusedBox = this;
this.tryUpdateHeight();
+ //applyDevTools.applyDevTools(this._editorView);
// see if we need to preserve the insertion point
const prosediv = this.ProseRef?.children?.[0] as any;
@@ -1416,6 +1415,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
+ static _highlightStyleSheet: any = addStyleSheet();
static _bulletStyleSheet: any = addStyleSheet();
static _userStyleSheet: any = addStyleSheet();
_forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle
@@ -1541,6 +1541,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
FormattedTextBox.LiveTextUndo?.end();
FormattedTextBox.LiveTextUndo = undefined;
+
+ const state = this._editorView!.state;
+ const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
+ if (this.layoutDoc.sidebarViewType === "translation" && !this.fieldKey.includes("translation") && curText.endsWith(" ") && curText !== this._lastText) {
+ try {
+ translateGoogleApi(curText, { from: "en", to: "es", }).then((result1: any) => {
+ setTimeout(() => translateGoogleApi(result1[0], { from: "es", to: "en", }).then((result: any) => {
+ this.dataDoc[this.fieldKey + "-translation"] = result1 + "\r\n\r\n" + result[0];
+ }), 1000);
+ });
+ } catch (e) { console.log(e.message); }
+ this._lastText = curText;
+ }
}
_lastTimedMark: Mark | undefined = undefined;
@@ -1589,9 +1602,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema));
eve.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash.
}
- onscrolled = (ev: React.UIEvent) => {
- if (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && !this._animatingScroll) {
- this.layoutDoc._scrollTop = this._scrollRef.current!.scrollTop;
+ onScroll = (ev: React.UIEvent) => {
+ if (!LinkDocPreview.LinkInfo && this._scrollRef.current) {
+ this._ignoreScroll = true;
+ this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop;
+ this._ignoreScroll = false;
}
}
@@ -1607,8 +1622,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.layoutDoc.limitHeight = undefined;
this.layoutDoc._autoHeight = false;
}
- const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight, 0);
- const dh = NumCast(this.rootDoc._height, 0);
+ const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight);
+ const dh = NumCast(this.rootDoc._height);
const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + this.titleHeight);
if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) {
// if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived...
@@ -1635,8 +1650,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
@computed get sidebarHandle() {
- const annotated = DocListCast(this.dataDoc[this.annotationKey]).filter(d => d?.author).length;
- return !this.props.isSelected() && !(annotated && !this.sidebarWidth()) ? (null) :
+ const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
+ return this.props.noSidebar || (!this.props.isSelected() && !(annotated && !this.sidebarWidth())) ? (null) :
<div className="formattedTextBox-sidebar-handle"
style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: annotated ? "lightblue" : this.props.styleProvider?.(this.props.Document, this.props, StyleProp.WidgetColor) }}
onPointerDown={this.sidebarDown}
@@ -1655,9 +1670,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
xMargin: 0,
yMargin: 0,
chromeStatus: "enabled",
- scaleField: this.annotationKey + "-scale",
+ scaleField: this.SidebarKey + "-scale",
isAnnotationOverlay: true,
- fieldKey: this.annotationKey,
+ fieldKey: this.SidebarKey,
fitContentsToDoc: fitToBox,
select: emptyFunction,
active: this.annotationsActive,
@@ -1670,12 +1685,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
ScreenToLocalTransform: this.sidebarScreenToLocal,
renderDepth: this.props.renderDepth + 1,
};
- return !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) :
+ return this.props.noSidebar || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) :
<div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")}
style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
- {this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ?
- <CollectionFreeFormView {...collectionProps} /> :
- <CollectionStackingView {...collectionProps} />}
+ {this.layoutDoc.sidebarViewType === "translation" ?
+ <FormattedTextBox {...collectionProps} noSidebar={true} fieldKey={`${this.fieldKey}-translation`} /> :
+ this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ?
+ <CollectionFreeFormView {...collectionProps} /> :
+ <CollectionStackingView {...collectionProps} />}
</div>;
}
@@ -1709,7 +1726,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
<div className={`formattedTextBox-cont`} ref={this._ref}
style={{
overflow: this.layoutDoc._autoHeight ? "hidden" : undefined,
- width: "100%",
height: this.props.height || (this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined),
background: this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor)),
color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color)),
@@ -1728,14 +1744,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
onPointerDown={this.onPointerDown}
onMouseUp={this.onMouseUp}
onWheel={this.onPointerWheel}
- onPointerEnter={action(() => this._entered = true)}
- onPointerLeave={action(e => {
- this._entered = false;
- const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y);
- for (let child: any = target; child; child = child?.parentElement) {
- child === this._ref.current! && (this._entered = true);
- }
- })}
onDoubleClick={this.onDoubleClick}
>
<div className={`formattedTextBox-outer${selected ? "-selected" : ""}`} ref={this._scrollRef}
@@ -1744,7 +1752,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
pointerEvents: !active && !SnappingManager.GetIsDragging() ? "none" : undefined,
overflow: this.layoutDoc._singleLine ? "hidden" : undefined,
}}
- onScroll={this.onscrolled} onDrop={this.ondrop} >
+ onScroll={this.onScroll} onDrop={this.ondrop} >
<div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selPaddingClass}`} ref={this.createDropTarget}
style={{
padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${padding}px`,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
index 81afba4d7..55b8446e9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
@@ -1,6 +1,8 @@
-.FormattedTextBox-tooltip {
+.formattedTextBox-tooltip {
position: absolute;
- pointer-events: none;
+ pointer-events: all;
+ height: 100%;
+ overflow: hidden;
z-index: 20;
background: white;
border: 1px solid silver;
@@ -9,70 +11,16 @@
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
box-shadow: 3px 3px 1.5px grey;
-
- .FormattedTextBoxComment {
- background-color: white;
- border: 8px solid white;
- //width: 200px;
-
- //display: flex;
- .FormattedTextBoxComment-info {
-
- margin-bottom: 37px;
-
- .FormattedTextBoxComment-title {
- padding-right: 4px;
- float: left;
-
- .FormattedTextBoxComment-description {
- text-decoration: none;
- font-style: italic;
- color: rgb(95, 97, 102);
- font-size: 10px;
- }
- }
-
- .FormattedTextBoxComment-button {
- display: inline;
- padding-left: 6px;
- padding-right: 6px;
- padding-top: 2.5px;
- padding-bottom: 2.5px;
- width: 17px;
- height: 17px;
- margin: 0;
- margin-right: 3px;
- border-radius: 50%;
- pointer-events: auto;
- background-color: rgb(0, 0, 0);
- color: rgb(255, 255, 255);
- transition: transform 0.2s;
- text-align: center;
- position: relative;
- font-size: 12px;
-
- &:hover {
- background-color: rgb(77, 77, 77);
- cursor: pointer;
- }
- }
- }
-
- .FormattedTextBoxComment-preview-wrapper {
- //width: 170px;
- height: 170px;
- overflow: hidden;
- //padding-top: 5px;
- margin-top: 10px;
- margin-bottom: 8px;
- align-content: center;
- justify-content: center;
- background-color: rgb(160, 160, 160);
- }
+ max-width: 400;
+ max-height: 235;
+ height:max-content;
+ .formattedTextBox-tooltipText {
+ height: max-content;
+ text-overflow: ellipsis;
}
}
-.FormattedTextBox-tooltip:before {
+.formattedTextBox-tooltip:before {
content: "";
height: 0;
width: 0;
@@ -85,7 +33,7 @@
border-top-color: silver;
}
-.FormattedTextBox-tooltip:after {
+.formattedTextBox-tooltip:after {
content: "";
height: 0;
width: 0;
@@ -96,12 +44,4 @@
border: 5px solid transparent;
border-bottom-width: 0;
border-top-color: white;
-}
-
-.FormattedTextBoxComment-buttons {
- display: none;
- position: absolute;
- top: 50%;
- right: 0;
- transform: translateY(-50%);
} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 5371bd10a..89df5e246 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -1,43 +1,17 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, observable } from "mobx";
import { Mark, ResolvedPos } from "prosemirror-model";
-import { EditorState, Plugin } from "prosemirror-state";
+import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
-import * as ReactDOM from 'react-dom';
-import wiki from "wikijs";
-import { Doc, DocCastAsync, DocListCast, Opt } from "../../../../fields/Doc";
-import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, Utils } from "../../../../Utils";
-import { DocServer } from "../../../DocServer";
-import { Docs } from "../../../documents/Documents";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { LinkManager } from "../../../util/LinkManager";
-import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
-import { DocumentLinksButton } from "../DocumentLinksButton";
-import { DocumentView } from "../DocumentView";
+import { Doc } from "../../../../fields/Doc";
import { LinkDocPreview } from "../LinkDocPreview";
import { FormattedTextBox } from "./FormattedTextBox";
import './FormattedTextBoxComment.scss';
import { schema } from "./schema_rts";
-import React = require("react");
-export let formattedTextBoxCommentPlugin = new Plugin({
- view(editorView) { return new FormattedTextBoxComment(editorView); }
-});
-export function findOtherUserMark(marks: Mark[]): Mark | undefined {
- return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
-}
-export function findUserMark(marks: Mark[]): Mark | undefined {
- return marks.find(m => m.attrs.userid);
-}
-export function findLinkMark(marks: Mark[]): Mark | undefined {
- return marks.find(m => m.type === schema.marks.linkAnchor);
-}
+export function findOtherUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); }
+export function findUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid); }
+export function findLinkMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.type === schema.marks.linkAnchor); }
export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
- let before = 0;
- let nbef = rpos.nodeBefore;
+ let before = 0, nbef = rpos.nodeBefore;
while (nbef && finder(nbef.marks)) {
before += nbef.nodeSize;
rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
@@ -46,8 +20,7 @@ export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (ma
return before;
}
export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
- let after = 0;
- let naft = rpos.nodeAfter;
+ let after = 0, naft = rpos.nodeAfter;
while (naft && finder(naft.marks)) {
after += naft.nodeSize;
rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
@@ -59,283 +32,93 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark
// this view appears when clicking on text that has a hyperlink which is configured to show a preview of its target.
// this will also display metadata information about text when the view is configured to display things like other people who authored text.
//
-
export class FormattedTextBoxComment {
static tooltip: HTMLElement;
static tooltipText: HTMLElement;
- static tooltipInput: HTMLInputElement;
- static start: number;
- static end: number;
- static mark: Mark;
+ static startUserMarkRegion: number;
+ static endUserMarkRegion: number;
+ static userMark: Mark;
static textBox: FormattedTextBox | undefined;
- static linkDoc: Doc | undefined;
-
- static _deleteRef: Opt<HTMLDivElement | null>;
- static _followRef: Opt<HTMLDivElement | null>;
- static _nextRef: Opt<HTMLDivElement | null>;
-
- static _lastState?: EditorState;
- static _lastView?: EditorView;
-
- @observable static _hrefInd = 0;
- static _hrefs: string[] | undefined = [];
constructor(view: any) {
if (!FormattedTextBoxComment.tooltip) {
- const root = document.getElementById("root");
- FormattedTextBoxComment.tooltipInput = document.createElement("input");
- FormattedTextBoxComment.tooltipInput.type = "checkbox";
- FormattedTextBoxComment.tooltip = document.createElement("div");
- FormattedTextBoxComment.tooltipText = document.createElement("div");
- //FormattedTextBoxComment.tooltipText.style.width = "100%";
- FormattedTextBoxComment.tooltipText.style.height = "100%";
- FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
- FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
- FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
- FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
- FormattedTextBoxComment.tooltip.style.maxWidth = "400px";
- FormattedTextBoxComment.tooltip.style.maxHeight = "235px";
- //FormattedTextBoxComment.tooltip.style.width = "100%";
- FormattedTextBoxComment.tooltip.style.height = "100%";
- FormattedTextBoxComment.tooltip.style.overflow = "hidden";
- FormattedTextBoxComment.tooltip.style.display = "none";
- // FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput);
- FormattedTextBoxComment.tooltip.onpointerdown = async (e: PointerEvent) => {
- const keep = e.target && (e.target as any).type === "checkbox" ? true : false;
- const textBox = FormattedTextBoxComment.textBox;
- const linkDoc = FormattedTextBoxComment.linkDoc;
- if (linkDoc && !keep && textBox) {
- if (linkDoc.author) {
- if (FormattedTextBoxComment._deleteRef?.contains(e.target as any)) {
- this.deleteLink();
- } else if (FormattedTextBoxComment._nextRef?.contains(e.target as any)) {
- FormattedTextBoxComment.showPreview(FormattedTextBoxComment._lastView!, FormattedTextBoxComment._lastState, FormattedTextBoxComment._hrefs?.[(++FormattedTextBoxComment._hrefInd) % FormattedTextBoxComment._hrefs?.length]);
- } else {
- FormattedTextBoxComment.linkDoc = undefined;
- if (linkDoc.type !== DocumentType.LINK) {
- textBox.props.addDocTab(linkDoc, e.ctrlKey ? "add" : "add:right");
- } else {
- const target = LinkManager.getOppositeAnchor(linkDoc, textBox.dataDoc);
- target && LinkManager.FollowLink(linkDoc, textBox.dataDoc, textBox.props, e.altKey);
- }
- }
- }
- } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
- textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, useCors: true }), "add:right");
- }
- keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
- FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
+ const tooltip = FormattedTextBoxComment.tooltip = document.createElement("div");
+ const tooltipText = FormattedTextBoxComment.tooltipText = document.createElement("div");
+ tooltip.className = "FormattedTextBox-tooltip";
+ tooltipText.className = "FormattedTextBox-tooltipText";
+ tooltip.style.display = "none";
+ tooltip.appendChild(tooltipText);
+ tooltip.onpointerdown = (e: PointerEvent) => {
+ const { textBox, startUserMarkRegion, endUserMarkRegion, userMark } = FormattedTextBoxComment;
+ false && startUserMarkRegion !== undefined && textBox?.adoptAnnotation(startUserMarkRegion, endUserMarkRegion, userMark);
e.stopPropagation();
e.preventDefault();
};
- root?.appendChild(FormattedTextBoxComment.tooltip);
+ document.getElementById("root")?.appendChild(tooltip);
}
}
-
- @undoBatch
- deleteLink = action(() => {
- FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null;
- LinkDocPreview.LinkInfo = undefined;
- DocumentLinksButton.EditLink = undefined;
- FormattedTextBoxComment.Hide();
- });
-
public static Hide() {
FormattedTextBoxComment.textBox = undefined;
- FormattedTextBoxComment.linkDoc = undefined;
- FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
- try {
- ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
- FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText);
- } catch (e) { }
+ FormattedTextBoxComment.tooltip.style.display = "none";
}
- public static SetState(textBox: any, start: number, end: number, mark: Mark) {
+ public static saveMarkRegion(textBox: any, start: number, end: number, mark: Mark) {
FormattedTextBoxComment.textBox = textBox;
- FormattedTextBoxComment.start = start;
- FormattedTextBoxComment.end = end;
- FormattedTextBoxComment.mark = mark;
- FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
+ FormattedTextBoxComment.startUserMarkRegion = start;
+ FormattedTextBoxComment.endUserMarkRegion = end;
+ FormattedTextBoxComment.userMark = mark;
+ FormattedTextBoxComment.tooltip.style.display = "";
}
- static showCommentbox(set: string, view: EditorView, nbef: number) {
+ static showCommentbox(view: EditorView, nbef: number) {
const state = view.state;
- if (set !== "none") {
- // These are in screen coordinates
- // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
- const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
- // The box in which the tooltip is positioned, to use as base
- const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
- // Find a center-ish x position from the selection endpoints (when
- // crossing lines, end may be more to the left)
- const left = Math.max((start.left + end.left) / 2, start.left + 3);
- FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
- FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
- }
- FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
+ // These are in screen coordinates
+ const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
+ // The box in which the tooltip is positioned, to use as base
+ const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
+ // Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left)
+ const left = Math.max((start.left + end.left) / 2, start.left + 3);
+ FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
+ FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
+ FormattedTextBoxComment.tooltip.style.display = "";
}
- static update(view: EditorView, lastState?: EditorState, forceUrl: string = "") {
- // Don't do anything if the document/selection didn't change
- if (!forceUrl && lastState?.doc.eq(view.state.doc) && lastState?.selection.eq(view.state.selection)) {
- return;
+ static update(view: EditorView, lastState?: EditorState, hrefs: string = "") {
+ if (FormattedTextBoxComment.textBox && (hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) {
+ FormattedTextBoxComment.setupPreview(view, FormattedTextBoxComment.textBox, hrefs ? hrefs.trim().split(" ") : undefined);
}
- FormattedTextBoxComment._lastState = lastState;
- FormattedTextBoxComment._lastView = view;
- FormattedTextBoxComment._hrefs = forceUrl ? forceUrl.trim().split(" ") : undefined;
- FormattedTextBoxComment._hrefInd = 0;
- FormattedTextBoxComment.linkDoc = undefined;
- FormattedTextBoxComment.showPreview(view, lastState, FormattedTextBoxComment._hrefs?.[FormattedTextBoxComment._hrefInd]);
}
- static showPreview(view: EditorView, lastState?: EditorState, forceUrl: string = "") {
+ static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[]) {
const state = view.state;
- const textBox = FormattedTextBoxComment.textBox;
- if (!textBox || !textBox.props) {
- return;
- }
- let set = "none";
- let nbef = 0;
- FormattedTextBoxComment.tooltipInput.style.display = "none";
- FormattedTextBoxComment.tooltip.style.width = "";
- FormattedTextBoxComment.tooltip.style.height = "";
- (FormattedTextBoxComment.tooltipText as any).href = "";
- FormattedTextBoxComment.tooltipText.style.whiteSpace = "";
- FormattedTextBoxComment.tooltipText.style.overflow = "";
// this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date
if (state.selection.$from) {
- nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
+ const nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
- const noselection = view.state.selection.$from === view.state.selection.$to;
+ const noselection = state.selection.$from === state.selection.$to;
let child: any = null;
state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
const mark = child && findOtherUserMark(child.marks);
if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
- FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
+ FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
}
if (mark && child && ((nbef && naft) || !noselection)) {
FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " on " + (new Date(mark.attrs.modified * 1000)).toLocaleString();
- set = "";
- FormattedTextBoxComment.tooltipInput.style.display = "";
- }
+ FormattedTextBoxComment.showCommentbox(view, nbef);
+ } else FormattedTextBoxComment.Hide();
}
+
// this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links.
- if (set === "none" && state.selection.$from) {
- nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
+ if (state.selection.$from && hrefs) {
+ const nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
- let child: any = null;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
- child = child || (nbef && state.selection.$from.nodeBefore);
- const mark = child ? findLinkMark(child.marks) : undefined;
- const href = forceUrl || (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allLinks.find((item: { href: string }) => item.href)?.href;
- if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) {
- try {
- ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
- FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText);
- } catch (e) { }
- FormattedTextBoxComment.tooltipText = document.createElement("div");
- FormattedTextBoxComment.tooltipText.className = "FormattedTextBoxComment-toolTipText";
- FormattedTextBoxComment.tooltipText.style.width = "100%";
- FormattedTextBoxComment.tooltipText.style.height = "100%";
- FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
- FormattedTextBoxComment.tooltipText.style.cursor = "pointer";
- FormattedTextBoxComment.tooltipText.textContent = "URL: " + href;
- (FormattedTextBoxComment.tooltipText as any).href = href;
- FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
-
- if (href.startsWith("https://en.wikipedia.org/wiki/")) {
- wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
- } else {
- FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre";
- FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
- }
- if (href.indexOf(Utils.prepend("/doc/")) === 0) {
- const docTarget = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
- FormattedTextBoxComment.tooltipText.textContent = "target not found...";
- (FormattedTextBoxComment.tooltipText as any).href = "";
- docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => {
- if (linkDoc instanceof Doc) {
- (FormattedTextBoxComment.tooltipText as any).href = href;
- FormattedTextBoxComment.linkDoc = linkDoc;
- const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
- const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
- if (anchor !== target && anchor && target) {
- target._scrollPreviewY = NumCast(anchor?.y);
- }
- if (target?.author) {
- FormattedTextBoxComment.showCommentbox("", view, nbef);
-
- const title = StrCast(target.title).length > 16 ? StrCast(target.title).substr(0, 16) + "..." : target.title;
-
- const docPreview = <div className="FormattedTextBoxComment">
- <div className="FormattedTextBoxComment-info">
- <div className="FormattedTextBoxComment-title">
- {title}
- {FormattedTextBoxComment.linkDoc.description === "" ? (null) :
- <p className="FormattedTextBoxComment-description"> {StrCast(FormattedTextBoxComment.linkDoc.description)}</p>}
- </div>
- <div className="wrapper" style={{ float: "right" }}>
- {(FormattedTextBoxComment._hrefs?.length || 0) <= 1 ? (null) : <Tooltip title={<><div className="dash-tooltip">Next Link</div></>} placement="top">
- <div className="FormattedTextBoxComment-button" ref={(r) => this._nextRef = r}>
- <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="chevron-right" color="white" size="sm" />
- </div>
- </Tooltip>}
-
- <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>} placement="top">
- <div className="FormattedTextBoxComment-button" ref={(r) => this._deleteRef = r}>
- <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="trash" color="white" size="sm" />
- </div>
- </Tooltip>
-
- <Tooltip title={<><div className="dash-tooltip">Follow Link</div></>} placement="top">
- <div className="FormattedTextBoxComment-button" ref={(r) => this._followRef = r}>
- <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="arrow-right" color="white" size="sm" />
- </div>
- </Tooltip>
- </div>
- </div>
- <div className="FormattedTextBoxComment-preview-wrapper">
- <DocumentView
- Document={target}
- moveDocument={returnFalse}
- rootSelected={returnFalse}
- ScreenToLocalTransform={Transform.Identity}
- parentActive={returnFalse}
- addDocument={returnFalse}
- removeDocument={returnFalse}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- dontRegisterView={true}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionDoc={undefined}
- ContainingCollectionView={undefined}
- renderDepth={-1}
- PanelWidth={() => 175} //Math.min(350, NumCast(target._width, 350))}
- PanelHeight={() => 175} //Math.min(250, NumCast(target._height, 250))}
- focus={emptyFunction}
- whenActiveChanged={returnFalse}
- bringToFront={returnFalse}
- NativeWidth={Doc.NativeWidth(target) ? (() => Doc.NativeWidth(target)) : undefined}
- NativeHeight={Doc.NativeHeight(target) ? (() => Doc.NativeHeight(target)) : undefined}
- />
- </div>
- </div>;
-
- FormattedTextBoxComment.showCommentbox("", view, nbef);
-
- ReactDOM.render(docPreview, FormattedTextBoxComment.tooltipText);
-
- //FormattedTextBoxComment.tooltip.style.width = "100%";
- FormattedTextBoxComment.tooltip.style.height = "100%";
- }
- }
- });
- }
- set = "";
- }
+ nbef && naft && LinkDocPreview.SetLinkInfo({
+ docProps: textBox.props,
+ linkSrc: textBox.rootDoc,
+ location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - nbef)),
+ hrefs,
+ showHeader: true
+ });
}
- FormattedTextBoxComment.showCommentbox(set, view, nbef);
}
destroy() { }
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index dc630af74..5da868281 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -806,7 +806,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
<p>Linked to:</p>
<input value={link} ref={this._linkToRef} placeholder="Enter URL" onChange={onLinkChange} />
<button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "add:right")}>Apply hyperlink</button>
- <div className="divider"></div>
+ <div className="divider" />
<button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
</div>;
@@ -819,7 +819,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const node = this.view.state.selection.$from.nodeAfter;
const link = node && node.marks.find(m => m.type.name === "link");
if (link) {
- const href = link.attrs.allLinks.length > 0 ? link.attrs.allLinks[0].href : undefined;
+ const href = link.attrs.allAnchors.length > 0 ? link.attrs.allAnchors[0].href : undefined;
if (href) {
if (href.indexOf(Utils.prepend("/doc/")) === 0) {
const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
@@ -851,21 +851,21 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// TODO: should check for valid URL
@undoBatch
makeLinkToURL = (target: string, lcoation: string) => {
- ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRadd:rightight", "", target);
+ ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target);
}
@undoBatch
@action
deleteLink = () => {
if (this.view) {
- const link = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor);
- if (link) {
- const allLinks = link.attrs.allLinks.slice();
- this.TextView.RemoveLinkFromSelection(link.attrs.allLinks);
+ const linkAnchor = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor);
+ if (linkAnchor) {
+ const allAnchors = linkAnchor.attrs.allAnchors.slice();
+ this.TextView.RemoveAnchorFromSelection(allAnchors);
// bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected.
- allLinks.filter((aref: any) => aref?.href.indexOf(Utils.prepend("/doc/")) === 0).forEach((aref: any) => {
- const linkId = aref.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
- linkId && DocServer.GetRefField(linkId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
+ allAnchors.filter((aref: any) => aref?.href.indexOf(Utils.prepend("/doc/")) === 0).forEach((aref: any) => {
+ const anchorId = aref.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
});
}
}
@@ -877,8 +877,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
let startIndex = $start.index();
let endIndex = $start.indexAfter();
- while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allLinks.find((item: { href: string }) => item.href === href)).length) startIndex--;
- while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allLinks.find((item: { href: string }) => item.href === href)).length) endIndex++;
+ while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) startIndex--;
+ while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) endIndex++;
let startPos = $start.start();
let endPos = startPos;
diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx
index abbb89780..2252de3d6 100644
--- a/src/client/views/nodes/formattedText/RichTextSchema.tsx
+++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx
@@ -138,6 +138,7 @@ export class DashDocView {
removeDocument={removeDoc}
layerProvider={this._textBox.props.layerProvider}
styleProvider={this._textBox.props.styleProvider}
+ docViewPath={this._textBox.props.docViewPath}
ScreenToLocalTransform={this.getDocTransform}
addDocTab={this._textBox.props.addDocTab}
pinToPres={returnFalse}
diff --git a/src/client/views/nodes/formattedText/TooltipTextMenu.scss b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
index e2149e9c1..0e4b752ac 100644
--- a/src/client/views/nodes/formattedText/TooltipTextMenu.scss
+++ b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
@@ -103,7 +103,7 @@
}
.tooltipMenu, .basic-tools {
- z-index: 20000;
+ z-index: 3000;
pointer-events: all;
padding: 3px;
padding-bottom: 5px;
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index ea239e4d3..655ee7e44 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -22,8 +22,7 @@ export const marks: { [index: string]: MarkSpec } = {
// element.
linkAnchor: {
attrs: {
- allLinks: { default: [] as { href: string, title: string, linkId: string, targetId: string }[] },
- showPreview: { default: true },
+ allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] },
location: { default: null },
title: { default: null },
docref: { default: false } // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text
@@ -31,17 +30,23 @@ export const marks: { [index: string]: MarkSpec } = {
inclusive: false,
parseDOM: [{
tag: "a[href]", getAttrs(dom: any) {
- return { allLinks: [{ href: dom.getAttribute("href"), title: dom.getAttribute("title"), linkId: dom.getAttribute("linkids"), targetId: dom.dataset.targetids }], location: dom.getAttribute("location"), };
+ return {
+ location: dom.getAttribute("location"),
+ title: dom.getAttribute("title")
+ };
}
}],
toDOM(node: any) {
- const targetids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.targetId, "");
- const targethrefs = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.href, "");
- const linkids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.linkId, "");
+ const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.href : item.href, "");
+ const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.anchorId : item.anchorId, "");
return node.attrs.docref && node.attrs.title ?
- ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, href: node.attrs.allLinks[0].href, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] :
+ ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", {
+ ...node.attrs,
+ class: "prosemirror-attribution",
+ href: node.attrs.allAnchors[0].href,
+ }, node.attrs.title], ["br"]] :
//node.attrs.allLinks.length === 1 ?
- ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, "data-targethrefs": targethrefs, title: `${node.attrs.title}`, href: node.attrs.allLinks[0]?.href, style: `text-decoration: ${linkids === " " ? "underline" : undefined}` }, 0];
+ ["a", { class: anchorids, "data-targethrefs": targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0];
// ["div", { class: "prosemirror-anchor" },
// ["span", { class: "prosemirror-linkBtn" },
// ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0],
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 722c0a836..5d9c8b56d 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -22,6 +22,8 @@ export const nodes: { [index: string]: NodeSpec } = {
content: "block+"
},
+ paragraph: ParagraphNodeSpec,
+
audiotag: {
group: "block",
attrs: {
@@ -64,8 +66,6 @@ export const nodes: { [index: string]: NodeSpec } = {
parseDOM: [{ tag: "footnote" }]
},
- paragraph: ParagraphNodeSpec,
-
// :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
blockquote: {
content: "block*",
diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js
index 746c93868..5bc323a4d 100644
--- a/src/client/views/nodes/formattedText/prosemirrorPatches.js
+++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js
@@ -143,17 +143,21 @@ function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWith
// :: ([Mark]) → ?Mark
// Tests whether there is a mark of this type in the given set.
function isInSetWithAttrs(mark, set, attrs) {
+ var markAllAnchors = attrs.allAnchors !== undefined ? Array.from(attrs.allAnchors.map(al => al.anchorId)) : [];
for (var i = 0; i < set.length; i++) {
if (set[i].type == mark) {
- if (Array.from(Object.keys(attrs)).reduce((p, akey) => {
+ return Array.from(Object.keys(attrs)).reduce((p, akey) => {
if (p && JSON.stringify(set[i].attrs[akey]) === JSON.stringify(attrs[akey])) return true;
- set[i].attrs.allLinks = Array.from(set[i].attrs.allLinks).filter(a => !Array.from(attrs.allLinks.map(al => al.targetId)).includes(a.targetId) || !Array.from(attrs.allLinks.map(al => al.linkId).includes(a.linkId)))
+ // bcz: hack to allow special case of anchors in Dash to be removed from a mark which has multiple
+ if (set[i].attrs.allAnchors !== undefined) {
+ set[i].attrs.allAnchors = set[i].attrs.allAnchors.filter(a => !markAllAnchors.includes(a.anchorId));
+ if (set[i].attrs.allAnchors.length) return undefined;
+ }
return false;
- }, true)) {
- return set[i];
- }
+ }, true);
}
}
+ return undefined;
};
// :: (number, number, ?union<Mark, MarkType>) → this
@@ -170,7 +174,9 @@ function removeMarkWithAttrs(tr, from, to, mark, attrs) {
step++;
var toRemove = null;
if (mark) {
- if (isInSetWithAttrs(mark, node.marks, attrs)) { toRemove = [mark]; }
+ const inset = isInSetWithAttrs(mark, node.marks, attrs);
+ if (inset === true) { toRemove = [mark]; }
+ // if (inset === undefined) { console.log("lightened") } // anchorids were removed from the mark, but the mark wasn't removed since there are still anchorsids left
} else {
toRemove = node.marks;
}