From 1f5db9cfc594dbf337d752ec94dab5fca7d8b6f7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 21 Nov 2022 14:50:25 -0500
Subject: working cleaned up version of link editor properties in
 propertiesView

---
 src/client/views/linking/LinkMenuGroup.tsx | 65 ++++++++++++++++--------------
 1 file changed, 34 insertions(+), 31 deletions(-)

(limited to 'src/client/views/linking/LinkMenuGroup.tsx')

diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index fa6a2f506..9d2082e21 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -1,19 +1,19 @@
-import { observer } from "mobx-react";
-import { observable, action } from "mobx";
-import { Doc, StrListCast } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { Cast } from "../../../fields/Types";
-import { LinkManager } from "../../util/LinkManager";
-import { DocumentView } from "../nodes/DocumentView";
+import { observer } from 'mobx-react';
+import { observable, action } from 'mobx';
+import { Doc, StrListCast } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { Cast } from '../../../fields/Types';
+import { LinkManager } from '../../util/LinkManager';
+import { DocumentView } from '../nodes/DocumentView';
 import './LinkMenu.scss';
-import { LinkMenuItem } from "./LinkMenuItem";
-import React = require("react");
+import { LinkMenuItem } from './LinkMenuItem';
+import React = require('react');
 
 interface LinkMenuGroupProps {
     sourceDoc: Doc;
     group: Doc[];
     groupType: string;
-    clearLinkEditor: () => void;
+    clearLinkEditor?: () => void;
     showEditor: (linkDoc: Doc) => void;
     docView: DocumentView;
     itemHandler?: (doc: Doc) => void;
@@ -26,49 +26,52 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
     getBackgroundColor = (): string => {
         const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList);
         const linkColorList = StrListCast(Doc.UserDoc().linkColorList);
-        let color = "white";
+        let color = 'white';
         // if this link's relationship property is not default "link", set its color
         if (linkRelationshipList) {
             const relationshipIndex = linkRelationshipList.indexOf(this.props.groupType);
             const RGBcolor: string = linkColorList[relationshipIndex];
             if (RGBcolor) {
                 //set opacity to 0.25 by modifiying the rgb string
-                color = RGBcolor.slice(0, RGBcolor.length - 1) + ", 0.25)";
+                color = RGBcolor.slice(0, RGBcolor.length - 1) + ', 0.25)';
             }
         }
         return color;
-    }
+    };
 
     @observable _collapsed = false;
 
     render() {
         const set = new Set<Doc>(this.props.group);
         const groupItems = Array.from(set.keys()).map(linkDoc => {
-            const destination = LinkManager.getOppositeAnchor(linkDoc, this.props.sourceDoc) ||
+            const destination =
+                LinkManager.getOppositeAnchor(linkDoc, this.props.sourceDoc) ||
                 LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, null).annotationOn === this.props.sourceDoc ? Cast(linkDoc.anchor2, Doc, null) : Cast(linkDoc.anchor1, Doc, null));
             if (destination && this.props.sourceDoc) {
-                return <LinkMenuItem key={linkDoc[Id]}
-                    itemHandler={this.props.itemHandler}
-                    groupType={this.props.groupType}
-                    docView={this.props.docView}
-                    linkDoc={linkDoc}
-                    sourceDoc={this.props.sourceDoc}
-                    destinationDoc={destination}
-                    clearLinkEditor={this.props.clearLinkEditor}
-                    showEditor={this.props.showEditor}
-                    menuRef={this._menuRef} />;
+                return (
+                    <LinkMenuItem
+                        key={linkDoc[Id]}
+                        itemHandler={this.props.itemHandler}
+                        groupType={this.props.groupType}
+                        docView={this.props.docView}
+                        linkDoc={linkDoc}
+                        sourceDoc={this.props.sourceDoc}
+                        destinationDoc={destination}
+                        clearLinkEditor={this.props.clearLinkEditor}
+                        showEditor={this.props.showEditor}
+                        menuRef={this._menuRef}
+                    />
+                );
             }
         });
 
         return (
             <div className="linkMenu-group" ref={this._menuRef}>
-                <div className="linkMenu-group-name" onClick={action(() => this._collapsed = !this._collapsed)} style={{ background: this.getBackgroundColor() }}>
-                    <p className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"}> {this.props.groupType}:</p>
+                <div className="linkMenu-group-name" onClick={action(() => (this._collapsed = !this._collapsed))} style={{ background: this.getBackgroundColor() }}>
+                    <p className={this.props.groupType === '*' || this.props.groupType === '' ? '' : 'expand-one'}> {this.props.groupType}:</p>
                 </div>
-                {this._collapsed ? (null) : <div className="linkMenu-group-wrapper">
-                    {groupItems}
-                </div>}
-            </div >
+                {this._collapsed ? null : <div className="linkMenu-group-wrapper">{groupItems}</div>}
+            </div>
         );
     }
-}
\ No newline at end of file
+}
-- 
cgit v1.2.3-70-g09d2


From c6d1059e24f362a167b9ac24e6f13d1e45361da9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 29 Nov 2022 12:05:29 -0500
Subject: changes to streamline link editing UI (got rid of LinkEditor). 
 cleaned up link (un)highlighting.

---
 src/client/documents/Documents.ts                  |   1 -
 src/client/util/DocumentManager.ts                 |  19 +-
 src/client/util/LinkFollower.ts                    |   2 +-
 src/client/util/LinkManager.ts                     |   2 +-
 src/client/util/SelectionManager.ts                |   1 -
 src/client/views/DocumentDecorations.tsx           |  14 +-
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/PropertiesView.scss               |  15 +-
 src/client/views/PropertiesView.tsx                |  94 +++--
 .../CollectionFreeFormLinksView.tsx                |   2 +-
 src/client/views/global/globalCssVariables.scss    |   1 +
 .../views/global/globalCssVariables.scss.d.ts      |   1 +
 src/client/views/linking/LinkEditor.scss           | 334 ---------------
 src/client/views/linking/LinkEditor.tsx            | 454 ---------------------
 src/client/views/linking/LinkMenu.tsx              |  37 +-
 src/client/views/linking/LinkMenuGroup.tsx         |  50 ++-
 src/client/views/linking/LinkMenuItem.tsx          |   4 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |   2 +-
 src/client/views/nodes/DocumentView.tsx            |  16 +-
 src/client/views/nodes/LinkAnchorBox.tsx           | 110 ++---
 src/fields/Doc.ts                                  |  64 +--
 21 files changed, 194 insertions(+), 1031 deletions(-)
 delete mode 100644 src/client/views/linking/LinkEditor.scss
 delete mode 100644 src/client/views/linking/LinkEditor.tsx

(limited to 'src/client/views/linking/LinkMenuGroup.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c5e08eeea..eed839520 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1375,7 +1375,6 @@ export namespace DocUtils {
                     'acl-Public': SharingPermissions.Augment,
                     '_acl-Public': SharingPermissions.Augment,
                     linkDisplay: true,
-                    _hidden: true,
                     _linkAutoMove: true,
                     linkRelationship,
                     _showCaption: 'description',
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 7120eeb88..4f02a8202 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,7 +1,7 @@
 import { action, observable, runInAction } from 'mobx';
 import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
-import { Cast } from '../../fields/Types';
+import { Cast, DocCast } from '../../fields/Types';
 import { returnFalse } from '../../Utils';
 import { DocumentType } from '../documents/DocumentTypes';
 import { LightboxView } from '../views/LightboxView';
@@ -36,15 +36,14 @@ export class DocumentManager {
         //console.log("MOUNT " + view.props.Document.title + "/" + view.props.LayoutTemplateString);
         if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
             const viewAnchorIndex = view.props.LayoutTemplateString.includes('anchor2') ? 'anchor2' : 'anchor1';
-            DocListCast(view.rootDoc.links).forEach(link => {
-                this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView =>
-                    this.LinkedDocumentViews.push({
-                        a: viewAnchorIndex === 'anchor2' ? otherView : view,
-                        b: viewAnchorIndex === 'anchor2' ? view : otherView,
-                        l: link,
-                    })
-                );
-            });
+            const link = view.rootDoc;
+            this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView =>
+                this.LinkedDocumentViews.push({
+                    a: viewAnchorIndex === 'anchor2' ? otherView : view,
+                    b: viewAnchorIndex === 'anchor2' ? view : otherView,
+                    l: link,
+                })
+            );
             this.LinkAnchorBoxViews.push(view);
             // this.LinkedDocumentViews.forEach(view => console.log("  LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " +
             //     view.b.props.Document.title + "/" + view.b.props.LayoutTemplateString));
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 5374bf44b..282116f1b 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -67,7 +67,7 @@ export class LinkFollower {
             docViewProps.ContainingCollectionDoc,
             action(() => {
                 batch.end();
-                DocumentDecorations.Instance.overrideBounds = false;
+                Doc.AddUnlightWatcher(action(() => (DocumentDecorations.Instance.overrideBounds = false)));
             }),
             altKey ? true : undefined
         );
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 49cc3218d..01f4df723 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -44,7 +44,7 @@ export class LinkManager {
                         if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
                             Doc.GetProto(a1)[DirectLinksSym].add(link);
                             Doc.GetProto(a2)[DirectLinksSym].add(link);
-                            Doc.GetProto(link)[DirectLinksSym].add(link);
+                            //Doc.GetProto(link)[DirectLinksSym].add(link); // bcz: links are not linked to themself, so this was a hack
                         }
                     })
                 );
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 02d672a65..a3d6f5227 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -4,7 +4,6 @@ import { Doc, Opt } from '../../fields/Doc';
 import { DocCast } from '../../fields/Types';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { DocumentView } from '../views/nodes/DocumentView';
-import { LinkManager } from './LinkManager';
 import { ScriptingGlobals } from './ScriptingGlobals';
 
 export namespace SelectionManager {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3efb5fb37..d1f0bf2ac 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -27,7 +27,7 @@ import { Colors } from './global/globalEnums';
 import { InkingStroke } from './InkingStroke';
 import { InkStrokeProperties } from './InkStrokeProperties';
 import { LightboxView } from './LightboxView';
-import { DocumentView } from './nodes/DocumentView';
+import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { ImageBox } from './nodes/ImageBox';
 import React = require('react');
@@ -253,17 +253,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         if (selectedDocs.length) {
             if (e.ctrlKey) {
                 // open an alias in a new tab with Ctrl Key
-                CollectionDockingView.AddSplit(Doc.BestAlias(selectedDocs[0].props.Document), 'right');
+                CollectionDockingView.AddSplit(Doc.BestAlias(selectedDocs[0].props.Document), OpenWhereMod.right);
             } else if (e.shiftKey) {
                 // open centered in a new workspace with Shift Key
                 const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
                 alias.context = undefined;
                 alias.x = -alias[WidthSym]() / 2;
                 alias.y = -alias[HeightSym]() / 2;
-                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: 'Tab for ' + alias.title }), 'right');
+                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: 'Tab for ' + alias.title }), OpenWhereMod.right);
             } else if (e.altKey) {
                 // open same document in new tab
-                CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, 'right');
+                CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, OpenWhereMod.right);
             } else {
                 var openDoc = selectedDocs[0].props.Document;
                 if (openDoc.layoutKey === 'layout_icon') {
@@ -720,13 +720,15 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating;
         const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating;
         // if multiple documents have been opened at the same time, then don't show open button
-        const hideOpenButton =hideDecorations || 
+        const hideOpenButton =
+            hideDecorations ||
             seldocview.props.hideOpenButton ||
             seldocview.rootDoc.hideOpenButton ||
             SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) ||
             this._isRounding ||
             this._isRotating;
-        const hideDeleteButton =hideDecorations || 
+        const hideDeleteButton =
+            hideDecorations ||
             this._isRounding ||
             this._isRotating ||
             seldocview.props.hideDeleteButton ||
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 98d0378be..09063901d 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -484,7 +484,7 @@ export class MainView extends React.Component {
     }
 
     globalPointerDown = action((e: PointerEvent) => {
-        runInAction(() => (Doc.HighlightBrush.linkFollowEffect = undefined));
+        Doc.linkFollowUnhighlight();
         AudioBox.Enabled = true;
         const targets = document.elementsFromPoint(e.x, e.y);
         if (targets.length) {
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index 30806f718..897be9a32 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -4,8 +4,13 @@
     height: 100%;
     width: 250;
     font-family: 'Roboto';
+    font-size: 12px;
     cursor: auto;
 
+    .slider-text {
+        font-size: 8px;
+    }
+
     overflow-x: hidden;
     overflow-y: auto;
 
@@ -865,7 +870,15 @@
     }
 
     .propertiesButton {
-        width: 4rem;
+        width: 2rem;
+        height: 2rem;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        > svg {
+            width: 15px;
+            height: 15px;
+        }
     }
 }
 
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index ad3f62990..e8fd540a8 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -55,7 +55,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     }
 
     @computed get selectedDoc() {
-        return LinkManager.currentLink || SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard;
+        return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard;
     }
     @computed get selectedDocumentView() {
         if (SelectionManager.Views().length) return SelectionManager.Views()[0];
@@ -306,7 +306,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     }
 
     @computed get links() {
-        return !this.selectedDoc ? null : <PropertiesDocBacklinksSelector Document={this.selectedDoc} hideTitle={true} addDocTab={this.props.addDocTab} />;
+        const selAnchor = this.selectedDocumentView?.anchorViewDoc;
+        return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this.props.addDocTab} />;
     }
 
     @computed get layoutPreview() {
@@ -1426,11 +1427,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @undoBatch
     animationDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => {
         const lanch = this.sourceAnchor;
-        const color = lanch?.presEffectDirection === direction || (direction === PresEffectDirection.Center && !lanch?.presEffectDirection) ? Colors.LIGHT_BLUE : 'black';
+        const color = lanch?.presEffectDirection === direction || (direction === PresEffectDirection.Center && !lanch?.presEffectDirection) ? Colors.MEDIUM_BLUE : '';
         return (
             <Tooltip title={<div className="dash-tooltip">{direction}</div>}>
                 <div
-                    style={{ ...opts, border: direction === PresEffectDirection.Center ? `solid 2px ${color}` : undefined, borderRadius: '100%', cursor: 'pointer', gridColumn, gridRow, justifySelf: 'center', color }}
+                    style={{ ...opts, border: direction === PresEffectDirection.Center ? `solid 2px ${color}` : undefined, borderRadius: '20%', cursor: 'pointer', gridColumn, gridRow, justifySelf: 'center', background: color, color: 'black' }}
                     onClick={() => this.updateEffectDirection(direction)}>
                     {icon ? <FontAwesomeIcon icon={icon as any} /> : null}
                 </div>
@@ -1470,19 +1471,21 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         }
     };
 
-    toggleProp = (e: React.PointerEvent, prop: string) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc[prop] = !this.selectedDoc[prop]))));
+    toggleLinkProp = (e: React.PointerEvent, prop: string) => {
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.currentLink && (LinkManager.currentLink[prop] = !LinkManager.currentLink[prop]))));
     };
 
     @computed get destinationAnchor() {
         const ldoc = LinkManager.currentLink;
-        const lanch = LinkManager.currentLinkAnchor;
+        const lanch = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor;
         if (ldoc && lanch) return LinkManager.getOppositeAnchor(ldoc, lanch) ?? lanch;
         return ldoc ? DocCast(ldoc.anchor2) : ldoc;
     }
 
     @computed get sourceAnchor() {
-        return LinkManager.currentLinkAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink);
+        const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor;
+
+        return selAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink);
     }
 
     toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc) => {
@@ -1545,9 +1548,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         const isNovice = Doc.noviceMode;
         const zoom = Number((NumCast(this.sourceAnchor?.presZoom, 1) * 100).toPrecision(3));
         const targZoom = this.sourceAnchor?.followLinkZoom;
-        const selectedDoc = this.selectedDoc;
         const indent = 30;
-        if (!selectedDoc && !this.isPres) {
+        if (!this.selectedDoc && !this.isPres) {
             return (
                 <div className="propertiesView" style={{ width: this.props.width }}>
                     <div className="propertiesView-title" style={{ width: this.props.width }}>
@@ -1556,7 +1558,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                 </div>
             );
         } else {
-            if (selectedDoc && !this.isPres) {
+            if (this.selectedDoc && !this.isPres) {
                 return (
                     <div
                         className="propertiesView"
@@ -1573,9 +1575,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                         {this.contextsSubMenu}
 
                         {this.linksSubMenu}
-                        {!selectedDoc || !LinkManager.currentLink || !SelectionManager.Views().some(dv => DocListCast(dv.rootDoc.links).includes(LinkManager.currentLink!)) ? null : (
+                        {!this.selectedDoc || !LinkManager.currentLink || !SelectionManager.Views().some(dv => DocListCast(this.sourceAnchor?.links).includes(LinkManager.currentLink!)) ? null : (
                             <>
-                                <div className="propertiesView-section">
+                                <div className="propertiesView-section" style={{ background: 'darkgray' }}>
                                     <div className="propertiesView-input first" style={{ display: 'grid', gridTemplateColumns: '84px auto' }}>
                                         <p>Relationship</p>
                                         {this.editRelationship}
@@ -1584,11 +1586,41 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                                         <p>Description</p>
                                         {this.editDescription}
                                     </div>
+                                    <div className="propertiesView-input inline">
+                                        <p>Show link</p>
+                                        <button
+                                            style={{ background: !LinkManager.currentLink?.linkDisplay ? '' : '#4476f7', borderRadius: 3 }}
+                                            onPointerDown={e => this.toggleLinkProp(e, 'linkDisplay')}
+                                            onClick={e => e.stopPropagation()}
+                                            className="propertiesButton">
+                                            <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+                                        </button>
+                                    </div>
+                                    <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
+                                        <p>Auto-move anchors</p>
+                                        <button
+                                            style={{ background: !LinkManager.currentLink?.linkAutoMove ? '' : '#4476f7', borderRadius: 3 }}
+                                            onPointerDown={e => this.toggleLinkProp(e, 'linkAutoMove')}
+                                            onClick={e => e.stopPropagation()}
+                                            className="propertiesButton">
+                                            <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+                                        </button>
+                                    </div>
+                                    <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
+                                        <p>Display arrow</p>
+                                        <button
+                                            style={{ background: !LinkManager.currentLink?.linkDisplayArrow ? '' : '#4476f7', borderRadius: 3 }}
+                                            onPointerDown={e => this.toggleLinkProp(e, 'linkDisplayArrow')}
+                                            onClick={e => e.stopPropagation()}
+                                            className="propertiesButton">
+                                            <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+                                        </button>
+                                    </div>
                                 </div>
                                 <div className="propertiesView-section">
                                     <div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 84px)' }}>
                                         <p>Follow by</p>
-                                        <select onChange={e => this.changeFollowBehavior(e.currentTarget.value)} value={StrCast(selectedDoc.followLinkLocation, 'default')}>
+                                        <select onChange={e => this.changeFollowBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkLocation, 'default')}>
                                             <option value="default">Default</option>
                                             <option value="add:left">Opening in new left pane</option>
                                             <option value="add:right">Opening in new right pane</option>
@@ -1598,7 +1630,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                                             <option value="add">Opening in new tab</option>
                                             <option value="replace">Replacing current tab</option>
                                             <option value="inPlace">Opening in place</option>
-                                            {selectedDoc.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null}
+                                            {LinkManager.currentLink?.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null}
                                         </select>
                                     </div>
                                     <div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 134px) 50px' }}>
@@ -1609,7 +1641,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                                                 <option value={effect.toString()}>{effect.toString()}</option>
                                             ))}
                                         </select>
-                                        <div className="effectDirection" style={{ marginLeft: '10px', display: 'grid', width: 40, gridColumn: 3 }}>
+                                        <div className="effectDirection" style={{ marginLeft: '10px', display: 'grid', width: 40, height: 36, gridColumn: 3, gridTemplateRows: '12px 12px 12px' }}>
                                             {this.animationDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})}
                                             {this.animationDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})}
                                             {this.animationDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})}
@@ -1650,34 +1682,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                                             <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
                                         </button>
                                     </div>
-                                    <div className="propertiesView-input inline">
-                                        <p>Show link</p>
-                                        <button style={{ background: !selectedDoc.linkDisplay ? '' : '#4476f7', borderRadius: 3 }} onPointerDown={e => this.toggleProp(e, 'linkDisplay')} onClick={e => e.stopPropagation()} className="propertiesButton">
-                                            <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
-                                        </button>
-                                    </div>
-                                    <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
-                                        <p>Auto-move anchor</p>
-                                        <button
-                                            style={{ background: !selectedDoc.linkAutoMove ? '' : '#4476f7', borderRadius: 3 }}
-                                            onPointerDown={e => this.toggleProp(e, 'linkAutoMove')}
-                                            onClick={e => e.stopPropagation()}
-                                            className="propertiesButton">
-                                            <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
-                                        </button>
-                                    </div>
-                                    <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
-                                        <p>Display arrow</p>
-                                        <button
-                                            style={{ background: !selectedDoc.linkDisplayArrow ? '' : '#4476f7', borderRadius: 3 }}
-                                            onPointerDown={e => this.toggleProp(e, 'linkDisplayArrow')}
-                                            onClick={e => e.stopPropagation()}
-                                            className="propertiesButton">
-                                            <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
-                                        </button>
-                                    </div>
-                                    <div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 128px) 50px' }}>
-                                        <p>Zoom % screen</p>
+                                    <div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 108px) 50px' }}>
+                                        <p>Zoom %</p>
                                         <div className="ribbon-property" style={{ display: !targZoom ? 'none' : 'inline-flex' }}>
                                             <input className="presBox-input" style={{ width: '100%' }} type="number" value={zoom} />
                                             <div className="ribbon-propertyUpDown" style={{ display: 'flex', flexDirection: 'column' }}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index b8344dc0c..9e360f557 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -2,10 +2,10 @@ import { computed } from 'mobx';
 import { observer } from 'mobx-react';
 import { Id } from '../../../../fields/FieldSymbols';
 import { DocumentManager } from '../../../util/DocumentManager';
+import { LightboxView } from '../../LightboxView';
 import './CollectionFreeFormLinksView.scss';
 import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView';
 import React = require('react');
-import { LightboxView } from '../../LightboxView';
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component<React.PropsWithChildren<{}>> {
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index e68f9abe3..430a36dce 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -84,4 +84,5 @@ $TREE_BULLET_WIDTH: 20px;
     LEFT_MENU_WIDTH: $LEFT_MENU_WIDTH;
     TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH;
     INK_MASK_SIZE: $INK_MASK_SIZE;
+    MEDIUM_GRAY: $medium-gray;
 }
diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.scss.d.ts
index 76259113c..3375579d6 100644
--- a/src/client/views/global/globalCssVariables.scss.d.ts
+++ b/src/client/views/global/globalCssVariables.scss.d.ts
@@ -11,6 +11,7 @@ interface IGlobalScss {
     LEFT_MENU_WIDTH: string;
     TREE_BULLET_WIDTH: string;
     INK_MASK_SIZE: number;
+    MEDIUM_GRAY: string;
 }
 declare const globalCssVariables: IGlobalScss;
 
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
deleted file mode 100644
index b0ee4e46d..000000000
--- a/src/client/views/linking/LinkEditor.scss
+++ /dev/null
@@ -1,334 +0,0 @@
-@import '../global/globalCssVariables';
-
-.linkEditor {
-    width: 100%;
-    height: auto;
-    font-size: 13px; // TODO
-    user-select: none;
-    max-width: 280px;
-}
-
-.linkEditor-button-back {
-    //margin-bottom: 6px;
-    border-radius: 10px;
-    width: 18px;
-    height: 18px;
-    padding: 0;
-
-    &:hover {
-        cursor: pointer;
-    }
-}
-
-.linkEditor-info {
-    padding-top: 12px;
-    padding-left: 5px;
-    padding-bottom: 3px;
-    //margin-bottom: 6px;
-    display: flex;
-    justify-content: space-between;
-    color: black;
-
-    .linkEditor-linkedTo {
-        width: calc(100% - 46px);
-        overflow: hidden;
-        position: relative;
-        text-overflow: ellipsis;
-        white-space: pre;
-
-        .linkEditor-downArrow {
-            &:hover {
-                cursor: pointer;
-            }
-        }
-    }
-}
-
-.linkEditor-moreInfo {
-    margin-left: 12px;
-    padding-left: 13px;
-    padding-right: 6.5px;
-    padding-bottom: 4px;
-    font-size: 9px;
-    //font-style: italic;
-    text-decoration-color: grey;
-
-    .button {
-        color: black;
-
-        &:hover {
-            cursor: pointer;
-        }
-    }
-}
-
-.linkEditor-zoomFollow {
-    padding-left: 26px;
-    padding-right: 6.5px;
-    padding-bottom: 3.5px;
-    display: flex;
-
-    .linkEditor-zoomFollow-label {
-        text-decoration-color: black;
-        color: black;
-        line-height: 1.7;
-    }
-
-    .linkEditor-zoomFollow-input {
-        display: block;
-        width: 20px;
-    }
-}
-.linkEditor-deleteBtn {
-    padding-left: 3px;
-}
-
-.linkEditor-description {
-    padding-left: 26px;
-    padding-bottom: 3.5px;
-    display: flex;
-
-    .linkEditor-description-label {
-        text-decoration-color: black;
-        color: black;
-    }
-
-    .linkEditor-description-input {
-        display: flex;
-
-        .linkEditor-description-editing {
-            min-width: 85%;
-            //border: 1px solid grey;
-            //border-radius: 4px;
-            padding-left: 2px;
-            //margin-right: 4px;
-            color: black;
-            text-decoration-color: grey;
-        }
-
-        .linkEditor-description-add-button {
-            display: inline;
-            border-radius: 7px;
-            font-size: 9px;
-            background: black;
-            height: 80%;
-            color: white;
-            padding: 3px;
-            margin-left: 3px;
-
-            &:hover {
-                cursor: pointer;
-                background: grey;
-            }
-        }
-    }
-}
-
-.linkEditor-relationship-dropdown {
-    position: absolute;
-    width: 154px;
-    max-height: 90px;
-    overflow: auto;
-    background: white;
-
-    p {
-        padding: 3px;
-        cursor: pointer;
-        border: 1px solid $medium-gray;
-    }
-
-    p:hover {
-        background: $light-blue;
-    }
-}
-
-.linkEditor-followingDropdown {
-    padding-left: 26px;
-    padding-right: 6.5px;
-    padding-bottom: 15px;
-    display: flex;
-
-    &:hover {
-        cursor: pointer;
-    }
-
-    .linkEditor-followingDropdown-label {
-        color: black;
-        padding-right: 3px;
-    }
-
-    .linkEditor-followingDropdown-dropdown {
-        .linkEditor-followingDropdown-header {
-            border: 1px solid grey;
-            border-radius: 4px;
-            //background-color: rgb(236, 236, 236);
-            padding-left: 2px;
-            padding-right: 2px;
-            text-decoration-color: black;
-            color: rgb(94, 94, 94);
-
-            .linkEditor-followingDropdown-icon {
-                float: right;
-                color: black;
-            }
-        }
-
-        .linkEditor-followingDropdown-optionsList {
-            padding-left: 3px;
-            padding-right: 3px;
-
-            &:last-child {
-                border-bottom: none;
-            }
-
-            .linkEditor-followingDropdown-option {
-                border: 0.25px solid grey;
-                //background-color: rgb(236, 236, 236);
-                padding-left: 2px;
-                padding-right: 2px;
-                color: grey;
-                text-decoration-color: grey;
-                font-size: 9px;
-                border-top: none;
-
-                &:hover {
-                    background-color: rgb(187, 220, 231);
-                }
-            }
-        }
-    }
-}
-
-.linkEditor-button,
-.linkEditor-addbutton {
-    width: 15%;
-    border-radius: 7px;
-    font-size: 9px;
-    background: black;
-    padding: 3px;
-    height: 80%;
-    color: white;
-    text-align: center;
-    margin: auto;
-    margin-left: 3px;
-    > svg {
-        margin: auto;
-    }
-    &:disabled {
-        background-color: gray;
-    }
-}
-
-.linkEditor-addbutton {
-    margin-left: 0px;
-}
-
-.linkEditor-groupsLabel {
-    display: flex;
-    justify-content: space-between;
-}
-
-.linkEditor-group {
-    background-color: $light-gray;
-    padding: 6px;
-    margin: 3px 0;
-    border-radius: 3px;
-
-    .linkEditor-group-row {
-        display: flex;
-        margin-bottom: 3px;
-    }
-
-    .linkEditor-group-row-label {
-        margin-right: 6px;
-        display: inline-block;
-    }
-
-    .linkEditor-metadata-row {
-        display: flex;
-        justify-content: space-between;
-        margin-bottom: 6px;
-
-        .linkEditor-error {
-            border-color: red;
-        }
-
-        input {
-            width: calc(50% - 16px);
-            height: 20px;
-        }
-
-        button {
-            width: 20px;
-            height: 20px;
-            margin-left: 3px;
-            padding: 0;
-            font-size: 10px;
-        }
-    }
-}
-
-.linkEditor-dropdown {
-    width: 100%;
-    position: relative;
-    z-index: 999;
-
-    input {
-        width: 100%;
-    }
-
-    .linkEditor-options-wrapper {
-        width: 100%;
-        position: absolute;
-        top: 19px;
-        left: 0;
-        display: flex;
-        flex-direction: column;
-    }
-
-    .linkEditor-option {
-        background-color: $light-gray;
-        border: 1px solid $medium-gray;
-        border-top: 0;
-        padding: 3px;
-        cursor: pointer;
-
-        &:hover {
-            background-color: lightgray;
-        }
-
-        &.onDown {
-            background-color: gray;
-        }
-    }
-}
-
-.linkEditor-typeButton {
-    background-color: transparent;
-    color: $dark-gray;
-    height: 20px;
-    padding: 0 3px;
-    padding-bottom: 2px;
-    text-align: left;
-    text-transform: none;
-    letter-spacing: normal;
-    font-size: 12px;
-    font-weight: bold;
-    display: inline-block;
-    width: calc(100% - 40px);
-
-    &:hover {
-        background-color: $white;
-    }
-}
-
-.linkEditor-group-buttons {
-    height: 20px;
-    display: flex;
-    justify-content: flex-end;
-    margin-top: 5px;
-
-    .linkEditor-button {
-        margin-left: 3px;
-    }
-}
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
deleted file mode 100644
index 01e33708a..000000000
--- a/src/client/views/linking/LinkEditor.tsx
+++ /dev/null
@@ -1,454 +0,0 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
-import { action, computed, observable } from 'mobx';
-import { observer } from 'mobx-react';
-import { Doc, NumListCast, StrListCast, Field } from '../../../fields/Doc';
-import { DateCast, StrCast, Cast, BoolCast, DocCast, NumCast } from '../../../fields/Types';
-import { LinkManager } from '../../util/LinkManager';
-import { undoBatch } from '../../util/UndoManager';
-import './LinkEditor.scss';
-import { LinkRelationshipSearch } from './LinkRelationshipSearch';
-import React = require('react');
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
-import { PresBox, PresEffect } from '../nodes/trails';
-
-interface LinkEditorProps {
-    sourceDoc: Doc;
-    linkDoc: Doc;
-    showLinks?: () => void;
-    hideback?: boolean;
-}
-@observer
-export class LinkEditor extends React.Component<LinkEditorProps> {
-    @observable description = Field.toString(LinkManager.currentLink?.description as any as Field);
-    @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship);
-    @observable zoomFollow = BoolCast(this.props.sourceDoc.followLinkZoom);
-    @observable audioFollow = BoolCast(this.props.sourceDoc.followLinkAudio);
-    @observable openDropdown: boolean = false;
-    @observable openEffectDropdown: boolean = false;
-    @observable private buttonColor: string = '';
-    @observable private relationshipButtonColor: string = '';
-    @observable private relationshipSearchVisibility: string = 'none';
-    @observable private searchIsActive: boolean = false;
-
-    //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION";
-
-    @undoBatch
-    setRelationshipValue = action((value: string) => {
-        if (LinkManager.currentLink) {
-            const prevRelationship = LinkManager.currentLink.linkRelationship as string;
-            LinkManager.currentLink.linkRelationship = value;
-            Doc.GetProto(LinkManager.currentLink).linkRelationship = value;
-            const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList);
-            const linkRelationshipSizes = NumListCast(Doc.UserDoc().linkRelationshipSizes);
-            const linkColorList = StrListCast(Doc.UserDoc().linkColorList);
-
-            // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color
-            if (!linkRelationshipList?.includes(value)) {
-                linkRelationshipList.push(value);
-                linkRelationshipSizes.push(1);
-                const randColor = 'rgb(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ')';
-                linkColorList.push(randColor);
-                // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes
-            } else if (linkRelationshipList && value !== prevRelationship) {
-                const index = linkRelationshipList.indexOf(value);
-                //increment size of new relationship size
-                if (index !== -1 && index < linkRelationshipSizes.length) {
-                    const pvalue = linkRelationshipSizes[index];
-                    linkRelationshipSizes[index] = pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1;
-                }
-                //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation)
-                if (linkRelationshipList.includes(prevRelationship)) {
-                    const pindex = linkRelationshipList.indexOf(prevRelationship);
-                    if (pindex !== -1 && pindex < linkRelationshipSizes.length) {
-                        const pvalue = linkRelationshipSizes[pindex];
-                        linkRelationshipSizes[pindex] = Math.max(0, pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1);
-                    }
-                }
-            }
-            this.relationshipButtonColor = 'rgb(62, 133, 55)';
-            setTimeout(
-                action(() => (this.relationshipButtonColor = '')),
-                750
-            );
-            return true;
-        }
-    });
-
-    /**
-     * returns list of strings with possible existing relationships that contain what is currently in the input field
-     */
-    @action
-    getRelationshipResults = () => {
-        const query = this.relationship; //current content in input box
-        const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList);
-        if (linkRelationshipList) {
-            return linkRelationshipList.filter(rel => rel.includes(query));
-        }
-    };
-
-    /**
-     * toggles visibility of the relationship search results when the input field is focused on
-     */
-    @action
-    toggleRelationshipResults = () => {
-        this.relationshipSearchVisibility = this.relationshipSearchVisibility === 'none' ? 'block' : 'none';
-    };
-
-    @undoBatch
-    setDescripValue = action((value: string) => {
-        if (LinkManager.currentLink) {
-            Doc.GetProto(LinkManager.currentLink).description = value;
-            this.buttonColor = 'rgb(62, 133, 55)';
-            setTimeout(
-                action(() => (this.buttonColor = '')),
-                750
-            );
-            return true;
-        }
-    });
-
-    onDescriptionKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
-        if (e.key === 'Enter') {
-            this.setDescripValue(this.description);
-            document.getElementById('input')?.blur();
-        }
-        e.stopPropagation();
-    };
-
-    onRelationshipKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
-        if (e.key === 'Enter') {
-            this.setRelationshipValue(this.relationship);
-            document.getElementById('input')?.blur();
-        }
-        e.stopPropagation();
-    };
-
-    onDescriptionDown = () => this.setDescripValue(this.description);
-    onRelationshipDown = () => this.setRelationshipValue(this.relationship);
-
-    onBlur = () => {
-        //only hide the search results if the user clicks out of the input AND not on any of the search results
-        // i.e. if search is not active
-        if (!this.searchIsActive) {
-            this.toggleRelationshipResults();
-        }
-    };
-    onFocus = () => {
-        this.toggleRelationshipResults();
-    };
-    toggleSearchIsActive = () => {
-        this.searchIsActive = !this.searchIsActive;
-    };
-
-    @action
-    handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this.description = e.target.value;
-    };
-    @action
-    handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this.relationship = e.target.value;
-    };
-    @action
-    handleZoomFollowChange = () => {
-        this.props.sourceDoc.followLinkZoom = !this.props.sourceDoc.followLinkZoom;
-    };
-    @action
-    handleAudioFollowChange = () => {
-        this.props.sourceDoc.followLinkAudio = !this.props.sourceDoc.followLinkAudio;
-    };
-    @action
-    handleRelationshipSearchChange = (result: string) => {
-        this.setRelationshipValue(result);
-        this.toggleRelationshipResults();
-        this.relationship = result;
-    };
-    @computed
-    get editRelationship() {
-        //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS
-        return (
-            <div className="linkEditor-description">
-                <div className="linkEditor-description-label">Relationship:</div>
-                <div className="linkEditor-description-input">
-                    <div className="linkEditor-description-editing">
-                        <input
-                            style={{ width: '100%' }}
-                            id="input"
-                            value={this.relationship}
-                            autoComplete={'off'}
-                            placeholder={'Enter link relationship'}
-                            onKeyDown={this.onRelationshipKey}
-                            onChange={this.handleRelationshipChange}
-                            onFocus={this.onFocus}
-                            onBlur={this.onBlur}></input>
-                        <LinkRelationshipSearch results={this.getRelationshipResults()} display={this.relationshipSearchVisibility} handleRelationshipSearchChange={this.handleRelationshipSearchChange} toggleSearch={this.toggleSearchIsActive} />
-                    </div>
-                    <div className="linkEditor-description-add-button" style={{ background: this.relationshipButtonColor }} onPointerDown={this.onRelationshipDown}>
-                        Set
-                    </div>
-                </div>
-            </div>
-        );
-    }
-    @computed
-    get editZoomFollow() {
-        //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS
-        return (
-            <div className="linkEditor-zoomFollow">
-                <div className="linkEditor-zoomFollow-label">Zoom To Link Target:</div>
-                <div className="linkEditor-zoomFollow-input">
-                    <div className="linkEditor-zoomFollow-editing">
-                        <input type="checkbox" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.handleZoomFollowChange)} defaultChecked={this.zoomFollow} />
-                    </div>
-                </div>
-            </div>
-        );
-    }
-
-    @computed
-    get editAudioFollow() {
-        //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS
-        return (
-            <div className="linkEditor-zoomFollow">
-                <div className="linkEditor-zoomFollow-label">Play Target Audio:</div>
-                <div className="linkEditor-zoomFollow-input">
-                    <div className="linkEditor-zoomFollow-editing">
-                        <input type="checkbox" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.handleAudioFollowChange)} defaultChecked={this.audioFollow} />
-                    </div>
-                </div>
-            </div>
-        );
-    }
-
-    @computed
-    get editDescription() {
-        return (
-            <div className="linkEditor-description">
-                <div className="linkEditor-description-label">Description:</div>
-                <div className="linkEditor-description-input">
-                    <div className="linkEditor-description-editing">
-                        <input style={{ width: '100%' }} autoComplete={'off'} id="input" value={this.description} placeholder={'Enter link description'} onKeyDown={this.onDescriptionKey} onChange={this.handleDescriptionChange}></input>
-                    </div>
-                    <div className="linkEditor-description-add-button" style={{ background: this.buttonColor }} onPointerDown={this.onDescriptionDown}>
-                        Set
-                    </div>
-                </div>
-            </div>
-        );
-    }
-
-    @action
-    changeDropdown = () => {
-        this.openDropdown = !this.openDropdown;
-    };
-
-    @undoBatch
-    changeFollowBehavior = action((follow: string) => {
-        this.openDropdown = false;
-        Doc.GetProto(this.props.linkDoc).followLinkLocation = follow;
-    });
-
-    @computed
-    get followingDropdown() {
-        return (
-            <div className="linkEditor-followingDropdown">
-                <div className="linkEditor-followingDropdown-label">Follow by:</div>
-                <div className="linkEditor-followingDropdown-dropdown">
-                    <div className="linkEditor-followingDropdown-header" onPointerDown={this.changeDropdown}>
-                        {StrCast(this.props.linkDoc.followLinkLocation, 'default')}
-                        <FontAwesomeIcon className="linkEditor-followingDropdown-icon" icon={this.openDropdown ? 'chevron-up' : 'chevron-down'} size={'lg'} />
-                    </div>
-                    <div className="linkEditor-followingDropdown-optionsList" style={{ display: this.openDropdown ? '' : 'none' }}>
-                        <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('default')}>
-                            Default
-                        </div>
-                        <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('add:left')}>
-                            Always opening in new left pane
-                        </div>
-                        <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('add:right')}>
-                            Always opening in new right pane
-                        </div>
-                        <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('replace:right')}>
-                            Always replacing right tab
-                        </div>
-                        <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('replace:left')}>
-                            Always replacing left tab
-                        </div>
-                        <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('fullScreen')}>
-                            Always opening full screen
-                        </div>
-                        <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('add')}>
-                            Always opening in a new tab
-                        </div>
-                        <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('replace')}>
-                            Replacing Tab
-                        </div>
-                        <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('inPlace')}>
-                            Opening in Place
-                        </div>
-                        {this.props.linkDoc.linksToAnnotation ? (
-                            <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('openExternal')}>
-                                Always open in external page
-                            </div>
-                        ) : null}
-                    </div>
-                </div>
-            </div>
-        );
-    }
-
-    @computed get destinationAnchor() {
-        const ldoc = this.props.linkDoc;
-        if (this.props.sourceDoc !== ldoc.anchor1 && this.props.sourceDoc !== ldoc.anchor2) {
-            if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.anchor1).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.anchor2);
-            if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.anchor2).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.anchor1);
-        }
-        return LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc) ?? this.props.sourceDoc;
-    }
-    @action
-    changeEffectDropdown = () => {
-        this.openEffectDropdown = !this.openEffectDropdown;
-    };
-
-    @undoBatch
-    changeEffect = action((follow: string) => {
-        this.openEffectDropdown = false;
-        this.destinationAnchor.presEffect = follow;
-    });
-
-    @computed
-    get effectDropdown() {
-        return (
-            <div className="linkEditor-followingDropdown">
-                <div className="linkEditor-followingDropdown-label">Animation:</div>
-                <div className="linkEditor-followingDropdown-dropdown">
-                    <div className="linkEditor-followingDropdown-header" onPointerDown={this.changeEffectDropdown}>
-                        {StrCast(this.destinationAnchor.presEffect, 'default')}
-                        <FontAwesomeIcon className="linkEditor-followingDropdown-icon" icon={this.openEffectDropdown ? 'chevron-up' : 'chevron-down'} size={'lg'} />
-                    </div>
-                    <div className="linkEditor-followingDropdown-optionsList" style={{ display: this.openEffectDropdown ? '' : 'none' }}>
-                        {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => (
-                            <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeEffect(effect.toString())}>
-                                {effect.toString()}
-                            </div>
-                        ))}
-                    </div>
-                </div>
-            </div>
-        );
-    }
-
-    autoMove = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove))));
-    };
-
-    showAnchor = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.hidden = !this.props.linkDoc.hidden))));
-    };
-
-    showLink = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay))));
-    };
-
-    deleteLink = (e: React.PointerEvent): void => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this.props.linkDoc))));
-    };
-
-    render() {
-        const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
-
-        return !destination ? null : (
-            <div className="linkEditor" tabIndex={0} onKeyDown={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
-                <div className="linkEditor-info">
-                    {!this.props.showLinks ? null : (
-                        <Tooltip title={<div className="dash-tooltip">Return to link menu</div>} placement="top">
-                            <button className="linkEditor-button-back" style={{ display: this.props.hideback ? 'none' : '' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.props.showLinks?.())}>
-                                <FontAwesomeIcon icon="arrow-left" size="sm" />{' '}
-                            </button>
-                        </Tooltip>
-                    )}
-                    <p className="linkEditor-linkedTo">
-                        Editing Link to: <b>{StrCast(destination.proto?.title, StrCast(destination.title, 'untitled'))}</b>
-                    </p>
-                    <Tooltip title={<div className="dash-tooltip">Delete Link</div>}>
-                        <div className="linkEditor-deleteBtn" onPointerDown={this.deleteLink} onClick={e => e.stopPropagation()}>
-                            <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" />
-                        </div>
-                    </Tooltip>
-                </div>
-                <div className="linkEditor-moreInfo">
-                    {this.props.linkDoc.author ? (
-                        <>
-                            {' '}
-                            <b>Author:</b> {StrCast(this.props.linkDoc.author)}
-                        </>
-                    ) : null}
-                    {this.props.linkDoc.creationDate ? (
-                        <>
-                            {' '}
-                            <b>Creation Date:</b>
-                            {DateCast(this.props.linkDoc.creationDate).toString()}
-                        </>
-                    ) : null}
-                </div>
-                {this.editDescription}
-                {this.editRelationship}
-                {this.editZoomFollow}
-                {this.editAudioFollow}
-                <div className="linkEditor-description">
-                    Show Anchor:
-                    <Tooltip title={<div className="dash-tooltip">{this.props.linkDoc.hidden ? 'Show Link Anchor' : 'Hide Link Anchor'}</div>}>
-                        <div
-                            className="linkEditor-button"
-                            style={{ background: this.props.linkDoc.hidden ? 'gray' : '#4476f7' /* $medium-blue                                       */ }}
-                            onPointerDown={this.showAnchor}
-                            onClick={e => e.stopPropagation()}>
-                            <FontAwesomeIcon className="fa-icon" icon={'eye'} size="sm" />
-                        </div>
-                    </Tooltip>
-                </div>
-                <div className="linkEditor-description">
-                    Show Link Line:
-                    <Tooltip title={<div className="dash-tooltip">{this.props.linkDoc.linkDisplay ? 'Hide Link Line' : 'Show Link Line'}</div>}>
-                        <div
-                            className="linkEditor-button"
-                            style={{ background: this.props.linkDoc.hidden ? 'gray' : this.props.linkDoc.linkDisplay ? '#4476f7' /* $medium-blue */ : '' }}
-                            onPointerDown={this.showLink}
-                            onClick={e => e.stopPropagation()}>
-                            <FontAwesomeIcon className="fa-icon" icon={'project-diagram'} size="sm" />
-                        </div>
-                    </Tooltip>
-                </div>
-                <div className="linkEditor-description">
-                    Freeze Anchor:
-                    <Tooltip title={<div className="dash-tooltip">{this.props.linkDoc.linkAutoMove ? 'Click to freeze link anchor position' : 'Click to auto move link anchor'}</div>}>
-                        <div
-                            className="linkEditor-button"
-                            style={{ background: this.props.linkDoc.hidden ? 'gray' : this.props.linkDoc.linkAutoMove ? '' : '#4476f7' /* $medium-blue */ }}
-                            onPointerDown={this.autoMove}
-                            onClick={e => e.stopPropagation()}>
-                            <FontAwesomeIcon className="fa-icon" icon={'play'} size="sm" />
-                        </div>
-                    </Tooltip>
-                </div>
-                {this.followingDropdown}
-                {this.effectDropdown}
-                {PresBox.inputter('0.1', '0.1', '10', NumCast(this.destinationAnchor.presTransition) / 1000, true, (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => (this.destinationAnchor.presTransition = timeInMS)))}
-                <div
-                    className={'slider-headers'}
-                    style={{
-                        marginLeft: 30,
-                        marginRight: 30,
-                        display: 'grid',
-                        justifyContent: 'space-between',
-                        width: '100%',
-                        gridTemplateColumns: 'auto auto auto',
-                    }}>
-                    <div className="slider-text">Fast</div>
-                    <div className="slider-text">Medium</div>
-                    <div className="slider-text">Slow</div>
-                </div>{' '}
-            </div>
-        );
-    }
-}
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 0c46a6d96..c9112eec3 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -1,14 +1,13 @@
-import { action, computed, observable } from 'mobx';
+import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../../fields/Doc';
+import { DocCast } from '../../../fields/Types';
 import { LinkManager } from '../../util/LinkManager';
 import { DocumentView } from '../nodes/DocumentView';
 import { LinkDocPreview } from '../nodes/LinkDocPreview';
-import { LinkEditor } from './LinkEditor';
 import './LinkMenu.scss';
 import { LinkMenuGroup } from './LinkMenuGroup';
 import React = require('react');
-import { emptyFunction } from '../../../Utils';
 
 interface Props {
     docView: DocumentView;
@@ -23,15 +22,9 @@ interface Props {
 @observer
 export class LinkMenu extends React.Component<Props> {
     _editorRef = React.createRef<HTMLDivElement>();
-    @observable _editingLink?: Doc;
     @observable _linkMenuRef = React.createRef<HTMLDivElement>();
 
-    clear = !this.props.clearLinkEditor
-        ? undefined
-        : action(() => {
-              this.props.clearLinkEditor?.();
-              this._editingLink = undefined;
-          });
+    clear = () => this.props.clearLinkEditor?.();
 
     componentDidMount() {
         this.props.clearLinkEditor && document.addEventListener('pointerdown', this.onPointerDown, true);
@@ -43,7 +36,7 @@ export class LinkMenu extends React.Component<Props> {
     onPointerDown = action((e: PointerEvent) => {
         LinkDocPreview.Clear();
         if (!this._linkMenuRef.current?.contains(e.target as any) && !this._editorRef.current?.contains(e.target as any)) {
-            this.clear?.();
+            this.clear();
         }
     });
 
@@ -54,34 +47,20 @@ export class LinkMenu extends React.Component<Props> {
      */
     renderAllGroups = (groups: Map<string, Array<Doc>>): Array<JSX.Element> => {
         const linkItems = Array.from(groups.entries()).map(group => (
-            <LinkMenuGroup
-                key={group[0]}
-                itemHandler={this.props.itemHandler}
-                docView={this.props.docView}
-                sourceDoc={this.props.docView.props.Document}
-                group={group[1]}
-                groupType={group[0]}
-                clearLinkEditor={this.clear}
-                showEditor={action(linkDoc => (this._editingLink = linkDoc))}
-            />
+            <LinkMenuGroup key={group[0]} itemHandler={this.props.itemHandler} docView={this.props.docView} sourceDoc={this.props.docView.props.Document} group={group[1]} groupType={group[0]} clearLinkEditor={this.clear} />
         ));
 
         return linkItems.length ? linkItems : this.props.style ? [<></>] : [<p key="">No links have been created yet. Drag the linking button onto another document to create a link.</p>];
     };
 
     render() {
-        const sourceDoc = this.props.docView.props.Document;
+        const sourceDoc = this.props.docView.rootDoc;
+        const sourceAnchor = this.props.docView.anchorViewDoc ?? sourceDoc;
         const style = this.props.style ?? (dv => ({ left: dv?.left || 0, top: this.props.docView.topMost ? undefined : (dv?.bottom || 0) + 15, bottom: this.props.docView.topMost ? 20 : undefined, maxWidth: 200 }))(this.props.docView.getBounds());
 
         return (
             <div className="linkMenu" ref={this._linkMenuRef} style={{ ...style }}>
-                {this._editingLink ? (
-                    <div className="linkMenu-listEditor">
-                        <LinkEditor sourceDoc={sourceDoc} linkDoc={this._editingLink} showLinks={action(() => (this._editingLink = undefined))} />
-                    </div>
-                ) : (
-                    <div className="linkMenu-list">{this.renderAllGroups(LinkManager.Instance.getRelatedGroupedLinks(sourceDoc))}</div>
-                )}
+                <div className="linkMenu-list">{this.renderAllGroups(LinkManager.Instance.getRelatedGroupedLinks(sourceAnchor))}</div>
             </div>
         );
     }
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 9d2082e21..d02a1c4eb 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -2,19 +2,19 @@ import { observer } from 'mobx-react';
 import { observable, action } from 'mobx';
 import { Doc, StrListCast } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
-import { Cast } from '../../../fields/Types';
+import { Cast, DocCast } from '../../../fields/Types';
 import { LinkManager } from '../../util/LinkManager';
 import { DocumentView } from '../nodes/DocumentView';
 import './LinkMenu.scss';
 import { LinkMenuItem } from './LinkMenuItem';
 import React = require('react');
+import { DocumentType } from '../../documents/DocumentTypes';
 
 interface LinkMenuGroupProps {
     sourceDoc: Doc;
     group: Doc[];
     groupType: string;
     clearLinkEditor?: () => void;
-    showEditor: (linkDoc: Doc) => void;
     docView: DocumentView;
     itemHandler?: (doc: Doc) => void;
 }
@@ -44,25 +44,33 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
     render() {
         const set = new Set<Doc>(this.props.group);
         const groupItems = Array.from(set.keys()).map(linkDoc => {
-            const destination =
-                LinkManager.getOppositeAnchor(linkDoc, this.props.sourceDoc) ||
-                LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, null).annotationOn === this.props.sourceDoc ? Cast(linkDoc.anchor2, Doc, null) : Cast(linkDoc.anchor1, Doc, null));
-            if (destination && this.props.sourceDoc) {
-                return (
-                    <LinkMenuItem
-                        key={linkDoc[Id]}
-                        itemHandler={this.props.itemHandler}
-                        groupType={this.props.groupType}
-                        docView={this.props.docView}
-                        linkDoc={linkDoc}
-                        sourceDoc={this.props.sourceDoc}
-                        destinationDoc={destination}
-                        clearLinkEditor={this.props.clearLinkEditor}
-                        showEditor={this.props.showEditor}
-                        menuRef={this._menuRef}
-                    />
-                );
-            }
+            const sourceDoc =
+                this.props.docView.anchorViewDoc ??
+                (this.props.docView.rootDoc.type === DocumentType.LINK //
+                    ? this.props.docView.props.LayoutTemplateString?.includes('anchor1')
+                        ? DocCast(linkDoc.anchor1)
+                        : DocCast(linkDoc.anchor2)
+                    : this.props.sourceDoc);
+            const destDoc = !sourceDoc
+                ? undefined
+                : this.props.docView.rootDoc.type === DocumentType.LINK
+                ? this.props.docView.props.LayoutTemplateString?.includes('anchor1')
+                    ? DocCast(linkDoc.anchor2)
+                    : DocCast(linkDoc.anchor1)
+                : LinkManager.getOppositeAnchor(linkDoc, sourceDoc) || LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, null).annotationOn === sourceDoc ? Cast(linkDoc.anchor2, Doc, null) : Cast(linkDoc.anchor1, Doc, null));
+            return !destDoc || !sourceDoc ? null : (
+                <LinkMenuItem
+                    key={linkDoc[Id]}
+                    itemHandler={this.props.itemHandler}
+                    groupType={this.props.groupType}
+                    docView={this.props.docView}
+                    linkDoc={linkDoc}
+                    sourceDoc={sourceDoc}
+                    destinationDoc={destDoc}
+                    clearLinkEditor={this.props.clearLinkEditor}
+                    menuRef={this._menuRef}
+                />
+            );
         });
 
         return (
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index c3705b0e1..fb4c6873e 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -18,6 +18,7 @@ import { DocumentView } from '../nodes/DocumentView';
 import { LinkDocPreview } from '../nodes/LinkDocPreview';
 import './LinkMenuItem.scss';
 import React = require('react');
+import { SelectionManager } from '../../util/SelectionManager';
 
 interface LinkMenuItemProps {
     groupType: string;
@@ -26,7 +27,6 @@ interface LinkMenuItemProps {
     sourceDoc: Doc;
     destinationDoc: Doc;
     clearLinkEditor?: () => void;
-    showEditor: (linkDoc: Doc) => void;
     menuRef: React.Ref<HTMLDivElement>;
     itemHandler?: (doc: Doc) => void;
 }
@@ -100,10 +100,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
             },
             emptyFunction,
             action(() => {
+                SelectionManager.SelectView(this.props.docView, false);
                 if ((SettingsManager.propertiesWidth ?? 0) < 100) {
                     SettingsManager.propertiesWidth = 250;
                 }
-                //this.props.showEditor(this.props.linkDoc);
             })
         );
     };
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 627487a9e..99fa62fa7 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -305,7 +305,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                     top: 0,
                     pointerEvents: 'none',
                 }}>
-                <Tooltip title={!DocumentLinksButton.LinkEditorDocView ? <div className="dash-tooltip">{title}</div> : <></>}>{this.linkButtonInner}</Tooltip>
+                {!DocumentLinksButton.LinkEditorDocView ? this.linkButtonInner : <Tooltip title={<div className="dash-tooltip">{title}</div>}>{this.linkButtonInner}</Tooltip>}
             </div>
         );
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2729a5047..a8bea61c9 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
 import { observer, renderReporter } from 'mobx-react';
-import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
+import { AclAdmin, AclEdit, AclPrivate, AnimationSym, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
 import { Document } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
@@ -11,7 +11,7 @@ import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { AudioField } from '../../../fields/URLField';
 import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
 import { MobileInterface } from '../../../mobile/MobileInterface';
@@ -1156,7 +1156,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     anchorPanelHeight = () => this.props.PanelHeight() || 1;
     anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
         // prettier-ignore
-        switch (property) {
+        switch (property.split(':')[0]) {
             case StyleProp.ShowTitle:     return '';
             case StyleProp.PointerEvents: return 'none';
             case StyleProp.LinkSource:    return this.props.Document; // pass the LinkSource to the LinkAnchorBox
@@ -1188,7 +1188,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         TraceMobx();
         if (this.layoutDoc.unrendered || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
         if (this.rootDoc.type === DocumentType.PRES || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return null;
-        const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => !d.hidden);
+        const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => d.linkDisplay);
         return filtered.map((link, i) => (
             <div className="documentView-anchorCont" key={link[Id]}>
                 <DocumentView
@@ -1415,7 +1415,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             clipPath: borderPath.path ? `path('${borderPath.path}')` : undefined,
         });
 
-        const animRenderDoc = Doc.IsHighlighted(this.rootDoc) || PresBox.Instance?.isActiveItemTarget(this.layoutDoc) ? PresBox.AnimationEffect(renderDoc, PresBox.Instance?.activeItem ?? Doc.HighlightBrush.linkFollowEffect, this.rootDoc) : renderDoc;
+        const animRenderDoc = Doc.IsHighlighted(this.rootDoc) || PresBox.Instance?.isActiveItemTarget(this.layoutDoc) ? PresBox.AnimationEffect(renderDoc, PresBox.Instance?.activeItem ?? this.rootDoc[AnimationSym], this.rootDoc) : renderDoc;
         return (
             <div
                 className={`${DocumentView.ROOT_DIV} docView-hack`}
@@ -1535,8 +1535,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     linkButtonInverseScaling = () => (this.props.NativeDimScaling?.() || 1) * this.screenToLocalTransform().Scale;
 
     @computed get linkCountView() {
-        return (this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton) &&
-            DocumentLinksButton.LinkEditorDocView?.rootDoc !== this.rootDoc ? null : (
+        return this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton ? null : (
             <DocumentLinksButton View={this} scaling={this.linkButtonInverseScaling} OnHover={true} Bottom={this.topMost} ShowCount={true} />
         );
     }
@@ -1654,6 +1653,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource);
 
+    @computed get anchorViewDoc() {
+        return this.props.LayoutTemplateString?.includes('anchor2') ? DocCast(this.rootDoc['anchor2']) : this.props.LayoutTemplateString?.includes('anchor1') ? DocCast(this.rootDoc['anchor1']) : this.rootDoc;
+    }
     docViewPathFunc = () => this.docViewPath;
     isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
     select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection);
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index be9565452..e89076c1f 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -1,4 +1,3 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../../fields/Doc';
@@ -7,20 +6,17 @@ import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
-import { SelectionManager } from '../../util/SelectionManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxBaseComponent } from '../DocComponent';
-import { LinkEditor } from '../linking/LinkEditor';
 import { StyleProp } from '../StyleProvider';
 import { FieldView, FieldViewProps } from './FieldView';
 import './LinkAnchorBox.scss';
 import { LinkDocPreview } from './LinkDocPreview';
 import React = require('react');
-import { OpenWhere } from './DocumentView';
-const higflyout = require('@hig/flyout');
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
+import { LinkManager } from '../../util/LinkManager';
+import globalCssVariables = require('../global/globalCssVariables.scss');
+import { SelectionManager } from '../../util/SelectionManager';
 
 @observer
 export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -34,15 +30,23 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     _timeout: NodeJS.Timeout | undefined;
     @observable _x = 0;
     @observable _y = 0;
-    @observable _selected = false;
-    @observable _editing = false;
-    @observable _forceOpen = false;
 
     onPointerDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, false);
+        const anchorContainerDoc = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource);
+        setupMoveUpEvents(
+            this,
+            e,
+            this.onPointerMove,
+            emptyFunction,
+            (e, doubleTap) => {
+                if (doubleTap) LinkFollower.FollowLink(this.rootDoc, anchorContainerDoc, this.props, false);
+                else this.props.select(false);
+            },
+            false
+        );
     };
     onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => {
-        const cdiv = this._ref && this._ref.current && this._ref.current.parentElement;
+        const cdiv = this._ref?.current?.parentElement;
         if (!this._isOpen && cdiv) {
             const bounds = cdiv.getBoundingClientRect();
             const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
@@ -60,58 +64,8 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
         return false;
     });
-    @action
-    onClick = (e: React.MouseEvent) => {
-        if (e.button === 2 || e.ctrlKey || !this.layoutDoc.isLinkButton) {
-            this.props.select(false);
-        }
-        if (!this._doubleTap && !e.ctrlKey && e.button < 2) {
-            const anchorContainerDoc = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource);
-            this._editing = true;
-            anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
-            if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
-                this._timeout = setTimeout(
-                    action(() => {
-                        LinkFollower.FollowLink(this.rootDoc, anchorContainerDoc, this.props, false);
-                        this._editing = false;
-                    }),
-                    300 - (Date.now() - this._lastTap)
-                );
-                e.stopPropagation();
-            }
-        } else {
-            this._timeout && clearTimeout(this._timeout);
-            this._timeout = undefined;
-            this._doubleTap = false;
-            this.openLinkEditor(e);
-            e.stopPropagation();
-        }
-    };
 
-    openLinkDocOnRight = (e: React.MouseEvent) => {
-        this.props.addDocTab(this.rootDoc, OpenWhere.addRight);
-    };
-    openLinkTargetOnRight = (e: React.MouseEvent) => {
-        const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null));
-        alias._isLinkButton = undefined;
-        alias.layoutKey = 'layout';
-        this.props.addDocTab(alias, OpenWhere.addRight);
-    };
-    @action
-    openLinkEditor = action((e: React.MouseEvent) => {
-        SelectionManager.DeselectAll();
-        this._editing = this._forceOpen = true;
-    });
-
-    specificContextMenu = (e: React.MouseEvent): void => {
-        const funcs: ContextMenuProps[] = [];
-        funcs.push({ description: 'Open Link Target on Right', event: () => this.openLinkTargetOnRight(e), icon: 'eye' });
-        funcs.push({ description: 'Open Link on Right', event: () => this.openLinkDocOnRight(e), icon: 'eye' });
-        funcs.push({ description: 'Open Link Editor', event: () => this.openLinkEditor(e), icon: 'eye' });
-        funcs.push({ description: 'Toggle Always Show Link', event: () => (this.props.Document.linkDisplay = !this.props.Document.linkDisplay), icon: 'eye' });
-
-        ContextMenu.Instance.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
-    };
+    specificContextMenu = (e: React.MouseEvent): void => {};
 
     render() {
         TraceMobx();
@@ -122,22 +76,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.BackgroundColor + ':anchor');
         const anchor = this.fieldKey === 'anchor1' ? 'anchor2' : 'anchor1';
         const anchorScale = !this.dataDoc[this.fieldKey + '-useLinkSmallAnchor'] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : 0.25;
-
         const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title);
-        const flyout = (
-            <div className="linkAnchorBoxBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.rootDoc)}>
-                <LinkEditor sourceDoc={Cast(this.dataDoc[this.fieldKey], Doc, null)} hideback={true} linkDoc={this.rootDoc} showLinks={action(() => {})} />
-                {!this._forceOpen ? null : (
-                    <div className="linkAnchorBox-linkCloser" onPointerDown={action(() => (this._isOpen = this._editing = this._forceOpen = false))}>
-                        <FontAwesomeIcon color="dimgray" icon={'times'} size={'sm'} />
-                    </div>
-                )}
-            </div>
-        );
+        const selView = SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('anchor1') ? 'anchor1' : SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('anchor2') ? 'anchor2' : '';
         return (
             <div
+                ref={this._ref}
+                title={targetTitle}
                 className={`linkAnchorBox-cont${small ? '-small' : ''}`}
-                //onPointerLeave={} //LinkDocPreview.Clear}
                 onPointerEnter={e =>
                     LinkDocPreview.SetLinkInfo({
                         docProps: this.props,
@@ -149,24 +94,15 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     })
                 }
                 onPointerDown={this.onPointerDown}
-                onClick={this.onClick}
-                title={targetTitle}
                 onContextMenu={this.specificContextMenu}
-                ref={this._ref}
                 style={{
+                    border: selView && this.rootDoc[selView] === this.rootDoc[this.fieldKey] ? `solid ${globalCssVariables.MEDIUM_GRAY} 2px` : undefined,
                     background,
                     left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`,
                     top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`,
                     transform: `scale(${anchorScale})`,
-                }}>
-                {!this._editing && !this._forceOpen ? null : (
-                    <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout} open={this._forceOpen ? true : undefined} onOpen={() => (this._isOpen = true)} onClose={action(() => (this._isOpen = this._forceOpen = this._editing = false))}>
-                        <span className="linkAnchorBox-button">
-                            <FontAwesomeIcon icon={'eye'} size={'lg'} />
-                        </span>
-                    </Flyout>
-                )}
-            </div>
+                }}
+            />
         );
     }
 }
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index c10751698..c6cabe269 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -94,6 +94,8 @@ export function DocListCastOrNull(field: FieldResult) {
 
 export const WidthSym = Symbol('Width');
 export const HeightSym = Symbol('Height');
+export const AnimationSym = Symbol('Animation');
+export const HighlightSym = Symbol('Highlight');
 export const DataSym = Symbol('Data');
 export const LayoutSym = Symbol('Layout');
 export const FieldsSym = Symbol('Fields');
@@ -329,6 +331,8 @@ export class Doc extends RefField {
     @observable private ___fieldKeys: any = {};
     @observable public [AclSym]: { [key: string]: symbol } = {};
     @observable public [DirectLinksSym]: Set<Doc> = new Set();
+    @observable public [AnimationSym]: Opt<Doc>;
+    @observable public [HighlightSym]: boolean = false;
 
     private [UpdatingFromServer]: boolean = false;
     private [ForceServerWrite]: boolean = false;
@@ -1261,53 +1265,55 @@ export namespace Doc {
     }
 
     export function linkFollowUnhighlight() {
-        Doc.UnhighlightAll();
+        UnhighlightWatchers.length = 0;
+        highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc));
         document.removeEventListener('pointerdown', linkFollowUnhighlight);
-        runInAction(() => (HighlightBrush.linkFollowEffect = undefined));
     }
 
-    let _lastDate = 0;
+    let UnhighlightWatchers: (() => void)[] = [];
+    let UnhighlightTimer: any;
+    export function AddUnlightWatcher(watcher: () => void) {
+        if (UnhighlightTimer) {
+            UnhighlightWatchers.push(watcher);
+        } else watcher();
+    }
     export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presEffect?: Doc) {
-        //linkFollowUnhighlight();
-        // runInAction(() => presEffect && (HighlightBrush.linkFollowEffect = undefined));
-        // setTimeout(() => runInAction(() => presEffect && (HighlightBrush.linkFollowEffect = presEffect)));
-        runInAction(() => presEffect && (HighlightBrush.linkFollowEffect = presEffect));
-        (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs));
+        linkFollowUnhighlight();
+        (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presEffect));
         document.removeEventListener('pointerdown', linkFollowUnhighlight);
         document.addEventListener('pointerdown', linkFollowUnhighlight);
-        const lastDate = (_lastDate = Date.now());
-        window.setTimeout(() => _lastDate === lastDate && linkFollowUnhighlight(), 5000);
+        if (UnhighlightTimer) clearTimeout(UnhighlightTimer);
+        UnhighlightTimer = window.setTimeout(() => {
+            UnhighlightWatchers.forEach(watcher => watcher());
+            linkFollowUnhighlight();
+            UnhighlightTimer = 0;
+        }, 5000);
     }
 
-    export class HighlightBrush {
-        @observable HighlightedDoc: Map<Doc, boolean> = new Map();
-        @observable static linkFollowEffect: Doc | undefined;
-    }
-    const highlightManager = new HighlightBrush();
+    var highlightedDocs = new Set<Doc>();
     export function IsHighlighted(doc: Doc) {
         if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return false;
-        return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc));
+        return doc[HighlightSym] || Doc.GetProto(doc)[HighlightSym];
     }
-    export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) {
+    export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presEffect?: Doc) {
         runInAction(() => {
-            highlightManager.HighlightedDoc.set(doc, true);
-            dataAndDisplayDocs && highlightManager.HighlightedDoc.set(Doc.GetProto(doc), true);
+            doc[AnimationSym] = presEffect;
+            highlightedDocs.add(doc);
+            doc[HighlightSym] = true;
+            if (dataAndDisplayDocs) {
+                highlightedDocs.add(Doc.GetProto(doc));
+                Doc.GetProto(doc)[HighlightSym] = true;
+            }
         });
     }
     export function UnHighlightDoc(doc: Doc) {
         runInAction(() => {
-            highlightManager.HighlightedDoc.set(doc, false);
-            highlightManager.HighlightedDoc.set(Doc.GetProto(doc), false);
+            highlightedDocs.delete(doc);
+            highlightedDocs.delete(Doc.GetProto(doc));
+            doc[HighlightSym] = Doc.GetProto(doc)[HighlightSym] = false;
+            doc[AnimationSym] = undefined;
         });
     }
-    export function UnhighlightAll() {
-        const mapEntries = highlightManager.HighlightedDoc.keys();
-        let docEntry: IteratorResult<Doc>;
-        while (!(docEntry = mapEntries.next()).done) {
-            const targetDoc = docEntry.value;
-            targetDoc && Doc.UnHighlightDoc(targetDoc);
-        }
-    }
     export function UnBrushAllDocs() {
         brushManager.BrushedDoc.clear();
     }
-- 
cgit v1.2.3-70-g09d2

