aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorvkalev <vjk1883@gmail.com>2021-07-31 15:37:52 -0400
committervkalev <vjk1883@gmail.com>2021-07-31 15:37:52 -0400
commit193b90b9ade89b2060c701009ba6e57daf0c8c53 (patch)
treec8d05d1161cf5bf9869af6a0d6cd464d52303f4c /src
parent09e5f2c58ca87ded318627cbc5dea436989fcff2 (diff)
parent970ea305fde86c6d33fc08c8880ef258b7b318bc (diff)
Merge branch 'master' into ink-gfx-victor
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts11
-rw-r--r--src/client/documents/DocumentTypes.ts4
-rw-r--r--src/client/documents/Documents.ts14
-rw-r--r--src/client/util/DocumentManager.ts4
-rw-r--r--src/client/util/DragManager.ts16
-rw-r--r--src/client/util/LinkManager.ts38
-rw-r--r--src/client/util/Scripting.ts2
-rw-r--r--src/client/util/SelectionManager.ts3
-rw-r--r--src/client/views/AntimodeMenu.scss1
-rw-r--r--src/client/views/LightboxView.scss30
-rw-r--r--src/client/views/LightboxView.tsx22
-rw-r--r--src/client/views/MarqueeAnnotator.tsx10
-rw-r--r--src/client/views/SidebarAnnos.tsx2
-rw-r--r--src/client/views/StyleProvider.tsx3
-rw-r--r--src/client/views/collections/CollectionLinearView.scss23
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx10
-rw-r--r--src/client/views/collections/CollectionSubView.tsx2
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx12
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx4
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx21
-rw-r--r--src/client/views/linking/LinkEditor.scss8
-rw-r--r--src/client/views/linking/LinkMenu.scss26
-rw-r--r--src/client/views/linking/LinkMenu.tsx8
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx2
-rw-r--r--src/client/views/linking/LinkMenuItem.scss8
-rw-r--r--src/client/views/linking/LinkPopup.scss45
-rw-r--r--src/client/views/linking/LinkPopup.tsx114
-rw-r--r--src/client/views/nodes/DocumentLinksButton.scss1
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx22
-rw-r--r--src/client/views/nodes/DocumentView.scss2
-rw-r--r--src/client/views/nodes/ImageBox.tsx2
-rw-r--r--src/client/views/nodes/WebBox.tsx3
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx3
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx1
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx18
-rw-r--r--src/client/views/pdf/PDFViewer.tsx3
-rw-r--r--src/fields/Doc.ts33
38 files changed, 383 insertions, 150 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index ef9c51b8b..d87c3cc6b 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -191,11 +191,12 @@ export namespace Utils {
return { h: h, s: s, l: l };
}
- export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number) {
- if (scrollTop + contextHgt < targetY + targetHgt * 1.1) {
- return Math.ceil(targetY + targetHgt * 1.1 - contextHgt);
- } else if (scrollTop > targetY - targetHgt * .1) {
- return Math.max(0, Math.floor(targetY - targetHgt * .1));
+ export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number, minSpacing: number) {
+ if (scrollTop + contextHgt < targetY + minSpacing + targetHgt) {
+ return Math.ceil(targetY + minSpacing + targetHgt - contextHgt);
+ }
+ if (scrollTop > targetY - minSpacing - targetHgt) {
+ return Math.max(0, Math.floor(targetY - minSpacing - targetHgt));
}
}
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 8565784b4..dba7ff907 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -18,7 +18,7 @@ export enum DocumentType {
LABEL = "label", // simple text label
BUTTON = "button", // onClick button
WEBCAM = "webcam", // webcam
- HTMLANCHOR = "htmlanchor", // text selection anchor in PDF/Web
+ MARKER = "marker", // generic marker document not intended to be viewed independently of its context (e.g., for text selections in PDF/Web/RTF)
DATE = "date", // calendar view of a date
SCRIPTING = "script", // script editor
EQUATION = "equation", // equation editor
@@ -40,6 +40,4 @@ export enum DocumentType {
LINKDB = "linkdb", // database of links ??? why do we have this
SCRIPTDB = "scriptdb", // database of scripts
GROUPDB = "groupdb", // database of groups
-
- TEXTANCHOR = "textanchor" // selection of text in a text box
} \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f1db3e32c..e863b4198 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -427,7 +427,7 @@ export namespace Docs {
[DocumentType.PRESELEMENT, {
layout: { view: PresElementBox, dataField: defaultDataKey }
}],
- [DocumentType.HTMLANCHOR, {
+ [DocumentType.MARKER, {
layout: { view: CollectionView, dataField: defaultDataKey },
options: { links: ComputedField.MakeFunction("links(self)") as any, hideLinkButton: true }
}],
@@ -452,10 +452,6 @@ export namespace Docs {
layout: { view: EmptyBox, dataField: defaultDataKey },
options: { links: ComputedField.MakeFunction("links(self)") as any }
}],
- [DocumentType.TEXTANCHOR, {
- layout: { view: EmptyBox, dataField: defaultDataKey },
- options: { targetDropAction: "move", links: ComputedField.MakeFunction("links(self)") as any, hideLinkButton: true }
- }]
]);
const suffix = "Proto";
@@ -670,9 +666,9 @@ export namespace Docs {
viewProps["acl-Override"] = "None";
viewProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add;
const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewProps, true, true);
- ![DocumentType.LINK, DocumentType.TEXTANCHOR, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc);
+ ![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc);
- !Doc.IsSystem(dataDoc) && ![DocumentType.HTMLANCHOR, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR, DocumentType.TEXTANCHOR].includes(proto.type as any) &&
+ !Doc.IsSystem(dataDoc) && ![DocumentType.MARKER, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(proto.type as any) &&
!dataDoc.isFolder && !dataProps.annotationOn && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", dataDoc);
return viewDoc;
@@ -802,7 +798,7 @@ export namespace Docs {
}
export function TextanchorDocument(options: DocumentOptions = {}, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.TEXTANCHOR), undefined, options, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.MARKER), undefined, options, id);
}
export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
@@ -811,7 +807,7 @@ export namespace Docs {
return inst;
}
export function HTMLAnchorDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.HTMLANCHOR), new List(documents), options, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.MARKER), new List(documents), options, id);
}
export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 304215a8f..5b092258a 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -28,7 +28,7 @@ export class DocumentManager {
DocListCast(view.rootDoc.links).forEach(link => {
const whichOtherAnchor = view.props.LayoutTemplateString?.includes("anchor2") ? "anchor1" : "anchor2";
const otherDoc = link && (link[whichOtherAnchor] as Doc);
- const otherDocAnno = otherDoc?.type === DocumentType.TEXTANCHOR ? otherDoc.annotationOn as Doc : undefined;
+ const otherDocAnno = DocumentType.MARKER === otherDoc?.type ? otherDoc.annotationOn as Doc : undefined;
otherDoc && DocumentManager.Instance.DocumentViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, otherDoc) || Doc.AreProtosEqual(dv.rootDoc, otherDocAnno)).
forEach(otherView => {
if (otherView.rootDoc.type !== DocumentType.LINK || otherView.props.LayoutTemplateString !== view.props.LayoutTemplateString) {
@@ -162,7 +162,7 @@ export class DocumentManager {
const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext : undefined;
const targetDocContext = contextDoc || annotatedDoc;
const targetDocContextView = targetDocContext && getFirstDocView(targetDocContext);
- const focusView = !docView && targetDoc.type === DocumentType.TEXTANCHOR && annoContainerView ? annoContainerView : docView;
+ const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView;
if (!docView && annoContainerView && !focusView) {
annoContainerView.focus(targetDoc); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
}
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index ab58f25e9..c4842e88a 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -210,16 +210,16 @@ export namespace DragManager {
dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc);
return dropDoc;
};
- const finishDrag = (e: DragCompleteEvent) => {
+ const finishDrag = async (e: DragCompleteEvent) => {
const docDragData = e.docDragData;
dropEvent?.(); // glr: optional additional function to be called - in this case with presentation trails
if (docDragData && !docDragData.droppedDocuments.length) {
docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
docDragData.droppedDocuments =
- dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) :
+ await Promise.all(dragData.draggedDocuments.map(async d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) :
docDragData.dropAction === "alias" ? Doc.MakeAlias(d) :
docDragData.dropAction === "proto" ? Doc.GetProto(d) :
- docDragData.dropAction === "copy" ? Doc.MakeClone(d) : d);
+ docDragData.dropAction === "copy" ? (await Doc.MakeClone(d)).clone : d));
!["same", "proto"].includes(docDragData.dropAction as any) && docDragData.droppedDocuments.forEach((drop: Doc, i: number) => {
const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []);
const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps));
@@ -427,7 +427,7 @@ export namespace DragManager {
SnappingManager.clearSnapLines();
batch.end();
});
- const moveHandler = (e: PointerEvent) => {
+ const moveHandler = async (e: PointerEvent) => {
e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
if (dragData instanceof DocumentDragData) {
dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : dragData.defaultDropAction;
@@ -438,7 +438,7 @@ export namespace DragManager {
dragData.removeDocument?.(dragData.draggedDocuments[0]);
}
AbortDrag();
- finishDrag?.(new DragCompleteEvent(true, dragData));
+ await finishDrag?.(new DragCompleteEvent(true, dragData));
DragManager.StartWindowDrag?.({
pageX: e.pageX,
pageY: e.pageY,
@@ -509,7 +509,7 @@ export namespace DragManager {
`translate(${(xs[i] += moveVec.x) + (options?.offsetX || 0)}px, ${(ys[i] += moveVec.y) + (options?.offsetY || 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`)
);
};
- const upHandler = (e: PointerEvent) => {
+ const upHandler = async (e: PointerEvent) => {
dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options);
endDrag();
};
@@ -517,7 +517,7 @@ export namespace DragManager {
document.addEventListener("pointerup", upHandler);
}
- function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number, y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions) {
+ async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number, y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions) {
const dropArgs = {
bubbles: true,
detail: {
@@ -531,7 +531,7 @@ export namespace DragManager {
}
};
target.dispatchEvent(new CustomEvent<DropEvent>("dashPreDrop", dropArgs));
- finishDrag?.(complete);
+ await finishDrag?.(complete);
target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", dropArgs));
options?.dragComplete?.(complete);
}
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 3c3d5c3b8..08f4ac9b7 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,15 +1,14 @@
+import { observable, observe, action } from "mobx";
import { computedFn } from "mobx-utils";
-import { Doc, DocListCast, Opt, DirectLinksSym, Field } from "../../fields/Doc";
-import { BoolCast, Cast, StrCast, PromiseValue } from "../../fields/Types";
+import { DirectLinksSym, Doc, DocListCast, Field, Opt } from "../../fields/Doc";
+import { List } from "../../fields/List";
+import { ProxyField } from "../../fields/Proxy";
+import { BoolCast, Cast, PromiseValue, StrCast } from "../../fields/Types";
import { LightboxView } from "../views/LightboxView";
import { DocumentViewSharedProps, ViewAdjustment } from "../views/nodes/DocumentView";
import { DocumentManager } from "./DocumentManager";
import { SharingManager } from "./SharingManager";
import { UndoManager } from "./UndoManager";
-import { observe, observable, reaction } from "mobx";
-import { listSpec } from "../../fields/Schema";
-import { List } from "../../fields/List";
-import { ProxyField } from "../../fields/Proxy";
type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void;
/*
@@ -34,7 +33,7 @@ export class LinkManager {
LinkManager._instance = this;
setTimeout(() => {
LinkManager.userDocs = [Doc.LinkDBDoc().data as Doc, ...SharingManager.Instance.users.map(user => user.linkDatabase)];
- const addLinkToDoc = (link: Doc): any => {
+ const addLinkToDoc = action((link: Doc): any => {
const a1 = link?.anchor1;
const a2 = link?.anchor2;
if (a1 instanceof Promise || a2 instanceof Promise) return PromiseValue(a1).then(a1 => PromiseValue(a2).then(a2 => addLinkToDoc(link)));
@@ -43,8 +42,8 @@ export class LinkManager {
Doc.GetProto(a2)[DirectLinksSym].add(link);
Doc.GetProto(link)[DirectLinksSym].add(link);
}
- };
- const remLinkFromDoc = (link: Doc): any => {
+ });
+ const remLinkFromDoc = action((link: Doc): any => {
const a1 = link?.anchor1;
const a2 = link?.anchor2;
if (a1 instanceof Promise || a2 instanceof Promise) return PromiseValue(a1).then(a1 => PromiseValue(a2).then(a2 => remLinkFromDoc(link)));
@@ -53,7 +52,7 @@ export class LinkManager {
Doc.GetProto(a2)[DirectLinksSym].delete(link);
Doc.GetProto(link)[DirectLinksSym].delete(link);
}
- };
+ });
const watchUserLinks = (userLinks: List<Doc>) => {
const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field; // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields
observe(userLinks, change => {
@@ -75,8 +74,10 @@ export class LinkManager {
});
}
- public addLink(linkDoc: Doc) {
- return Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc);
+ public addLink(linkDoc: Doc, checkExists = false) {
+ if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) {
+ Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc);
+ }
}
public deleteLink(linkDoc: Doc) { return Doc.RemoveDocFromList(Doc.LinkDBDoc(), "data", linkDoc); }
public deleteAllLinksOnAnchor(anchor: Doc) { LinkManager.Instance.relatedLinker(anchor).forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc)); }
@@ -85,19 +86,6 @@ export class LinkManager {
public getAllDirectLinks(anchor: Doc): Doc[] {
return Array.from(Doc.GetProto(anchor)[DirectLinksSym]);
} // finds all links that contain the given anchor
- public getAllLinks(): Doc[] { return []; }//this.allLinks(); }
-
- // allLinks = computedFn(function allLinks(this: any): Doc[] {
- // const linkData = Doc.LinkDBDoc().data;
- // const lset = new Set<Doc>(DocListCast(linkData));
- // SharingManager.Instance.users.forEach(user => DocListCast(user.linkDatabase?.data).forEach(doc => lset.add(doc)));
- // LinkManager.Instance.allLinks().filter(link => {
- // const a1 = Cast(link?.anchor1, Doc, null);
- // const a2 = Cast(link?.anchor2, Doc, null);
- // return link && ((a1?.author !== undefined && a2?.author !== undefined) || link.author === Doc.CurrentUserEmail) && (Doc.AreProtosEqual(anchor, a1) || Doc.AreProtosEqual(anchor, a2) || Doc.AreProtosEqual(link, anchor));
- // });
- // return Array.from(lset);
- // }, true);
relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
const lfield = Doc.LayoutFieldKey(anchor);
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index c3c3083be..f981f84cd 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -181,7 +181,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
if (batch) {
batch.end();
}
- onError?.(error);
+ onError?.(script + " " + error);
return { success: false, error, result: errorVal };
}
};
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 00f0894c7..dbcc49f3d 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -4,6 +4,7 @@ import { Doc, Opt } from "../../fields/Doc";
import { CollectionSchemaView } from "../views/collections/collectionSchema/CollectionSchemaView";
import { CollectionViewType } from "../views/collections/CollectionView";
import { DocumentView } from "../views/nodes/DocumentView";
+import { DocumentType } from "../documents/DocumentTypes";
export namespace SelectionManager {
@@ -22,7 +23,7 @@ export namespace SelectionManager {
@action
SelectView(docView: DocumentView, ctrlPressed: boolean): void {
// if doc is not in SelectedDocuments, add it
- if (!manager.SelectedViews.get(docView)) {
+ if (!manager.SelectedViews.get(docView) && docView.props.Document.type !== DocumentType.MARKER) {
if (!ctrlPressed) {
this.DeselectAll();
}
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
index 2bac03af4..0e3ac4716 100644
--- a/src/client/views/AntimodeMenu.scss
+++ b/src/client/views/AntimodeMenu.scss
@@ -8,7 +8,6 @@
background: $dark-gray;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
// border-radius: 0px 6px 6px 6px;
- z-index: 1001;
display: flex;
&.with-rows {
diff --git a/src/client/views/LightboxView.scss b/src/client/views/LightboxView.scss
index 4ea2dc2d6..5d42cd97f 100644
--- a/src/client/views/LightboxView.scss
+++ b/src/client/views/LightboxView.scss
@@ -1,3 +1,32 @@
+
+ .lightboxView-navBtn {
+ margin: auto;
+ position: absolute;
+ right: 10;
+ top: 10;
+ background: transparent;
+ border-radius: 8;
+ color:white;
+ opacity: 0.7;
+ width: 35;
+ &:hover {
+ opacity: 1;
+ }
+ }
+ .lightboxView-tabBtn {
+ margin: auto;
+ position: absolute;
+ right: 35;
+ top: 10;
+ background: transparent;
+ border-radius: 8;
+ color:white;
+ opacity: 0.7;
+ width: 35;
+ &:hover {
+ opacity: 1;
+ }
+ }
.lightboxView-frame {
position: absolute;
top: 0; left: 0;
@@ -15,7 +44,6 @@
position: relative;
background: transparent;
border-radius: 8;
- color:white;
opacity: 0.7;
width: 35;
&:hover {
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index ce36d9182..88739fe91 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -1,19 +1,20 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, trace } from 'mobx';
+import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
import { Cast, NumCast, StrCast } from '../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue, returnFalse } from '../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils';
import { DocUtils } from '../documents/Documents';
import { DocumentManager } from '../util/DocumentManager';
import { LinkManager } from '../util/LinkManager';
import { SelectionManager } from '../util/SelectionManager';
import { Transform } from '../util/Transform';
+import { CollectionDockingView } from './collections/CollectionDockingView';
import { TabDocView } from './collections/TabDocView';
import "./LightboxView.scss";
-import { DocumentView, ViewAdjustment } from './nodes/DocumentView';
+import { DocumentView } from './nodes/DocumentView';
import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider';
interface LightboxViewProps {
@@ -160,7 +161,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
const { doc, target } = LightboxView._history?.lastElement();
const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc);
if (docView) {
- LightboxView._docTarget = undefined;
+ LightboxView._docTarget = target;
const focusSpeed = 1000;
doc._viewTransition = `transform ${focusSpeed}ms`;
if (!target) docView.ComponentView?.shrinkWrap?.();
@@ -197,7 +198,6 @@ export class LightboxView extends React.Component<LightboxViewProps> {
TabDocView.PinDoc(coll, { hidePresBox: true });
}
}
- setTimeout(LightboxView.Next);
}
future = () => LightboxView._future;
@@ -228,7 +228,6 @@ export class LightboxView extends React.Component<LightboxViewProps> {
const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target);
if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.();
else target && targetView?.focus(target, { willZoom: true, scale: 0.9, instant: true });
- LightboxView._docTarget = undefined;
}));
})}
Document={LightboxView.LightboxDoc}
@@ -270,7 +269,16 @@ export class LightboxView extends React.Component<LightboxViewProps> {
LightboxView.Next();
})}
<LightboxTourBtn navBtn={this.navBtn} future={this.future} stepInto={this.stepInto} tourMap={this.tourMap} />
- <div className="lightboxView-navBtn" title={"toggle fit width"} style={{ position: "absolute", right: 10, top: 10, color: "white" }}
+ <div className="lightboxView-tabBtn" title={"open in tab"}
+ onClick={e => {
+ e.stopPropagation();
+ CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, "onRight");
+ SelectionManager.DeselectAll();
+ LightboxView.SetLightboxDoc(undefined);
+ }}>
+ <FontAwesomeIcon icon={"file-download"} size="2x" />
+ </div>
+ <div className="lightboxView-navBtn" title={"toggle fit width"}
onClick={e => { e.stopPropagation(); LightboxView.LightboxDoc!._fitWidth = !LightboxView.LightboxDoc!._fitWidth; }}>
<FontAwesomeIcon icon={"arrows-alt-h"} size="2x" />
</div>
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 717bd0768..805cda95c 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -120,9 +120,11 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
return marqueeAnno;
}
- const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, title: "Selection on " + this.props.rootDoc.title, _width: 1, _height: 1 });
+ const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, backgroundColor: "transparent", title: "Selection on " + this.props.rootDoc.title });
+ let minX = Number.MAX_VALUE;
let maxX = -Number.MAX_VALUE;
let minY = Number.MAX_VALUE;
+ let maxY = -Number.MIN_VALUE;
const annoDocs: Doc[] = [];
savedAnnoMap.forEach((value: HTMLDivElement[], key: number) => value.map(anno => {
const textRegion = new Doc();
@@ -135,12 +137,16 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
annoDocs.push(textRegion);
anno.remove();
minY = Math.min(NumCast(textRegion.y), minY);
+ minX = Math.min(NumCast(textRegion.x), minX);
+ maxY = Math.max(NumCast(textRegion.y) + NumCast(textRegion._height), maxY);
maxX = Math.max(NumCast(textRegion.x) + NumCast(textRegion._width), maxX);
}));
const textRegionAnnoProto = Doc.GetProto(textRegionAnno);
textRegionAnnoProto.y = Math.max(minY, 0);
- textRegionAnnoProto.x = Math.max(maxX, 0);
+ textRegionAnnoProto.x = Math.max(minX, 0);
+ textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0);
+ textRegionAnnoProto.width = Math.max(maxX, 0) - Math.max(minX, 0);
// mainAnnoDocProto.text = this._selectionText;
textRegionAnnoProto.textInlineAnnotations = new List<Doc>(annoDocs);
savedAnnoMap.clear();
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 9c5a54574..c5ae82c61 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -79,7 +79,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
sidebarStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) => {
if (property === StyleProp.ShowTitle) {
- return doc === this.props.rootDoc ? 0 : StrCast(this.props.layoutDoc["sidebar-childShowTitle"], "title");
+ return doc === this.props.rootDoc ? undefined : StrCast(this.props.layoutDoc["sidebar-childShowTitle"], "title");
}
return this.props.styleProvider?.(doc, props, property);
}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 6b94539c9..c9e532745 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -101,7 +101,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
const backColor = backgroundCol();
if (!backColor) return undefined;
const nonAlphaColor = backColor.startsWith("#") ? (backColor as string).substring(0, 7) :
- backColor.startsWith("rgba") ? backColor.replace(/,.[^,]*\)/, ")").replace("rgba", "rgb") : backColor
+ backColor.startsWith("rgba") ? backColor.replace(/,.[^,]*\)/, ")").replace("rgba", "rgb") : backColor;
const col = Color(nonAlphaColor).rgb();
const colsum = (col.red() + col.green() + col.blue());
if (colsum / col.alpha() > 400 || col.alpha() < 0.25) return Colors.DARK_GRAY;
@@ -174,6 +174,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
}
}
case StyleProp.PointerEvents:
+ if (doc?.type === DocumentType.MARKER) return "none";
if (props?.pointerEvents === "none") return "none";
const layer = doc && props?.layerProvider?.(doc);
if (opacity() === 0 || (doc?.type === DocumentType.INK && !docProps?.treeViewDoc) || doc?.isInkMask) return "none";
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
index ec8805907..86610ac20 100644
--- a/src/client/views/collections/CollectionLinearView.scss
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -20,19 +20,21 @@
}
.bottomPopup-background {
- padding-right: 14px;
+ background: $light-blue;
+ display: flex;
height: 35;
- transform: translate3d(6px, 5px, 0px);
- padding-top: 6.5px;
- padding-bottom: 7px;
- padding-left: 5px;
+ transform: translate3d(6px, 0px, 0px);
+ align-content: center;
+ justify-content: center;
+ align-items: center;
}
.bottomPopup-text {
+ color: black;
display: inline;
white-space: nowrap;
padding-left: 8px;
- padding-right: 4px;
+ padding-right: 20px;
vertical-align: middle;
font-size: 12.5px;
}
@@ -43,8 +45,8 @@
padding-left: 8px;
padding-right: 8px;
vertical-align: middle;
- background-color: lightgrey;
- border-radius: 5.5px;
+ background-color: #efefef;
+ border-radius: 3px;
color: black;
margin-right: 5px;
}
@@ -52,11 +54,12 @@
.bottomPopup-exit {
display: inline;
white-space: nowrap;
+ margin-right: 10px;
padding-left: 8px;
padding-right: 8px;
vertical-align: middle;
- background-color: lightgrey;
- border-radius: 5.5px;
+ background-color: #f3b6b6;
+ border-radius: 3px;
color: black;
}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index e0b90304b..52c836556 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -167,24 +167,22 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
})}
</div>
{DocumentLinksButton.StartLink ? <span className="bottomPopup-background" style={{
- background: backgroundColor === color ? "black" : backgroundColor,
pointerEvents: "all"
}}
onPointerDown={e => e.stopPropagation()} >
<span className="bottomPopup-text" >
- Creating link from: {DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'}
+ Creating link from: <b>{DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'}</b>
</span>
- <Tooltip title={<><div className="dash-tooltip">{LinkDescriptionPopup.showDescriptions ? "Turn off description pop-up" :
- "Turn on description pop-up"} </div></>} placement="top">
+ <Tooltip title={<><div className="dash-tooltip">{"Toggle description pop-up"} </div></>} placement="top">
<span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
</span>
</Tooltip>
- <Tooltip title={<><div className="dash-tooltip">Exit link clicking mode </div></>} placement="top">
+ <Tooltip title={<><div className="dash-tooltip">Exit linking mode</div></>} placement="top">
<span className="bottomPopup-exit" onClick={this.exitLongLinks}>
- Clear
+ Stop
</span>
</Tooltip>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index f39443ae2..a5d27f038 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -454,7 +454,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
if (completed) completed(set);
else {
if (isFreeformView && generatedDocuments.length > 1) {
- addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!);
+ addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!));
} else {
generatedDocuments.forEach(addDocument);
}
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index f41043179..339163510 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -37,7 +37,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
@observable _focusRangeFilters: Opt<string[]>;
getAnchor = () => {
- const anchor = Docs.Create.TextanchorDocument({
+ const anchor = Docs.Create.HTMLAnchorDocument({
title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any,
annotationOn: this.rootDoc
});
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a4e310e6c..8ef0057bd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -48,6 +48,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso
import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
+import { DocumentType } from "../../../documents/DocumentTypes";
export const panZoomSchema = createSchema({
_panX: "number",
@@ -834,10 +835,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
map(doc => ({ ...this.childDataProvider(doc, ""), ...this.childSizeProvider(doc, "") }));
if (measuredDocs.length) {
const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) => // computes range of content
- ({
- xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) },
- yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) }
- })
+ ({
+ xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) },
+ yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) }
+ })
, {
xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }
@@ -1486,7 +1487,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
onDragOver={e => e.preventDefault()}
onContextMenu={this.onContextMenu}
style={{
- pointerEvents: this.backgroundEvents ? "all" : this.props.pointerEvents as any,
+ pointerEvents: this.props.Document.type === DocumentType.MARKER ? "none" : // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach.
+ this.backgroundEvents ? "all" : this.props.pointerEvents as any,
transform: `scale(${this.contentScaling || 1})`,
width: `${100 / (this.contentScaling || 1)}%`,
height: this.isAnnotationOverlay && this.Document.scrollHeight ? this.Document.scrollHeight : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b1f2750c3..1f4fcb2a5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -368,8 +368,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
SelectionManager.DeselectAll();
selected.forEach(d => this.props.removeDocument?.(d));
const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2);
- this.props.addDocument?.(newCollection!);
- this.props.selectDocuments([newCollection!]);
+ this.props.addDocument?.(newCollection);
+ this.props.selectDocuments([newCollection]);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
index 90f64f163..fd99abce5 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
@@ -103,6 +103,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
this.props.setPreviewDoc(this.props.rowProps.original);
+ console.log("click cell");
let url: string;
if (url = StrCast(this.props.rowProps.row.href)) {
try {
@@ -246,13 +247,13 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
} else {
// check if the input is a number
let inputIsNum = true;
- for (let s of value) {
- if (isNaN(parseInt(s)) && !(s == ".") && !(s == ",")) {
+ for (const s of value) {
+ if (isNaN(parseInt(s)) && !(s === ".") && !(s === ",")) {
inputIsNum = false;
}
}
// check if the input is a boolean
- let inputIsBool: boolean = value == "false" || value == "true";
+ const inputIsBool: boolean = value === "false" || value === "true";
// what to do in the case
if (!inputIsNum && !inputIsBool && !value.startsWith("=")) {
// if it's not a number, it's a string, and should be processed as such
@@ -263,12 +264,12 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const vsqLength = valueSansQuotes.length;
// get rid of outer quotes
valueSansQuotes = valueSansQuotes.substring(value.startsWith("\"") ? 1 : 0,
- valueSansQuotes.charAt(vsqLength - 1) == "\"" ? vsqLength - 1 : vsqLength);
+ valueSansQuotes.charAt(vsqLength - 1) === "\"" ? vsqLength - 1 : vsqLength);
}
let inputAsString = '"';
// escape any quotes in the string
for (const i of valueSansQuotes) {
- if (i == '"') {
+ if (i === '"') {
inputAsString += '\\"';
} else {
inputAsString += i;
@@ -278,7 +279,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
inputAsString += '"';
//two options here: we can strip off outer quotes or we can figure out what's going on with the script
const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
- const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length
+ const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length;
script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
// handle numbers and expressions
} else if (inputIsNum || value.startsWith("=")) {
@@ -286,18 +287,18 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const inputscript = value.substring(value.startsWith("=") ? 1 : 0);
// if commas are not stripped, the parser only considers the numbers after the last comma
let inputSansCommas = "";
- for (let s of inputscript) {
- if (!(s == ",")) {
+ for (const s of inputscript) {
+ if (!(s === ",")) {
inputSansCommas += s;
}
}
const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
- const changeMade = value.length !== value.length || value.length - 2 !== value.length
+ const changeMade = value.length !== value.length || value.length - 2 !== value.length;
script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
// handle booleans
} else if (inputIsBool) {
const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
- const changeMade = value.length !== value.length || value.length - 2 !== value.length
+ const changeMade = value.length !== value.length || value.length - 2 !== value.length;
script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
}
}
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index 839ebf894..e45a91d57 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -22,7 +22,7 @@
.linkEditor-info {
//border-bottom: 0.5px solid $light-gray;
//padding-bottom: 1px;
- padding-top: 5px;
+ padding: 12px;
padding-left: 5px;
//margin-bottom: 6px;
display: flex;
@@ -61,7 +61,7 @@
}
.linkEditor-description {
- padding-left: 6.5px;
+ padding-left: 26px;
padding-right: 6.5px;
padding-bottom: 3.5px;
@@ -107,9 +107,9 @@
}
.linkEditor-followingDropdown {
- padding-left: 6.5px;
+ padding-left: 26px;
padding-right: 6.5px;
- padding-bottom: 6px;
+ padding-bottom: 15px;
&:hover {
cursor: pointer;
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index a2ea42999..19c6463d3 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -7,20 +7,19 @@
z-index: 2001;
.linkMenu-list,
- .linkMenu-listEditor
- {
+ .linkMenu-listEditor {
display: inline-block;
position: relative;
- border: 1px solid black;
- box-shadow: 3px 3px 1.5px grey;
+ border: 1px solid #e4e4e4;
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
background: white;
-
min-width: 170px;
- max-height: 170px;
+ max-height: 230px;
overflow-y: scroll;
z-index: 10;
- }
- .linkMenu-list {
+ }
+
+ .linkMenu-list {
white-space: nowrap;
overflow-x: hidden;
width: 240px;
@@ -46,13 +45,13 @@
}
.linkMenu-group-name {
+ padding: 10px;
&:hover {
- p {
- background-color: lightgray;
-
- }
+ // p {
+ // background-color: lightgray;
+ // }
p.expand-one {
width: calc(100% + 20px);
@@ -65,10 +64,9 @@
p {
width: 100%;
- //padding: 4px 6px;
line-height: 12px;
border-radius: 5px;
- font-weight: bold;
+ text-transform: capitalize;
}
.linkEditor-tableButton {
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index c7888c5ee..6fc860447 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -15,6 +15,9 @@ interface Props {
changeFlyout: () => void;
}
+/**
+ * the outermost component for the link menu of a node that contains a list of its linked nodes
+ */
@observer
export class LinkMenu extends React.Component<Props> {
private _editorRef = React.createRef<HTMLDivElement>();
@@ -36,6 +39,11 @@ export class LinkMenu extends React.Component<Props> {
}
}
+ /**
+ * maps each link to a JSX element to be rendered
+ * @param groups LinkManager containing info of all of the links
+ * @returns list of link JSX elements if there at least one linked element
+ */
renderAllGroups = (groups: Map<string, Array<Doc>>): Array<JSX.Element> => {
const linkItems = Array.from(groups.entries()).map(group =>
<LinkMenuGroup
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 74af78234..c7586a467 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -40,7 +40,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
return (
<div className="linkMenu-group" ref={this._menuRef}>
<div className="linkMenu-group-name">
- <p className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
+ <p className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"}> {this.props.groupType}:</p>
</div>
<div className="linkMenu-group-wrapper">
{groupItems}
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index 4f9881565..90722daf9 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -4,7 +4,7 @@
// border-top: 0.5px solid $medium-gray;
position: relative;
display: flex;
- border-bottom: 0.5px solid black;
+ border-top: 0.5px solid #cdcdcd;
padding-left: 6.5px;
padding-right: 2px;
@@ -55,8 +55,8 @@
.linkMenu-destination-title {
text-decoration: none;
- color: rgb(85, 120, 196);
- font-size: 14px;
+ color: #4476F7;
+ font-size: 16px;
padding-bottom: 2px;
padding-right: 4px;
margin-right: 4px;
@@ -76,7 +76,7 @@
text-decoration: none;
font-style: italic;
color: rgb(95, 97, 102);
- font-size: 10px;
+ font-size: 9px;
margin-left: 20px;
max-width: 125px;
height: auto;
diff --git a/src/client/views/linking/LinkPopup.scss b/src/client/views/linking/LinkPopup.scss
new file mode 100644
index 000000000..8ae65158d
--- /dev/null
+++ b/src/client/views/linking/LinkPopup.scss
@@ -0,0 +1,45 @@
+.linkPopup-container {
+ background: white;
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
+ top: 35px;
+ height: 200px;
+ width: 200px;
+ position: absolute;
+ padding: 15px;
+ border-radius: 3px;
+
+ input {
+ border: 1px solid #b9b9b9;
+ border-radius: 20px;
+ height: 25px;
+ width: 100%;
+ padding-left: 10px;
+ }
+
+ .divider {
+ margin: 10px 0;
+ height: 20px;
+ width: 100%;
+
+ .line {
+ height: 1px;
+ background-color: #b9b9b9;
+ width: 100%;
+ position: relative;
+ top: 12px;
+ }
+
+ .divider-text {
+ width: 20px;
+ background-color: white;
+ text-align: center;
+ position: relative;
+ margin: auto;
+ }
+ }
+
+
+ .searchBox-container {
+ background: pink;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
new file mode 100644
index 000000000..2c4b718f4
--- /dev/null
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -0,0 +1,114 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, observable, runInAction } from 'mobx';
+import { observer } from "mobx-react";
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { Cast, StrCast } from '../../../fields/Types';
+import { WebField } from '../../../fields/URLField';
+import { emptyFunction, setupMoveUpEvents, returnFalse, returnTrue, returnEmptyDoclist, returnEmptyFilter } from '../../../Utils';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DocumentManager } from '../../util/DocumentManager';
+import { DragManager } from '../../util/DragManager';
+import { Hypothesis } from '../../util/HypothesisUtils';
+import { LinkManager } from '../../util/LinkManager';
+import { undoBatch } from '../../util/UndoManager';
+import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
+import { DocumentView, DocumentViewSharedProps } from '../nodes/DocumentView';
+import { LinkDocPreview } from '../nodes/LinkDocPreview';
+import './LinkPopup.scss';
+import React = require("react");
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
+import { DefaultStyleProvider } from '../StyleProvider';
+import { Transform } from '../../util/Transform';
+import { DocUtils } from '../../documents/Documents';
+import { SearchBox } from '../search/SearchBox';
+import { EditorView } from 'prosemirror-view';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+
+interface LinkPopupProps {
+ showPopup: boolean;
+ // groupType: string;
+ // linkDoc: Doc;
+ // docView: DocumentView;
+ // sourceDoc: Doc;
+}
+
+/**
+ * Popup component for creating links from text to Dash documents
+ */
+
+@observer
+export class LinkPopup extends React.Component<LinkPopupProps> {
+ @observable private linkURL: string = "";
+ @observable public view?: EditorView;
+
+
+
+ // TODO: should check for valid URL
+ @undoBatch
+ makeLinkToURL = (target: string, lcoation: string) => {
+ ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target);
+ }
+
+ @action
+ onLinkChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.linkURL = e.target.value;
+ console.log(this.linkURL)
+ }
+
+
+ getPWidth = () => 500;
+ getPHeight = () => 500;
+
+ render() {
+ const popupVisibility = this.props.showPopup ? "block" : "none";
+ return (
+ <div className="linkPopup-container" style={{ display: popupVisibility }}>
+ <div className="linkPopup-url-container">
+ <input autoComplete="off" type="text" value={this.linkURL} placeholder="Enter URL..." onChange={this.onLinkChange} />
+ <button onPointerDown={e => this.makeLinkToURL(this.linkURL, "add:right")}
+ style={{ display: "block", margin: "10px auto", }}>Apply hyperlink</button>
+ </div>
+ <div className="divider">
+ <div className="line"></div>
+ <p className="divider-text">or</p>
+ </div>
+ <div className="linkPopup-document-search-container">
+ {/* <i></i>
+ <input defaultValue={""} autoComplete="off" type="text" placeholder="Search for Document..." id="search-input"
+ className="linkPopup-searchBox searchBox-input" /> */}
+
+ <SearchBox Document={CurrentUserUtils.MySearchPanelDoc}
+ DataDoc={CurrentUserUtils.MySearchPanelDoc}
+ fieldKey="data"
+ dropAction="move"
+ isSelected={returnTrue}
+ isContentActive={returnTrue}
+ select={returnTrue}
+ setHeight={returnFalse}
+ addDocument={undefined}
+ addDocTab={returnTrue}
+ pinToPres={emptyFunction}
+ rootSelected={returnTrue}
+ styleProvider={DefaultStyleProvider}
+ layerProvider={undefined}
+ removeDocument={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ PanelWidth={this.getPWidth}
+ PanelHeight={this.getPHeight}
+ renderDepth={0}
+ focus={DocUtils.DefaultFocus}
+ docViewPath={returnEmptyDoclist}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined} />
+ </div>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index daffaf9e7..93e1737fe 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -6,6 +6,7 @@
min-height: 20;
position: absolute;
}
+
.documentLinksButton,
.documentLinksButton-endLink,
.documentLinksButton-startLink {
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index a6d07374a..ddc36daa1 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -30,12 +30,12 @@ interface DocumentLinksButtonProps {
Offset?: (number | undefined)[];
AlwaysOn?: boolean;
InMenu?: boolean;
- StartLink?: boolean;
+ StartLink?: boolean; //whether the link HAS been started (i.e. now needs to be completed)
}
@observer
export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
private _linkButton = React.createRef<HTMLDivElement>();
- @observable public static StartLink: Doc | undefined;
+ @observable public static StartLink: Doc | undefined; //origin's Doc, if defined
@observable public static StartLinkView: DocumentView | undefined;
@observable public static AnnotationId: string | undefined;
@observable public static AnnotationUri: string | undefined;
@@ -45,6 +45,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
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) {
@@ -120,7 +121,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
- } else {
+ } else { //if this LinkButton's Document is undefined
DocumentLinksButton.StartLink = this.props.View.props.Document;
DocumentLinksButton.StartLinkView = this.props.View;
}
@@ -131,6 +132,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
}
}
+
completeLink = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action((e, doubleTap) => {
if (doubleTap && !this.props.StartLink) {
@@ -141,7 +143,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
} else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
const sourceDoc = DocumentLinksButton.StartLink;
const targetDoc = this.props.View.ComponentView?.getAnchor?.() || this.props.View.Document;
- const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, "long drag");
+ const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, "links"); //why is long drag here when this is used for completing links by clicking?
LinkManager.currentLink = linkDoc;
@@ -184,7 +186,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
} else if (startLink !== endLink) {
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);
+ const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "link", undefined, undefined, true);
LinkManager.currentLink = linkDoc;
@@ -242,6 +244,9 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
return results;
}
+ /**
+ * gets the JSX of the link button (btn used to start/complete links) OR the link-view button (btn on bottom left of each linked node)
+ */
@computed get linkButtonInner() {
const btnDim = this.props.InMenu ? "20px" : "30px";
const link = <img style={{ width: "22px", height: "16px" }} src={`/assets/${"link.png"}`} />;
@@ -252,7 +257,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
<div className={"documentLinksButton"}
onPointerDown={this.onLinkButtonDown} onClick={this.onLinkClick}
style={{
- backgroundColor: this.props.InMenu ? "" : "#add8e6",
+ backgroundColor: this.props.InMenu ? "" : '#BDDDF5',
color: this.props.InMenu ? "white" : "black",
width: btnDim,
height: btnDim,
@@ -263,7 +268,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
: link
: Array.from(this.filteredLinks).length}
</div>
- {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ?
+ {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? //if the origin node is not this node
<div className={"documentLinksButton-endLink"}
style={{
width: btnDim, height: btnDim,
@@ -276,7 +281,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
onClick={e => DocumentLinksButton.StartLink && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)} />
: (null)
}
- {DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.InMenu && this.props.StartLink ?
+ {DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.InMenu && this.props.StartLink ? //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
<div className={"documentLinksButton-startLink"} onPointerDown={this.clearLinks} onClick={this.clearLinks} style={{ width: btnDim, height: btnDim }} />
: (null)
}
@@ -290,6 +295,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
const buttonTitle = "Tap to view links; double tap to open link collection";
const title = this.props.InMenu ? menuTitle : buttonTitle;
+ //render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu
return !Array.from(this.filteredLinks).length && !this.props.AlwaysOn ? (null) :
this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink) ?
<Tooltip title={<div className="dash-tooltip">{title}</div>}>
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 8f86417d6..281f25fb3 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -147,7 +147,7 @@
.documentView-titleWrapper,
.documentView-titleWrapper-hover {
overflow: hidden;
- color: white;
+ color: gray;
transform-origin: top left;
top: 0;
width: 100%;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d876ae818..cfd43bb62 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -340,7 +340,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@action
finishMarquee = () => {
this._marqueeing = undefined;
- this.props.select(false)
+ this.props.select(false);
}
render() {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 88e38712a..f5b1f96f2 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -162,7 +162,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
scrollFocus = (doc: Doc, smooth: boolean) => {
if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
if (doc !== this.rootDoc && this._outerRef.current) {
- const scrollTo = doc.type === DocumentType.TEXTANCHOR ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1));
+ const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
+ const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * .1);
if (scrollTo !== undefined) {
const focusSpeed = smooth ? 500 : 0;
this._initialScroll !== undefined && (this._initialScroll = scrollTo);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 6dd63fb47..140d39929 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -215,6 +215,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
AnchorMenu.Instance.Status = "marquee";
AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => {
this._editorView?.state && RichTextMenu.Instance.insertHighlight(color, this._editorView.state, this._editorView?.dispatch);
+ console.log("highlight")
return undefined;
});
/**
@@ -787,7 +788,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
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.dontSelectOnLoad && 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._cachedLinks = DocListCast(this.Document.links);
this._disposers.breakupDictation = reaction(() => DocumentManager.Instance.RecordingEvent, this.breakupDictation);
this._disposers.autoHeight = reaction(() => this.autoHeight, autoHeight => autoHeight && this.tryUpdateScrollHeight());
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 59b2d3753..a6f8ff2e2 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -852,6 +852,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@undoBatch
makeLinkToURL = (target: string, lcoation: string) => {
((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target);
+ console.log((this.view as any)?.TextView);
}
@undoBatch
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index c24c4eaaf..70ca19842 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -10,6 +10,7 @@ import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
import { ButtonDropdown } from "../nodes/formattedText/RichTextMenu";
import "./AnchorMenu.scss";
import { SelectionManager } from "../../util/SelectionManager";
+import { LinkPopup } from "../linking/LinkPopup";
@observer
export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -38,6 +39,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable private _valueValue: string = "";
@observable private _added: boolean = false;
@observable private highlightColor: string = "rgba(245, 230, 95, 0.616)";
+ @observable private _showLinkPopup: boolean = false;
@observable public _colorBtn = false;
@observable public Highlighting: boolean = false;
@@ -80,6 +82,14 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
}
}
+ @action
+ toggleLinkPopup = (e: React.MouseEvent) => {
+ //ignore the potential null type error because this method cannot be called unless the user selects text and clicks the link button
+ console.log(window.getSelection().toString())
+ //change popup visibility field to visible
+ this._showLinkPopup = !this._showLinkPopup;
+ }
+
@computed get highlighter() {
const button =
<button className="antimodeMenu-button color-preview-button" title="" key="highlighter-button" onClick={this.highlightClicked}>
@@ -136,6 +146,14 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
<FontAwesomeIcon icon="comment-alt" size="lg" />
</button>
</Tooltip>,
+
+ //NOTE: link popup is currently incomplete
+ // <Tooltip key="link" title={<div className="dash-tooltip">{"Link selected text to document or URL"}</div>}>
+ // <button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup} style={{}}>
+ // <FontAwesomeIcon icon="link" size="lg" />
+ // </button>
+ // </Tooltip>,
+ // <LinkPopup showPopup={this._showLinkPopup} />
] : [
<Tooltip key="trash" title={<div className="dash-tooltip">{"Remove Link Anchor"}</div>}>
<button className="antimodeMenu-button" onPointerDown={this.Delete}>
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 4a50dccf3..e8c7a4ab0 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -184,7 +184,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
const mainCont = this._mainCont.current;
let focusSpeed: Opt<number>;
if (doc !== this.props.rootDoc && mainCont && this._pdfViewer) {
- const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1));
+ const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
+ const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight);
if (scrollTo !== undefined) {
focusSpeed = 500;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 464a8ad05..111fd3f0d 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -516,30 +516,30 @@ export namespace Doc {
return alias;
}
- export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> {
+ export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<Doc, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> {
if (Doc.IsBaseProto(doc)) return doc;
if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
const copy = dontCreate ? asBranch ? (Cast(doc.branchMaster, Doc, null) || doc) : doc : new Doc(undefined, true);
cloneMap.set(doc[Id], copy);
- if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy);
- const filter = [...exclusions, ...Cast(doc.cloneFieldFilter, listSpec("string"), [])];
- await Promise.all([...Object.keys(doc), "links"].map(async key => {
+ const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== "annotationOn") : exclusions;
+ const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec("string"), [])];
+ await Promise.all(Object.keys(doc).map(async key => {
if (filter.includes(key)) return;
const assignKey = (val: any) => !dontCreate && (copy[key] = val);
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = key === "links" && Doc.IsPrototype(doc) ? doc[key] : ProxyField.WithoutProxy(() => doc[key]);
+ const field = ProxyField.WithoutProxy(() => doc[key]);
const copyObjectField = async (field: ObjectField) => {
const list = Cast(doc[key], listSpec(Doc));
const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
if (docs !== undefined && docs.length) {
- const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, rtfs, exclusions, dontCreate, asBranch)));
+ const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)));
!dontCreate && assignKey(new List<Doc>(clones));
} else if (doc[key] instanceof Doc) {
- assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded teplate fields
+ assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded teplate fields
} else {
!dontCreate && assignKey(ObjectField.MakeCopy(field));
if (field instanceof RichTextField) {
- if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) {
+ if (field.Data.includes('"audioId":') || field.Data.includes('"textId":') || field.Data.includes('"anchorId":')) {
rtfs.push({ copy, key, field });
}
}
@@ -547,14 +547,17 @@ export namespace Doc {
};
if (key === "proto") {
if (doc[key] instanceof Doc) {
- assignKey(await Doc.makeClone(doc[key]!, cloneMap, rtfs, exclusions, dontCreate, asBranch));
+ assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch));
+ }
+ } else if (key === "anchor1" || key === "anchor2") {
+ if (doc[key] instanceof Doc) {
+ assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, true, asBranch));
}
} else {
if (field instanceof RefField) {
assignKey(field);
} else if (cfield instanceof ComputedField) {
!dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript));
- (key === "links" && field instanceof ObjectField) && await copyObjectField(field);
} else if (field instanceof ObjectField) {
await copyObjectField(field);
} else if (field instanceof Promise) {
@@ -564,6 +567,10 @@ export namespace Doc {
}
}
}));
+ for (const link of Array.from(doc[DirectLinksSym])) {
+ const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch);
+ linkMap.set(link, linkClone);
+ }
if (!dontCreate) {
Doc.SetInPlace(copy, "title", (asBranch ? "BRANCH: " : "CLONE: ") + doc.title, true);
asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc);
@@ -576,8 +583,10 @@ export namespace Doc {
}
export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false) {
const cloneMap = new Map<string, Doc>();
+ const linkMap = new Map<Doc, Doc>();
const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
- const copy = await Doc.makeClone(doc, cloneMap, rtfMap, ["context", "annotationOn", "cloneOf", "branches", "branchOf"], dontCreate, asBranch);
+ const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ["context", "annotationOn", "cloneOf", "branches", "branchOf"], dontCreate, asBranch);
+ Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true));
rtfMap.map(({ copy, key, field }) => {
const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
const mapped = cloneMap.get(id);
@@ -589,7 +598,7 @@ export namespace Doc {
};
const regex = `(${Utils.prepend("/doc/")})([^"]*)`;
const re = new RegExp(regex, "g");
- copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
+ copy[key] = new RichTextField(field.Data.replace(/("textId":|"audioId":|"anchorId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
});
return { clone: copy, map: cloneMap };
}