From 80baae4dcf49f818dca4dbfe340013e0e9d197bb Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 3 Sep 2021 03:58:21 -0400
Subject: fixed drawing of link lines - doesn't generate duplicates, and works
 with text regions, scroll locations, and document anchors.

---
 .../collections/collectionFreeForm/CollectionFreeFormLinkView.tsx | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 16258404d..6fe46bbb2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -43,8 +43,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         if (SnappingManager.GetIsDragging() || !A.ContentDiv || !B.ContentDiv) return;
         setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
         setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
-        const acont = A.rootDoc.type === DocumentType.LINK ? A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
-        const bcont = B.rootDoc.type === DocumentType.LINK ? B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+        const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+        const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
         const adiv = acont.length ? acont[0] : A.ContentDiv;
         const bdiv = bcont.length ? bcont[0] : B.ContentDiv;
         const a = adiv.getBoundingClientRect();
@@ -140,8 +140,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         if (!A.ContentDiv || !B.ContentDiv || !LinkDocs.length) return undefined;
         const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
         const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
-        const adiv = (acont.length ? acont[0] : A.ContentDiv);
-        const bdiv = (bcont.length ? bcont[0] : B.ContentDiv);
+        const adiv = acont.length ? acont[0] : A.ContentDiv;
+        const bdiv = bcont.length ? bcont[0] : B.ContentDiv;
         for (let apdiv = adiv; apdiv; apdiv = apdiv.parentElement as any) if ((apdiv as any).hidden) return;
         for (let bpdiv = bdiv; bpdiv; bpdiv = bpdiv.parentElement as any) if ((bpdiv as any).hidden) return;
         const a = adiv.getBoundingClientRect();
-- 
cgit v1.2.3-70-g09d2


From 64ff849d21273f7440917ef15a4b1c7f1909687c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 3 Sep 2021 13:16:48 -0400
Subject: fixed warnings.  cleaned up link lines a bit more - simplified, and
 allowed multiple aliases to share a link.  fixed link lines to text anchors
 from flapping around wildly when camera pans

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/util/CurrentUserUtils.ts                | 12 +++----
 src/client/util/DocumentManager.ts                 | 38 +++++++++++++---------
 src/client/views/PropertiesButtons.tsx             |  2 +-
 .../CollectionFreeFormLinkView.tsx                 | 13 +++++---
 .../CollectionFreeFormLinksView.tsx                | 19 ++---------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |  1 -
 src/client/views/nodes/PDFBox.tsx                  | 10 +++---
 src/client/views/nodes/WebBox.tsx                  |  4 +--
 .../views/nodes/button/textButton/TextButton.tsx   |  2 +-
 src/client/views/nodes/button/textButton/index.ts  |  2 +-
 .../views/nodes/button/toggleButton/index.ts       |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 13 +++-----
 src/client/views/pdf/PDFViewer.tsx                 |  5 ++-
 15 files changed, 60 insertions(+), 67 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 671e27408..9a45da440 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1217,7 +1217,7 @@ export namespace DocUtils {
             }),
             icon: Doc.toIcon(dragDoc),
         })) as ContextMenuProps[];
-        documentList.push(math)
+        documentList.push(math);
         ContextMenu.Instance.addItem({
             description: "Create document",
             subitems: documentList,
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 56807c63b..f9c55da29 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -832,7 +832,7 @@ export class CurrentUserUtils {
         await doc.myTrails;
         if (doc.myTrails === undefined) {
             const newTrail = ScriptField.MakeScript(`createNewPresentation()`);
-            const newTrailButton:Doc = Docs.Create.FontIconDocument({ onClick: newTrail, _forceActive: true, toolTip: "New trail", _stayInCollection: true, _hideContextMenu: true, title: "New trail", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true });
+            const newTrailButton: Doc = Docs.Create.FontIconDocument({ onClick: newTrail, _forceActive: true, toolTip: "New trail", _stayInCollection: true, _hideContextMenu: true, title: "New trail", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true });
             doc.myTrails = new PrefetchProxy(Docs.Create.TreeDocument([], {
                 title: "My Trails", _showTitle: "title", _height: 100,
                 treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _fitWidth: true, _gridGap: 5, _forceActive: true, childDropAction: "alias",
@@ -852,16 +852,16 @@ export class CurrentUserUtils {
             doc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true });
             // doc.myFileRoot = Docs.Create.TreeDocument([], { title: "file root", _stayInCollection: true, system: true, isFolder: true });
             const newFolder = ScriptField.MakeFunction(`doc.makeFolder()`, { doc: doc.myFilesystem })!;
-            const newFolderButton:Doc = Docs.Create.FontIconDocument({ onClick: newFolder, _forceActive: true, toolTip: "New folder", _stayInCollection: true, _hideContextMenu: true, title: "New folder", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New folder", icon: "folder-plus", system: true });
+            const newFolderButton: Doc = Docs.Create.FontIconDocument({ onClick: newFolder, _forceActive: true, toolTip: "New folder", _stayInCollection: true, _hideContextMenu: true, title: "New folder", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New folder", icon: "folder-plus", system: true });
             doc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([doc.myFileOrphans as Doc], {
-                title: "My Documents", _showTitle: "title",  buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100,
+                title: "My Documents", _showTitle: "title", buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100,
                 treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
                 treeViewTruncateTitleWidth: 150, ignoreClick: true,
                 isFolder: true, treeViewType: "fileSystem", childHideLinkButton: true,
                 _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true,
                 explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
             }));
-            (doc.myTrails as any as Doc).contextMenuScripts = new List<ScriptField>([newFolder!]);
+            (doc.myTrails as any as Doc).contextMenuScripts = new List<ScriptField>([newFolder]);
             (doc.myTrails as any as Doc).contextMenuLabels = new List<string>(["Create new folder"]);
         }
         return doc.myFilesystem as any as Doc;
@@ -870,7 +870,7 @@ export class CurrentUserUtils {
     static setupRecentlyClosedDocs(doc: Doc) {
         if (doc.myRecentlyClosedDocs === undefined) {
             const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`);
-            const clearDocsButton:Doc = Docs.Create.FontIconDocument({ onClick: clearAll, _forceActive: true, toolTip: "Empty recently closed", _stayInCollection: true, _hideContextMenu: true, title: "Empty", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "Empty", icon: "trash", system: true });
+            const clearDocsButton: Doc = Docs.Create.FontIconDocument({ onClick: clearAll, _forceActive: true, toolTip: "Empty recently closed", _stayInCollection: true, _hideContextMenu: true, title: "Empty", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "Empty", icon: "trash", system: true });
             doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], {
                 title: "My Recently Closed", _showTitle: "title", buttonMenu: true, buttonMenuDoc: clearDocsButton, childHideLinkButton: true,
                 treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias",
@@ -1215,7 +1215,7 @@ export class CurrentUserUtils {
     // Import sidebar is where shared documents are contained
     static setupUploadSidebar(doc: Doc) {
         if (doc.myUploadDocs === undefined) {
-            const newUploadButton:Doc = Docs.Create.FontIconDocument({ onClick: ScriptField.MakeScript("importDocument()"), _forceActive: true, toolTip: "Upload from computer", _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Upload", btnType: ButtonType.ClickButton, buttonText: "Upload", icon: "upload", system: true });
+            const newUploadButton: Doc = Docs.Create.FontIconDocument({ onClick: ScriptField.MakeScript("importDocument()"), _forceActive: true, toolTip: "Upload from computer", _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Upload", btnType: ButtonType.ClickButton, buttonText: "Upload", icon: "upload", system: true });
             doc.myUploadDocs = new PrefetchProxy(Docs.Create.StackingDocument([], {
                 title: "My Uploads", _forceActive: true, buttonMenu: true, buttonMenuDoc: newUploadButton, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0,
                 childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true,
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index b72a3189d..9e190ad02 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,19 +1,21 @@
 import { action, observable, runInAction } from 'mobx';
 import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
-import { Cast, NumCast, StrCast } from '../../fields/Types';
+import { Cast } from '../../fields/Types';
 import { returnFalse } from '../../Utils';
 import { DocumentType } from '../documents/DocumentTypes';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { CollectionView } from '../views/collections/CollectionView';
 import { LightboxView } from '../views/LightboxView';
 import { DocumentView, ViewAdjustment } from '../views/nodes/DocumentView';
+import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
 import { Scripting } from './Scripting';
 
 export class DocumentManager {
 
     //global holds all of the nodes (regardless of which collection they're in)
     @observable public DocumentViews: DocumentView[] = [];
+    @observable public LinkAnchorBoxViews: DocumentView[] = [];
     @observable public RecordingEvent = 0;
     @observable public LinkedDocumentViews: { a: DocumentView, b: DocumentView, l: Doc }[] = [];
 
@@ -26,23 +28,24 @@ export class DocumentManager {
     @action
     public AddView = (view: DocumentView) => {
         //console.log("MOUNT " + view.props.Document.title + "/" + view.props.LayoutTemplateString);
-        if (view.props.LayoutTemplateString?.includes("LinkAnchorBox")) {
+        if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
             const viewAnchorIndex = view.props.LayoutTemplateString.includes("anchor2") ? "anchor2" : "anchor1";
-            view.props.LayoutTemplateString && DocListCast(view.rootDoc.links).forEach(link => {
-                DocumentManager.Instance.DocumentViews?.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
-                            });
-                    });
+            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
+                        })
+                    );
             });
+            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));
+        } else {
+            this.DocumentViews.push(view);
         }
-        this.DocumentViews.push(view);
     }
     public RemoveView = action((view: DocumentView) => {
         this.LinkedDocumentViews.slice().forEach(action(pair => {
@@ -52,8 +55,13 @@ export class DocumentManager {
             }
         }));
 
-        const index = this.DocumentViews.indexOf(view);
-        index !== -1 && this.DocumentViews.splice(index, 1);
+        if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
+            const index = this.LinkAnchorBoxViews.indexOf(view);
+            this.LinkAnchorBoxViews.splice(index, 1);
+        } else {
+            const index = this.DocumentViews.indexOf(view);
+            index !== -1 && this.DocumentViews.splice(index, 1);
+        }
     });
 
     //gets all views
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 9fe2122e6..dd737dc9d 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -156,7 +156,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             ["toggleDetail", "Toggle Detail"],
             ["linkInPlace", "Follow Link"],
             ["linkOnRight", "Open Link on Right"]
-        ]
+        ];
         const currentSelection = this.selectedDoc.onClickBehavior;
         // Get items to place into the list
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 6fe46bbb2..3b3e069d8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -5,7 +5,6 @@ import { Id } from "../../../../fields/FieldSymbols";
 import { List } from "../../../../fields/List";
 import { NumCast, StrCast } from "../../../../fields/Types";
 import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
-import { DocumentType } from "../../../documents/DocumentTypes";
 import { LinkManager } from "../../../util/LinkManager";
 import { SnappingManager } from "../../../util/SnappingManager";
 import { DocumentView } from "../../nodes/DocumentView";
@@ -69,8 +68,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         } else {
             const m = targetAhyperlink.getBoundingClientRect();
             const mp = A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
-            linkDoc.anchor1_x = Math.min(1, mp[0] / A.props.PanelWidth()) * 100;
-            linkDoc.anchor1_y = Math.min(1, mp[1] / A.props.PanelHeight()) * 100;
+            const mpx = mp[0] / A.props.PanelWidth();
+            const mpy = mp[1] / A.props.PanelHeight();
+            if (mpx >= 0 && mpx <= 1) linkDoc.anchor1_x = mpx * 100;
+            if (mpy >= 0 && mpy <= 1) linkDoc.anchor1_y = mpy * 100;
         }
         if (!targetBhyperlink) {
             if (linkDoc.linkAutoMove) {
@@ -80,8 +81,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         } else {
             const m = targetBhyperlink.getBoundingClientRect();
             const mp = B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
-            linkDoc.anchor2_x = Math.min(1, mp[0] / B.props.PanelWidth()) * 100;
-            linkDoc.anchor2_y = Math.min(1, mp[1] / B.props.PanelHeight()) * 100;
+            const mpx = mp[0] / B.props.PanelWidth();
+            const mpy = mp[1] / B.props.PanelHeight();
+            if (mpx >= 0 && mpx <= 1) linkDoc.anchor2_x = mpx * 100;
+            if (mpy >= 0 && mpy <= 1) linkDoc.anchor2_y = mpy * 100;
         }
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index e812064b7..dacbb3508 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,30 +1,17 @@
 import { computed } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../../../fields/Doc";
 import { Id } from "../../../../fields/FieldSymbols";
-import { Utils } from "../../../../Utils";
 import { DocumentManager } from "../../../util/DocumentManager";
-import { DocumentView } from "../../nodes/DocumentView";
 import "./CollectionFreeFormLinksView.scss";
 import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
 import React = require("react");
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { LinkManager } from "../../../util/LinkManager";
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component {
     @computed get uniqueConnections() {
-        const connections = DocumentManager.Instance.LinkedDocumentViews
-            .filter(c => c.a.props.Document.type === DocumentType.LINK || c.b.props.Document.type === DocumentType.LINK)
-            .reduce((drawnPairs, connection) => {
-                const matchingPairs = drawnPairs.filter(pair => connection.a === pair.a && connection.b === pair.b);
-                matchingPairs.forEach(drawnPair => drawnPair.l.add(connection.l));
-                if (!matchingPairs.length) drawnPairs.push({ a: connection.a, b: connection.b, l: new Set<Doc>([connection.l]) });
-                return drawnPairs;
-            }, [] as { a: DocumentView, b: DocumentView, l: Set<Doc> }[]);
-        const set = new Map<Doc, { a: DocumentView, b: DocumentView, l: Doc[] }>();
-        connections.map(c => !set.has(Array.from(c.l)[0]) && set.set(Array.from(c.l)[0], { a: c.a, b: c.b, l: Array.from(c.l) }));
-        return Array.from(set.values()).map(c => <CollectionFreeFormLinkView key={c.l[0][Id]} A={c.a} B={c.b} LinkDocs={c.l} />);
+        return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)).map(c =>
+            <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />
+        );
     }
 
     render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2deaea823..018c2a895 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1457,7 +1457,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             getTransform={this.getTransform}
             isAnnotationOverlay={this.isAnnotationOverlay}>
             <div ref={this._marqueeRef} style={{ display: this.props.dontRenderDocuments ? "none" : undefined }}>
-                {this.layoutDoc["_backgroundGridShow"] ? this.backgroundGrid : (null)}
+                {this.layoutDoc._backgroundGridShow ? this.backgroundGrid : (null)}
                 <CollectionFreeFormViewPannableContents
                     isAnnotationOverlay={this.isAnnotationOverlay}
                     transform={this.contentTransform}
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 8f9959693..1e0172d24 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -3,7 +3,6 @@ import { action, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
-import { Id } from "../../../fields/FieldSymbols";
 import { makeInterface } from "../../../fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { TraceMobx } from "../../../fields/util";
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 2bafb3b7f..9706d73c7 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -3,13 +3,13 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
-import { Doc, Opt, WidthSym, DocListCast } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt, WidthSym } from "../../../fields/Doc";
 import { documentSchema } from '../../../fields/documentSchemas';
 import { makeInterface } from "../../../fields/Schema";
-import { Cast, NumCast, StrCast, BoolCast } from '../../../fields/Types';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { PdfField } from "../../../fields/URLField";
 import { TraceMobx } from '../../../fields/util';
-import { Utils, setupMoveUpEvents, emptyFunction, returnOne } from '../../../Utils';
+import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { KeyCodes } from '../../util/KeyCodes';
 import { undoBatch } from '../../util/UndoManager';
@@ -17,14 +17,14 @@ import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeF
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
+import { Colors } from '../global/globalEnums';
+import { AnchorMenu } from '../pdf/AnchorMenu';
 import { PDFViewer } from "../pdf/PDFViewer";
 import { SidebarAnnos } from '../SidebarAnnos';
 import { FieldView, FieldViewProps } from './FieldView';
 import { pageSchema } from "./ImageBox";
 import "./PDFBox.scss";
 import React = require("react");
-import { AnchorMenu } from '../pdf/AnchorMenu';
-import { Colors } from '../global/globalEnums';
 
 type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
 const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index eaedd48c7..7e46d8433 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as WebRequest from 'web-request';
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
@@ -24,6 +24,7 @@ import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent";
 import { DocumentDecorations } from "../DocumentDecorations";
+import { Colors } from "../global/globalEnums";
 import { LightboxView } from "../LightboxView";
 import { MarqueeAnnotator } from "../MarqueeAnnotator";
 import { AnchorMenu } from "../pdf/AnchorMenu";
@@ -33,7 +34,6 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { LinkDocPreview } from "./LinkDocPreview";
 import "./WebBox.scss";
 import React = require("react");
-import { Colors } from "../global/globalEnums";
 const _global = (window /* browser */ || global /* node */) as any;
 const htmlToText = require("html-to-text");
 
diff --git a/src/client/views/nodes/button/textButton/TextButton.tsx b/src/client/views/nodes/button/textButton/TextButton.tsx
index 414b50dcb..e18590a95 100644
--- a/src/client/views/nodes/button/textButton/TextButton.tsx
+++ b/src/client/views/nodes/button/textButton/TextButton.tsx
@@ -12,6 +12,6 @@ export class TextButton extends Component<IButtonProps> {
         return (<div className={`menuButton ${this.props.type}`} style={{ opacity: 1, backgroundColor: this.props.backgroundColor, color: this.props.color }}>
             <FontAwesomeIcon className={`fontIconBox-icon-${this.props.type}`} icon={this.props.icon} color={this.props.color} />
             {this.props.label}
-        </div>)
+        </div>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/button/textButton/index.ts b/src/client/views/nodes/button/textButton/index.ts
index 01fcde54f..01d62eb7e 100644
--- a/src/client/views/nodes/button/textButton/index.ts
+++ b/src/client/views/nodes/button/textButton/index.ts
@@ -1 +1 @@
-export * from './TextButton'
\ No newline at end of file
+export * from './TextButton';
\ No newline at end of file
diff --git a/src/client/views/nodes/button/toggleButton/index.ts b/src/client/views/nodes/button/toggleButton/index.ts
index 48b28fb4c..cdb9c527c 100644
--- a/src/client/views/nodes/button/toggleButton/index.ts
+++ b/src/client/views/nodes/button/toggleButton/index.ts
@@ -1 +1 @@
-export * from './ToggleButton'
\ No newline at end of file
+export * from './ToggleButton';
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index bc578f95d..cad9af2b0 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1448,18 +1448,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
             const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
             if (children) {
-                var proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
-                var scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
+                const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
+                const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
                 if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
                     const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
                     if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
                         setScrollHeight();
-                        // setTimeout(() => {
-                        //     proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
-                        //     scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
-                        //     scrollHeight && setScrollHeight();
-                        // }, 10);
-                    } else setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template.  So set a timeout and hope its arrived...
+                    } else {
+                        setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template.  So set a timeout and hope its arrived...
+                    }
                 }
             }
         }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 02010e123..d953c6b6c 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -2,14 +2,13 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
-import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
 import { Id } from "../../../fields/FieldSymbols";
 import { InkTool } from "../../../fields/InkField";
-import { createSchema } from "../../../fields/Schema";
 import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
 import { PdfField } from "../../../fields/URLField";
 import { TraceMobx } from "../../../fields/util";
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils, returnFalse, returnEmptyString, returnEmptyFilter } from "../../../Utils";
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from "../../../Utils";
 import { DocUtils } from "../../documents/Documents";
 import { Networking } from "../../Network";
 import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-- 
cgit v1.2.3-70-g09d2


From f097eefd91614d4efa2a89b3d8c7d1071d6c0b6f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 17 Sep 2021 14:07:09 -0400
Subject: added 'unset' docFilters.  changed link doc views to use comparison
 box with title/caption.  fixed linkEditor to write to data doc. generalized
 comparisonBox rendering to use parameterized fields. fixed pdf/web to honor
 pointerEvents none prop and fixed textAnnotations to get rendered once as an
 Annotation. moved filterIcon stuff into DocumentView

---
 src/Utils.ts                                          |  4 +++-
 src/client/DocServer.ts                               |  2 ++
 src/client/documents/Documents.ts                     | 17 +++++++++++------
 src/client/views/collections/CollectionView.tsx       | 12 ------------
 .../collectionFreeForm/CollectionFreeFormLinkView.tsx |  6 +++---
 src/client/views/linking/LinkEditor.tsx               | 11 ++++++-----
 src/client/views/nodes/ComparisonBox.tsx              |  8 +++++---
 src/client/views/nodes/DocumentView.tsx               | 19 +++++++++++++++++--
 src/client/views/nodes/LinkBox.tsx                    | 19 ++++++-------------
 src/client/views/nodes/LinkDescriptionPopup.tsx       |  7 ++++---
 src/client/views/nodes/PDFBox.tsx                     |  9 ++++-----
 src/client/views/nodes/WebBox.tsx                     | 14 ++++++++------
 src/client/views/pdf/Annotation.scss                  |  3 +++
 src/client/views/pdf/PDFViewer.tsx                    | 13 +++++++------
 src/fields/Doc.ts                                     |  2 +-
 15 files changed, 80 insertions(+), 66 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index 6eacd8296..e11f1154e 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -125,7 +125,9 @@ export namespace Utils {
         // bcz: isTransparent(__value__) is a hack.  it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one
         return `backgroundColor:${isTransparentFunctionHack},${noRecursionHack}:x`;// bcz: hack.  noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field
     }
-
+    export function PropUnsetFilter(prop: string) {
+        return `${prop}:any,${noRecursionHack}:unset`;
+    }
 
     export function toRGBAstr(col: { r: number, g: number, b: number, a?: number }) {
         return "rgba(" + col.r + "," + col.g + "," + col.b + (col.a !== undefined ? "," + col.a : "") + ")";
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index e498a7cca..3b376a0e7 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -241,6 +241,7 @@ export namespace DocServer {
             // the field has been returned from the server
             const getSerializedField = Utils.EmitCallback(_socket, MessageStore.GetRefField, id);
 
+            console.log(id)
             // when the serialized RefField has been received, go head and begin deserializing it into an object.
             // Here, once deserialized, we also invoke .proto to 'load' the document's prototype, which ensures that all
             // future .proto calls on the Doc won't have to go farther than the cache to get their actual value.
@@ -264,6 +265,7 @@ export namespace DocServer {
                 } else {
                     delete _cache[id];
                 }
+                console.log(id, field);
                 return field;
                 // either way, overwrite or delete any promises cached at this id (that we inserted as flags
                 // to indicate that the field was in the process of being fetched). Now everything
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f50f306a3..e8185400e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -962,13 +962,17 @@ export namespace DocUtils {
                 // metadata facets that exist
                 const exists = Object.keys(facet).filter(value => facet[value] === "exists");
 
+                // metadata facets that exist
+                const unsets = Object.keys(facet).filter(value => facet[value] === "unset");
+
                 // facets that have an x next to them
                 const xs = Object.keys(facet).filter(value => facet[value] === "x");
 
-                if (!exists.length && !xs.length && !checks.length && !matches.length) return true;
+                if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true;
                 const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value));
                 const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value));
                 const satisfiesExistsFacets = !exists.length ? true : exists.some(value => d[facetKey] !== undefined);
+                const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined);
                 const satisfiesMatchFacets = !matches.length ? true : matches.some(value => {
                     if (facetKey.startsWith("*")) { //  fields starting with a '*' are used to match families of related fields.  ie, *lastModified will match text-lastModified, data-lastModified, etc
                         const allKeys = Array.from(Object.keys(d));
@@ -980,11 +984,11 @@ export namespace DocUtils {
                 });
                 // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria
                 if ((parentCollection?.currentFilter as Doc)?.filterBoolean === "OR") {
-                    if (satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
+                    if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
                 }
                 // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria
                 else {
-                    if (!satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
+                    if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
                 }
 
             }
@@ -1088,10 +1092,11 @@ export namespace DocUtils {
             "anchor2-useLinkSmallAnchor": target.doc.useLinkSmallAnchor ? true : undefined,
             "acl-Public": SharingPermissions.Augment,
             "_acl-Public": SharingPermissions.Augment,
-            layout_linkView: Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null),
-            linkDisplay: true, hidden: true,
+            linkDisplay: true,
+            hidden: true,
             linkRelationship,
-            _layoutKey: "layout_linkView",
+            _showCaption: "description",
+            _showTitle: "linkRelationship",
             description
         }, id), showPopup);
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 5c9c8063b..38e027fb3 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -246,12 +246,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
     childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null);
     @computed get childLayoutString() { return StrCast(this.rootDoc.childLayoutString); }
 
-    /**
-    * Shows the filter icon if it's a user-created collection which isn't a dashboard and has some docFilters applied on it or on the current dashboard.
-    */
-    @computed get showFilterIcon() {
-        return this.props.Document.viewType !== CollectionViewType.Docking && !Doc.IsSystem(this.props.Document) && this._subView?.IsFiltered();
-    }
 
     @observable _subView: any = undefined;
 
@@ -280,12 +274,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             style={{ pointerEvents: this.props.layerProvider?.(this.rootDoc) === false ? "none" : undefined }}>
             {this.showIsTagged()}
             {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
-            {this.showFilterIcon ?
-                <FontAwesomeIcon icon={"filter"} size="lg"
-                    style={{ position: 'absolute', top: '1%', right: '1%', cursor: "pointer", padding: 1, color: this.showFilterIcon === "hasFilter" ? '#18c718bd' : "orange", zIndex: 1 }}
-                    onPointerDown={action(e => { this.props.select(false); CurrentUserUtils.propertiesWidth = 250; e.stopPropagation(); })}
-                />
-                : (null)}
         </div>);
     }
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 3b3e069d8..2cb487588 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,6 +1,6 @@
 import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../../../fields/Doc";
+import { Doc, Field } from "../../../../fields/Doc";
 import { Id } from "../../../../fields/FieldSymbols";
 import { List } from "../../../../fields/List";
 import { NumCast, StrCast } from "../../../../fields/Types";
@@ -180,7 +180,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         if (!this.renderData) return (null);
         const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
         LinkManager.currentLink = this.props.LinkDocs[0];
-        const linkRelationship = StrCast(LinkManager.currentLink?.linkRelationship); //get string representing relationship
+        const linkRelationship = Field.toString(LinkManager.currentLink?.linkRelationship as any as Field); //get string representing relationship
         const linkRelationshipList = Doc.UserDoc().linkRelationshipList as List<string>;
         const linkColorList = Doc.UserDoc().linkColorList as List<string>;
         //access stroke color using index of the relationship in the color list (default black)
@@ -189,7 +189,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
             <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2", stroke: strokeColor }}
                 d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} />
             {textX === undefined ? (null) : <text className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown} >
-                {StrCast(this.props.LinkDocs[0].description)}
+                {Field.toString(this.props.LinkDocs[0].description as any as Field)}
             </text>}
         </>);
     }
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 240a71c3e..219f7d3a2 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -2,13 +2,14 @@ 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, StrListCast } from "../../../fields/Doc";
-import { DateCast, StrCast } from "../../../fields/Types";
+import { Doc, StrListCast, Field } from "../../../fields/Doc";
+import { DateCast, StrCast, Cast } 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 { ToString } from "../../../fields/FieldSymbols";
 
 
 interface LinkEditorProps {
@@ -20,7 +21,7 @@ interface LinkEditorProps {
 @observer
 export class LinkEditor extends React.Component<LinkEditorProps> {
 
-    @observable description = StrCast(LinkManager.currentLink?.description);
+    @observable description = Field.toString(LinkManager.currentLink?.description as any as Field);
     @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship);
     @observable openDropdown: boolean = false;
     @observable showInfo: boolean = false;
@@ -41,7 +42,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
     @undoBatch
     setRelationshipValue = action((value: string) => {
         if (LinkManager.currentLink) {
-            LinkManager.currentLink.linkRelationship = value;
+            Doc.GetProto(LinkManager.currentLink).linkRelationship = value;
             const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList);
             const linkColorList = StrListCast(Doc.UserDoc().linkColorList);
             // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color 
@@ -79,7 +80,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
     @undoBatch
     setDescripValue = action((value: string) => {
         if (LinkManager.currentLink) {
-            LinkManager.currentLink.description = value;
+            Doc.GetProto(LinkManager.currentLink).description = value;
             this.buttonColor = "rgb(62, 133, 55)";
             setTimeout(action(() => this.buttonColor = ""), 750);
             return true;
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 6708a08ee..72c7e4f45 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -15,6 +15,7 @@ import "./ComparisonBox.scss";
 import { DocumentView, DocumentViewProps } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import React = require("react");
+import { DocumentType } from '../../documents/DocumentTypes';
 
 export const comparisonSchema = createSchema({});
 
@@ -87,7 +88,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
             </div>;
         };
         const displayDoc = (which: string) => {
-            const whichDoc = Cast(this.dataDoc[`compareBox-${which}`], Doc, null);
+            var whichDoc = Cast(this.dataDoc[which], Doc, null);
+            if (whichDoc.type === DocumentType.MARKER) whichDoc = Cast(whichDoc.annotationOn, Doc, null);
             return whichDoc ? <>
                 <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
                     isContentActive={returnFalse}
@@ -112,9 +114,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
 
         return (
             <div className={`comparisonBox${this.props.isContentActive() || SnappingManager.GetIsDragging() ? "-interactive" : ""}` /* change className to easily disable/enable pointer events in CSS */}>
-                {displayBox("after", 1, this.props.PanelWidth() - 3)}
+                {displayBox(this.fieldKey === "data" ? "compareBox-after" : `${this.fieldKey}2`, 1, this.props.PanelWidth() - 3)}
                 <div className="clip-div" style={{ width: clipWidth, transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, "gray") }}>
-                    {displayBox("before", 0, 0)}
+                    {displayBox(this.fieldKey === "data" ? "compareBox-before" : `${this.fieldKey}1`, 0, 0)}
                 </div>
 
                 <div className="slide-bar" style={{ left: `calc(${clipWidth} - 0.5px)`, cursor: NumCast(this.layoutDoc._clipWidth) < 5 ? "e-resize" : NumCast(this.layoutDoc._clipWidth) / 100 > (this.props.PanelWidth() - 5) / this.props.PanelWidth() ? "w-resize" : undefined }}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 46a8b6629..35249c7f4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -13,7 +13,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Ty
 import { AudioField } from "../../../fields/URLField";
 import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
 import { MobileInterface } from '../../../mobile/MobileInterface';
-import { emptyFunction, hasDescendantTarget, OmitKeys, returnTrue, returnVal, Utils, lightOrDark, simulateMouseClick } from "../../../Utils";
+import { emptyFunction, hasDescendantTarget, OmitKeys, returnTrue, returnVal, Utils, lightOrDark, simulateMouseClick, returnEmptyString } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -802,6 +802,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
     }
 
+    collectionFilters = () => StrListCast(this.props.Document._docFilters);
+    collectionRangeDocFilters = () => StrListCast(this.props.Document._docRangeFilters);
+    @computed get showFilterIcon() {
+        return this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" :
+            this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined
+    }
     rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
     panelHeight = () => this.props.PanelHeight() - this.headerMargin;
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
@@ -832,7 +838,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             </div>;
         return <div className="documentView-contentsView"
             style={{
-                pointerEvents: this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none",
+                pointerEvents: this.props.pointerEvents as any ? this.props.pointerEvents as any : (this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none"),
                 height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
             }}>
             <DocumentContentsView key={1} {...this.props}
@@ -869,6 +875,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     anchorPanelHeight = () => this.props.PanelHeight() || 1;
     anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
         switch (property) {
+            case StyleProp.ShowTitle: return "";
             case StyleProp.PointerEvents: return "none";
             case StyleProp.LinkSource: return this.props.Document;// pass the LinkSource to the LinkAnchorBox
             default: return this.props.styleProvider?.(doc, props, property);
@@ -901,6 +908,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     PanelWidth={this.anchorPanelWidth}
                     PanelHeight={this.anchorPanelHeight}
                     dontRegisterView={false}
+                    showTitle={returnEmptyString}
+                    hideCaptions={true}
                     fitWidth={returnTrue}
                     styleProvider={this.anchorStyleProvider}
                     removeDocument={this.hideLinkAnchor}
@@ -1087,6 +1096,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     </div>
                 </>
             }
+            {this.showFilterIcon ?
+                <FontAwesomeIcon icon={"filter"} size="lg"
+                    style={{ position: 'absolute', top: '1%', right: '1%', cursor: "pointer", padding: 1, color: this.showFilterIcon === "hasFilter" ? '#18c718bd' : "orange", zIndex: 1 }}
+                    onPointerDown={action(e => { this.props.select(false); CurrentUserUtils.propertiesWidth = 250; e.stopPropagation(); })}
+                />
+                : (null)}
         </div>;
     }
 }
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 55ea45bb8..b82d16677 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,10 +2,10 @@ import React = require("react");
 import { observer } from "mobx-react";
 import { documentSchema } from "../../../fields/documentSchemas";
 import { makeInterface } from "../../../fields/Schema";
-import { returnFalse } from "../../../Utils";
-import { CollectionTreeView } from "../collections/CollectionTreeView";
+import { emptyFunction, returnFalse } from "../../../Utils";
 import { ViewBoxBaseComponent } from "../DocComponent";
 import { StyleProp } from "../StyleProvider";
+import { ComparisonBox } from "./ComparisonBox";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./LinkBox.scss";
 
@@ -20,22 +20,15 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>(
         if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => this.dataDoc.treeViewOpen = true);
         return <div className={`linkBox-container${this.isContentActive() ? "-interactive" : ""}`}
             style={{ background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }} >
-            <CollectionTreeView {...this.props}
-                childDocuments={[this.dataDoc]}
-                treeViewOpen={true}
-                treeViewExpandedView={"fields"}
-                treeViewHideTitle={true}
-                treeViewSkipFields={["treeViewExpandedView", "aliases", "_removeDropProperties",
-                    "treeViewOpen", "aliasNumber", "isPrototype", "creationDate", "author"]}
+            <ComparisonBox {...this.props}
+                fieldKey="anchor"
+                setHeight={emptyFunction}
                 dontRegisterView={true}
                 renderDepth={this.props.renderDepth + 1}
-                CollectionView={undefined}
-                isAnyChildContentActive={returnFalse}
                 isContentActive={this.isContentActiveFunc}
                 addDocument={returnFalse}
                 removeDocument={returnFalse}
-                moveDocument={returnFalse}>
-            </CollectionTreeView>
+                moveDocument={returnFalse} />
         </div>;
     }
 } 
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index b62a4dd56..a9d33f161 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -1,8 +1,9 @@
 import React = require("react");
+import { action, observable } from "mobx";
 import { observer } from "mobx-react";
-import "./LinkDescriptionPopup.scss";
-import { observable, action } from "mobx";
+import { Doc } from "../../../fields/Doc";
 import { LinkManager } from "../../util/LinkManager";
+import "./LinkDescriptionPopup.scss";
 import { TaskCompletionBox } from "./TaskCompletedBox";
 
 
@@ -25,7 +26,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
     onDismiss = (add: boolean) => {
         LinkDescriptionPopup.descriptionPopup = false;
         if (add) {
-            LinkManager.currentLink && (LinkManager.currentLink.description = this.description);
+            LinkManager.currentLink && (Doc.GetProto(LinkManager.currentLink).description = this.description);
         }
     }
 
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index f0a502e31..30b4dc92a 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -3,9 +3,9 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
-import { Doc, DocListCast, Opt, WidthSym } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt, WidthSym, StrListCast } from "../../../fields/Doc";
 import { documentSchema } from '../../../fields/documentSchemas';
-import { makeInterface } from "../../../fields/Schema";
+import { makeInterface, listSpec } from "../../../fields/Schema";
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { PdfField } from "../../../fields/URLField";
 import { TraceMobx } from '../../../fields/util';
@@ -25,6 +25,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { pageSchema } from "./ImageBox";
 import "./PDFBox.scss";
 import React = require("react");
+import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 
 type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
 const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
@@ -249,9 +250,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable _showSidebar = false;
     @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
 
-    contentScaling = () => {
-        return 1;
-    }
+    contentScaling = () => 1;
     @computed get renderPdfView() {
         TraceMobx();
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 6a20ca14a..60a83d612 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -532,7 +532,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             NumCast(this.layoutDoc.nativeWidth)
 
     @computed get content() {
-        return <div className={"webBox-cont" + (!this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
+        const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents !== "none" && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting;
+        return <div className={"webBox-cont" + (interactive ? "-interactive" : "")}
             style={{ width: !this.layoutDoc.allowReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}>
             {this.urlContent}
         </div>;
@@ -575,10 +576,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
-    transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
+    basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")];
+    transparentFilter = () => [Utils.IsTransparentFilter(), ...this.basicFilter()];
+    opaqueFilter = () => [Utils.IsOpaqueFilter(), ...this.basicFilter()];
     render() {
-        const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined;
+        const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : this.props.pointerEvents ? this.props.pointerEvents as any : undefined;
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const scale = previewScale * (this.props.scaling?.() || 1);
         const renderAnnotations = (docFilters?: () => string[]) =>
@@ -593,7 +595,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 ScreenToLocalTransform={this.scrollXf}
                 scaling={returnOne}
                 dropAction={"alias"}
-                docFilters={docFilters || this.props.docFilters}
+                docFilters={docFilters || this.basicFilter}
                 dontRenderDocuments={docFilters ? false : true}
                 select={emptyFunction}
                 ContentScaling={returnOne}
@@ -606,7 +608,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 pointerEvents={CurrentUserUtils.SelectedTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
         return (
             <div className="webBox" ref={this._mainCont}
-                style={{ pointerEvents: this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none" }} >
+                style={{ pointerEvents: this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none" }} >
                 <div className={`webBox-container`} style={{ pointerEvents }} onContextMenu={this.specificContextMenu}>
                     <base target="_blank" />
                     <div className={"webBox-outerContent"} ref={this._outerRef}
diff --git a/src/client/views/pdf/Annotation.scss b/src/client/views/pdf/Annotation.scss
index e98f071c3..1de60ffed 100644
--- a/src/client/views/pdf/Annotation.scss
+++ b/src/client/views/pdf/Annotation.scss
@@ -4,4 +4,7 @@
     position: absolute;
     background-color: rgba(146, 245, 95, 0.467);
     transition: opacity 0.5s;
+    &:hover {
+        cursor: pointer;
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 7aa18e46f..78adedf5b 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -179,7 +179,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         let focusSpeed: Opt<number>;
         if (doc !== this.props.rootDoc && mainCont) {
             const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1);
-            const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight);
+            const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight);
             if (scrollTo !== undefined) {
                 focusSpeed = 500;
 
@@ -502,8 +502,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
     overlayTransform = () => this.scrollXf().scale(1 / this._zoomed);
     panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
     panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
-    transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
+    basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")];
+    transparentFilter = () => [Utils.IsTransparentFilter(), ...this.basicFilter()];
+    opaqueFilter = () => [Utils.IsOpaqueFilter(), ...this.basicFilter()];
     @computed get overlayLayer() {
         const renderAnnotations = (docFilters?: () => string[]) =>
             <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
@@ -516,7 +517,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 select={emptyFunction}
                 ContentScaling={this.contentZoom}
                 bringToFront={emptyFunction}
-                docFilters={docFilters || this.props.docFilters}
+                docFilters={docFilters || this.basicFilter}
                 dontRenderDocuments={docFilters ? false : true}
                 CollectionView={undefined}
                 ScreenToLocalTransform={this.overlayTransform}
@@ -543,7 +544,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         </div>;
     }
     @computed get pdfViewerDiv() {
-        return <div className={"pdfViewerDash-text" + (this._textSelecting && (this.props.isSelected() || this.props.isContentActive()) ? "-selected" : "")} ref={this._viewer} />;
+        return <div className={"pdfViewerDash-text" + (this.props.pointerEvents !== "none" && this._textSelecting && (this.props.isSelected() || this.props.isContentActive()) ? "-selected" : "")} ref={this._viewer} />;
     }
     @computed get contentScaling() { return this.props.ContentScaling?.() || 1; }
     @computed get standinViews() {
@@ -556,7 +557,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     render() {
         TraceMobx();
         return <div className="pdfViewer-content">
-            <div className={`pdfViewerDash${this.props.isContentActive() ? "-interactive" : ""}`} ref={this._mainCont}
+            <div className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents !== "none" ? "-interactive" : ""}`} ref={this._mainCont}
                 onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
                 style={{
                     overflowX: this._zoomed !== 1 ? "scroll" : undefined,
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 4d040f3bc..57bd0f46f 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1136,7 +1136,7 @@ export namespace Doc {
     // filters document in a container collection:
     // all documents with the specified value for the specified key are included/excluded
     // based on the modifiers :"check", "x", undefined
-    export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: "remove" | "match" | "check" | "x" | "exists", toggle?: boolean, fieldSuffix?: string, append: boolean = true) {
+    export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: "remove" | "match" | "check" | "x" | "exists" | "unset", toggle?: boolean, fieldSuffix?: string, append: boolean = true) {
         if (!container) return;
         const filterField = "_" + (fieldSuffix ? fieldSuffix + "-" : "") + "docFilters";
         const docFilters = Cast(container[filterField], listSpec("string"), []);
-- 
cgit v1.2.3-70-g09d2


From 0079b81fdd56e8aac4ded4c82ecb013fe06057a2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 22 Sep 2021 12:47:27 -0400
Subject: fixed default color of link lines in dark mode.

---
 .../collections/collectionFreeForm/CollectionFreeFormLinkView.tsx   | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 2cb487588..f6c2707da 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -3,9 +3,11 @@ import { observer } from "mobx-react";
 import { Doc, Field } from "../../../../fields/Doc";
 import { Id } from "../../../../fields/FieldSymbols";
 import { List } from "../../../../fields/List";
-import { NumCast, StrCast } from "../../../../fields/Types";
+import { NumCast } from "../../../../fields/Types";
 import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
 import { LinkManager } from "../../../util/LinkManager";
+import { ColorScheme } from "../../../util/SettingsManager";
 import { SnappingManager } from "../../../util/SnappingManager";
 import { DocumentView } from "../../nodes/DocumentView";
 import "./CollectionFreeFormLinkView.scss";
@@ -184,7 +186,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         const linkRelationshipList = Doc.UserDoc().linkRelationshipList as List<string>;
         const linkColorList = Doc.UserDoc().linkColorList as List<string>;
         //access stroke color using index of the relationship in the color list (default black)
-        const strokeColor = linkRelationshipList.indexOf(linkRelationship) === -1 ? "black" : linkColorList[linkRelationshipList.indexOf(linkRelationship)];
+        const strokeColor = linkRelationshipList.indexOf(linkRelationship) === -1 ? (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "white" : "black") : linkColorList[linkRelationshipList.indexOf(linkRelationship)];
         return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
             <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2", stroke: strokeColor }}
                 d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} />
-- 
cgit v1.2.3-70-g09d2

