From 810f86195188503b04d64f9d58ea4dfc3a639398 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 22 Mar 2022 11:32:47 -0400
Subject: fixed temporal media merge that had reverted a lot of things.

---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 27 +++++++++-------------
 1 file changed, 11 insertions(+), 16 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 311d52afa..d8cd8d4c5 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -12,13 +12,11 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../../fields/DateField';
 import { AclAdmin, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym, AclAugment } from "../../../../fields/Doc";
-import { documentSchema } from '../../../../fields/documentSchemas';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { RichTextField } from "../../../../fields/RichTextField";
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
-import { makeInterface } from "../../../../fields/Schema";
 import { Cast, DateCast, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../../Utils';
@@ -77,13 +75,10 @@ export interface FormattedTextBoxProps {
 }
 export const GoogleRef = "googleDocId";
 
-type RichTextDocument = makeInterface<[typeof documentSchema]>;
-const RichTextDocument = makeInterface(documentSchema);
-
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
 
 @observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps)>() {
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
     public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
     public static Instance: FormattedTextBox;
@@ -250,7 +245,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             this._editorView.updateState(state);
 
             const tsel = this._editorView.state.selection.$from;
-            tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
+            //tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
             const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
             const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined;               // the actual text in the text box
             const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null);              // the default text inherited from a prototype
@@ -645,7 +640,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 } else if (!this.rootDoc.isTemplateDoc) {
                     const title = StrCast(this.rootDoc.title);
                     this.rootDoc.title = "text";
-                    this.rootDoc.layout = (this.layoutDoc as Doc).layout as string;
+                    this.rootDoc.layout = this.layoutDoc.layout as string;
                     this.rootDoc.title = this.layoutDoc.isTemplateForField as string;
                     this.rootDoc.isTemplateDoc = false;
                     this.rootDoc.isTemplateForField = "";
@@ -1130,13 +1125,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 },
                 dispatchTransaction: this.dispatchTransaction,
                 nodeViews: {
-                    dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); },
-                    dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); },
-                    dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); },
-                    equation(node, view, getPos) { return new EquationView(node, view, getPos, self); },
-                    summary(node, view, getPos) { return new SummaryView(node, view, getPos); },
-                    ordered_list(node, view, getPos) { return new OrderedListView(); },
-                    footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); }
+                    dashComment(node: any, view: any, getPos: any) { return new DashDocCommentView(node, view, getPos); },
+                    dashDoc(node: any, view: any, getPos: any) { return new DashDocView(node, view, getPos, self); },
+                    dashField(node: any, view: any, getPos: any) { return new DashFieldView(node, view, getPos, self); },
+                    equation(node: any, view: any, getPos: any) { return new EquationView(node, view, getPos, self); },
+                    summary(node: any, view: any, getPos: any) { return new SummaryView(node, view, getPos); },
+                    ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); },
+                    footnote(node: any, view: any, getPos: any) { return new FootnoteView(node, view, getPos); }
                 },
                 clipboardTextSerializer: this.clipboardTextSerializer,
                 handlePaste: this.handlePaste,
@@ -1423,7 +1418,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         this.dataDoc[this.fieldKey + "-translation"] = result1 + "\r\n\r\n" + result[0];
                     }), 1000);
                 });
-            } catch (e) { console.log(e.message); }
+            } catch (e: any) { console.log(e.message); }
             this._lastText = curText;
         }
     }
-- 
cgit v1.2.3-70-g09d2


From 24c4443cc097694a12c26b1e5aa1c7a7930625c1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 30 Mar 2022 09:11:21 -0400
Subject: fixed drag document to another tab to keep the other tab active. 
 changed propertiesVew Backlinks to Links To.  Made marker doc text selection
 names start with "#"

---
 src/client/views/DocComponent.tsx                      |  2 +-
 src/client/views/PropertiesView.tsx                    | 18 +++++++++---------
 .../views/nodes/formattedText/FormattedTextBox.tsx     |  2 +-
 3 files changed, 11 insertions(+), 11 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 2e6ea1faa..73a261c40 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -155,7 +155,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                             Doc.AddDocToList(recent, "data", doc, undefined, true, true);
                         }
                     });
-                    this.props.select(false);
+                    this.isAnyChildContentActive() && this.props.select(false);
                     return true;
                 }
             }
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 92b52ed5b..8e1e9ea15 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -73,7 +73,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @observable openFields: boolean = true;
     @observable openLayout: boolean = false;
     @observable openContexts: boolean = true;
-    @observable openBacklinks: boolean = true;
+    @observable openLinks: boolean = true;
     @observable openAppearance: boolean = true;
     @observable openTransform: boolean = true;
     @observable openFilters: boolean = true; // should be false
@@ -282,7 +282,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         return !this.selectedDoc ? (null) : <PropertiesDocContextSelector Document={this.selectedDoc} hideTitle={true} addDocTab={(doc, where) => CollectionDockingView.AddSplit(doc, "right")} />;
     }
 
-    @computed get backlinks() {
+    @computed get links() {
         return !this.selectedDoc ? (null) : <PropertiesDocBacklinksSelector Document={this.selectedDoc} hideTitle={true} addDocTab={(doc, where) => CollectionDockingView.AddSplit(doc, "right")} />;
     }
 
@@ -1086,17 +1086,17 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         </div>;
     }
 
-    @computed get backlinksSubMenu() {
+    @computed get linksSubMenu() {
         return <div className="propertiesView-contexts">
             <div className="propertiesView-contexts-title"
-                onPointerDown={action(() => this.openBacklinks = !this.openBacklinks)}
-                style={{ backgroundColor: this.openBacklinks ? "black" : "" }}>
-                Backlinks
+                onPointerDown={action(() => this.openLinks = !this.openLinks)}
+                style={{ backgroundColor: this.openLinks ? "black" : "" }}>
+                Linked To
                 <div className="propertiesView-contexts-title-icon">
-                    <FontAwesomeIcon icon={this.openBacklinks ? "caret-down" : "caret-right"} size="lg" color="white" />
+                    <FontAwesomeIcon icon={this.openLinks ? "caret-down" : "caret-right"} size="lg" color="white" />
                 </div>
             </div>
-            {this.openBacklinks ? <div className="propertiesView-contexts-content"  >{this.backlinks}</div> : null}
+            {this.openLinks ? <div className="propertiesView-contexts-content"  >{this.links}</div> : null}
         </div>;
     }
 
@@ -1353,7 +1353,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
                     {this.contextsSubMenu}
 
-                    {this.backlinksSubMenu}
+                    {this.linksSubMenu}
 
                     {this.inkSubMenu}
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d8cd8d4c5..c12c4acf0 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -728,7 +728,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
             let tr = state.tr.addMark(sel.from, sel.to, splitter);
             if (sel.from !== sel.to) {
-                const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: this._editorView?.state.doc.textBetween(sel.from, sel.to), unrendered: true });
+                const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: "#" + this._editorView?.state.doc.textBetween(sel.from, sel.to), unrendered: true });
                 const href = targetHref ?? Doc.localServerPath(anchor);
                 if (anchor !== anchorDoc) this.addDocument(anchor);
                 tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
-- 
cgit v1.2.3-70-g09d2


From 85c24ec4dcc39551e8c7de83d3482ec15472c030 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 31 Mar 2022 12:01:37 -0400
Subject: added autoLinks to formattedTextBoxes.  Set auto link target by
 titling documents with a prefix '@'.

---
 src/client/util/CurrentUserUtils.ts                | 11 +++++
 src/client/views/DocumentDecorations.tsx           |  6 +++
 src/client/views/nodes/LinkDocPreview.tsx          |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 48 ++++++++++++++--------
 .../formattedText/FormattedTextBoxComment.tsx      | 10 +++--
 src/client/views/nodes/formattedText/marks_rts.ts  | 27 ++++++++++++
 6 files changed, 83 insertions(+), 21 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 22ab4beb8..e51abf63b 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1141,6 +1141,17 @@ export class CurrentUserUtils {
 
     // Sharing sidebar is where shared documents are contained
     static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
+        if (doc.myPublishedDocs === undefined) {
+            let pubDocs = Docs.newAccount ? undefined : Cast((await doc.myPublishedDocs), Doc, null);
+            if (!pubDocs) {
+                pubDocs = new Doc(linkDatabaseId, true);
+                (pubDocs as Doc).title = "LINK DATABASE: " + Doc.CurrentUserEmail;
+                (pubDocs as Doc).author = Doc.CurrentUserEmail;
+                (pubDocs as Doc).data = new List<Doc>([]);
+                (pubDocs as Doc)["acl-Public"] = SharingPermissions.Augment;
+                doc.myPublishedDocs = new PrefetchProxy(pubDocs);
+            }
+        }
         if (doc.myLinkDatabase === undefined) {
             let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId);
             if (!linkDocs) {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 4c9f15fef..328b8ab88 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -86,7 +86,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                 titleFieldKey === "title" && (d.dataDoc["title-custom"] = !this._accumulatedTitle.startsWith("-"));
                 //@ts-ignore
                 const titleField = (+this._accumulatedTitle === this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle);
+                if (titleFieldKey === "title" && StrCast(d.rootDoc.title).startsWith("@") && !this._accumulatedTitle.startsWith("@")) {
+                    Doc.RemoveDocFromList(Doc.UserDoc(), "myPublishedDocs", SelectionManager.Docs().lastElement());
+                }
                 Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
+                if (titleFieldKey === "title" && this._accumulatedTitle.startsWith("@")) {
+                    Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", SelectionManager.Docs().lastElement());
+                }
 
                 if (d.rootDoc.syncLayoutFieldWithTitle) {
                     const title = titleField.toString();
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 2e29c0656..46736aa4e 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -83,7 +83,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                 const anchorDoc = href.replace(Doc.localServerPath(), "").split("?")[0];
                 anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => {
                     if (anchor instanceof Doc && DocListCast(anchor.links).length) {
-                        this._linkDoc = DocListCast(anchor.links)[0];
+                        this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0];
                         this._linkSrc = anchor;
                         const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
                         this._targetDoc = linkTarget?.type === DocumentType.MARKER && linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index c12c4acf0..ab2d84893 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -63,6 +63,7 @@ import React = require("react");
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { Colors } from '../../global/globalEnums';
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { LinkManager } from '../../../util/LinkManager';
 const translateGoogleApi = require("translate-google-api");
 
 export interface FormattedTextBoxProps {
@@ -346,7 +347,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
     }
 
+    autoLink = () => {
+        if (this._editorView) {
+            const newAutoLinks = new Set<Doc>();
+            const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship = "automatic");
+            const f = this._editorView.state.selection.from;
+            const t = this._editorView.state.selection.to;
+            var tr = this._editorView.state.tr as any;
+            tr = tr.removeMark(0, tr.doc.content.size, this._editorView!.state.schema.marks.autoLinkAnchor);
+            DocListCast(Doc.UserDoc().myPublishedDocs).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks));
+            tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
+            this._editorView?.dispatch(tr);
+            oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink)).forEach(LinkManager.Instance.deleteLink);
+        }
+    }
+
     updateTitle = () => {
+        const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship = "automatic");
         if (!this.props.dontRegisterView &&  // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
             StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.dataDoc["title-custom"] &&
             (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) {
@@ -357,26 +374,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
     }
 
-    // needs a better API for taking in a set of words with target documents instead of just one target
-    hyperlinkTerms = (terms: string[], target: Doc) => {
-        if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
-            const res1 = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
-            let tr = this._editorView.state.tr;
-            const flattened1: TextSelection[] = [];
-            res1.map(r => r.map(h => flattened1.push(h)));
+    // creates links between terms in a document and documents which have a matching Id
+    hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
+        if (this._editorView && (this._editorView as any).docView) {
+            const flattened1 = this.findInNode(this._editorView, this._editorView.state.doc, StrCast(target.title).replace(/^@/, ""));
+            var alink: Doc | undefined;
             flattened1.forEach((flat, i) => {
-                const flattened: TextSelection[] = [];
-                const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
-                res.map(r => r.map(h => flattened.push(h)));
+                const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, StrCast(target.title).replace(/^@/, ""));
                 this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
-                const anchor = Docs.Create.TextanchorDocument();
-                const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!;
-                const allAnchors = [{ href: Doc.localServerPath(anchor), title: "a link", anchorId: anchor[Id] }];
-                const link = this._editorView!.state.schema.marks.linkAnchor.create({ allAnchors, title: "auto link", location });
+                alink = alink ?? (DocListCast(this.Document.links).find(link =>
+                    Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) &&
+                    Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!);
+                newAutoLinks.add(alink);
+                const allAnchors = [{ href: Doc.localServerPath(this.props.Document), title: "a link", anchorId: this.props.Document[Id] }];
+                const link = this._editorView!.state.schema.marks.autoLinkAnchor.create({ allAnchors, linkDoc: alink[Id], title: "auto link", location });
                 tr = tr.addMark(flattened[i].from, flattened[i].to, link);
             });
-            this._editorView.dispatch(tr);
         }
+        return tr;
     }
     highlightSearchTerms = (terms: string[], backward: boolean) => {
         if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
@@ -1256,7 +1271,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0))));
             let target = (e.target as any).parentElement; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>
             while (target && !target.dataset?.targethrefs) target = target.parentElement;
-            FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs);
+            FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc);
         }
         (e.nativeEvent as any).formattedHandled = true;
 
@@ -1399,6 +1414,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     @action
     onBlur = (e: any) => {
+        this.autoLink();
         FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined);
         if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) {
             RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 1fde6e5f0..3e673c0b2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -2,6 +2,7 @@ import { Mark, ResolvedPos } from "prosemirror-model";
 import { EditorState } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { Doc } from "../../../../fields/Doc";
+import { DocServer } from "../../../DocServer";
 import { LinkDocPreview } from "../LinkDocPreview";
 import { FormattedTextBox } from "./FormattedTextBox";
 import './FormattedTextBoxComment.scss';
@@ -9,7 +10,7 @@ import { schema } from "./schema_rts";
 
 export function findOtherUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); }
 export function findUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid); }
-export function findLinkMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.type === schema.marks.linkAnchor); }
+export function findLinkMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.type === schema.marks.autoLinkAnchor || m.type === schema.marks.linkAnchor); }
 export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
     let before = 0, nbef = rpos.nodeBefore;
     while (nbef && finder(nbef.marks)) {
@@ -82,14 +83,14 @@ export class FormattedTextBoxComment {
         FormattedTextBoxComment.tooltip.style.display = "";
     }
 
-    static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = "") {
+    static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = "", linkDoc: string = "") {
         FormattedTextBoxComment.textBox = textBox;
         if ((hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) {
-            FormattedTextBoxComment.setupPreview(view, textBox, hrefs?.trim().split(" ").filter(h => h));
+            FormattedTextBoxComment.setupPreview(view, textBox, hrefs?.trim().split(" ").filter(h => h), linkDoc);
         }
     }
 
-    static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[]) {
+    static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[], linkDoc?: string) {
         const state = view.state;
         // this section checks to see if the insertion point is over text entered by a different user.  If so, it sets ths comment text to indicate the user and the modification date
         if (state.selection.$from) {
@@ -115,6 +116,7 @@ export class FormattedTextBoxComment {
             nbef && naft && LinkDocPreview.SetLinkInfo({
                 docProps: textBox.props,
                 linkSrc: textBox.rootDoc,
+                linkDoc: linkDoc ? DocServer.GetCachedRefField(linkDoc) as Doc : undefined,
                 location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))),
                 hrefs,
                 showHeader: true
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 6103a28d6..52ef06b7f 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -17,6 +17,33 @@ export const marks: { [index: string]: MarkSpec } = {
             return ["div", { className: "dummy" }, 0];
         }
     },
+
+
+    // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each link has an href URL and a title for use in menus and hover (Dash links have linkIDs & targetIDs). `title`
+    // defaults to the empty string. Rendered and parsed as an `<a>`
+    // element.
+    autoLinkAnchor: {
+        attrs: {
+            allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] },
+            linkDoc: { default: "" },
+            location: { default: null },
+            title: { default: null },
+        },
+        inclusive: false,
+        parseDOM: [{
+            tag: "a[href]", getAttrs(dom: any) {
+                return {
+                    location: dom.getAttribute("location"),
+                    title: dom.getAttribute("title")
+                };
+            }
+        }],
+        toDOM(node: any) {
+            const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.href : item.href, "");
+            const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.anchorId : item.anchorId, "");
+            return ["a", { class: anchorids, "data-targethrefs": targethrefs, "data-linkdoc": node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0];
+        }
+    },
     // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each link has an href URL and a title for use in menus and hover (Dash links have linkIDs & targetIDs). `title`
     // defaults to the empty string. Rendered and parsed as an `<a>`
     // element.
-- 
cgit v1.2.3-70-g09d2


From dea5f9c74b62706b17958d95b154005af271cf59 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 31 Mar 2022 12:57:21 -0400
Subject: fixed initialization of myPublishedDocs to be a simple list.

---
 src/client/util/CurrentUserUtils.ts                       | 10 +---------
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  6 +++---
 2 files changed, 4 insertions(+), 12 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index e51abf63b..f2094407d 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1142,15 +1142,7 @@ export class CurrentUserUtils {
     // Sharing sidebar is where shared documents are contained
     static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
         if (doc.myPublishedDocs === undefined) {
-            let pubDocs = Docs.newAccount ? undefined : Cast((await doc.myPublishedDocs), Doc, null);
-            if (!pubDocs) {
-                pubDocs = new Doc(linkDatabaseId, true);
-                (pubDocs as Doc).title = "LINK DATABASE: " + Doc.CurrentUserEmail;
-                (pubDocs as Doc).author = Doc.CurrentUserEmail;
-                (pubDocs as Doc).data = new List<Doc>([]);
-                (pubDocs as Doc)["acl-Public"] = SharingPermissions.Augment;
-                doc.myPublishedDocs = new PrefetchProxy(pubDocs);
-            }
+            doc.myPublishedDocs = new List<Doc>();
         }
         if (doc.myLinkDatabase === undefined) {
             let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ab2d84893..6c4ca2d90 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -350,11 +350,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     autoLink = () => {
         if (this._editorView) {
             const newAutoLinks = new Set<Doc>();
-            const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship = "automatic");
+            const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === "automatic");
             const f = this._editorView.state.selection.from;
             const t = this._editorView.state.selection.to;
             var tr = this._editorView.state.tr as any;
-            tr = tr.removeMark(0, tr.doc.content.size, this._editorView!.state.schema.marks.autoLinkAnchor);
+            const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor
+            tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
             DocListCast(Doc.UserDoc().myPublishedDocs).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks));
             tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
             this._editorView?.dispatch(tr);
@@ -363,7 +364,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     updateTitle = () => {
-        const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship = "automatic");
         if (!this.props.dontRegisterView &&  // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
             StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.dataDoc["title-custom"] &&
             (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) {
-- 
cgit v1.2.3-70-g09d2


From 1ca9e485ea99db7db35fe52b0e15d6defa7568d3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 4 Apr 2022 11:39:17 -0400
Subject: cleaned up warnings.  fixed ink to work better with it's label boxes
 by not letting textbox ComponentView override inkinstroke.  made ink text
 boxes go away unless they have text or are selected.   fixed up brushing code
 and made it work for ink and ink w/ text labels

---
 src/client/views/InkingStroke.tsx                  | 23 ++++++++++++++-----
 .../collections/CollectionStackedTimeline.tsx      |  6 ++---
 .../collectionLinear/CollectionLinearView.tsx      |  2 +-
 src/client/views/nodes/AudioBox.tsx                | 20 ++++++++---------
 src/client/views/nodes/ComparisonBox.tsx           |  2 +-
 src/client/views/nodes/LinkDocPreview.tsx          |  1 +
 src/client/views/nodes/VideoBox.tsx                | 26 +++++++++++-----------
 src/client/views/nodes/button/FontIconBox.scss     | 15 +++++++++++++
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 src/fields/Doc.ts                                  | 12 +++++++---
 src/fields/Types.ts                                |  5 +++++
 11 files changed, 76 insertions(+), 38 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 23bdf8406..06671961d 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -25,16 +25,16 @@ import { action, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, WidthSym } from "../../fields/Doc";
 import { InkData, InkField, InkTool } from "../../fields/InkField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
+import { BoolCast, Cast, NumCast, RTFCast, StrCast } from "../../fields/Types";
 import { TraceMobx } from "../../fields/util";
-import { OmitKeys, returnFalse, setupMoveUpEvents } from "../../Utils";
+import { emptyFunction, OmitKeys, returnFalse, setupMoveUpEvents } from "../../Utils";
 import { CognitiveServices } from "../cognitive_services/CognitiveServices";
 import { InteractionUtils } from "../util/InteractionUtils";
 import { SnappingManager } from "../util/SnappingManager";
 import { Transform } from "../util/Transform";
 import { UndoManager } from "../util/UndoManager";
 import { ContextMenu } from "./ContextMenu";
-import { ViewBoxBaseComponent } from "./DocComponent";
+import { DocComponent, ViewBoxBaseComponent } from "./DocComponent";
 import { Colors } from "./global/globalEnums";
 import { InkControlPtHandles, InkEndPtHandles } from "./InkControlPtHandles";
 import "./InkStroke.scss";
@@ -43,6 +43,7 @@ import { InkTangentHandles } from "./InkTangentHandles";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
 import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox";
 import Color = require("color");
+import { DocComponentView } from "./nodes/DocumentView";
 
 @observer
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -69,8 +70,16 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
 
     // transform is the inherited screentolocal xf plus any scaling that was done to make the stroke
     // fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc)
-    screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1)
+    screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1);
 
+    getAnchor = () => {
+        console.log(document.activeElement);
+        return this._subContentView?.getAnchor?.() || this.rootDoc;
+    }
+
+    scrollFocus = (textAnchor: Doc, smooth: boolean) => {
+        return this._subContentView?.scrollFocus?.(textAnchor, smooth);
+    }
     /**
     * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space); 
     * DocumentDecorations calls getBounds() on DocumentViews which call getCenter() if defined - in the case of ink it needs to be defined since
@@ -296,6 +305,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                 </div>;
     }
 
+    _subContentView: DocComponentView | undefined;
+    setSubContentView = (doc: DocComponentView) => this._subContentView = doc;
     render() {
         TraceMobx();
         const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData();
@@ -323,7 +334,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
             StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
             markerScale, undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents ?? (this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted"), 0.0,
             false, downHdlr);
-
         return <div className="inkStroke-wrapper">
             <svg className="inkStroke"
                 style={{
@@ -344,7 +354,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                 {clickableLine(this.onPointerDown)}
                 {inkLine}
             </svg>
-            {!closed ? (null) :
+            {!closed || (!RTFCast(this.rootDoc.text)?.Text && !this.props.isSelected()) ? (null) :
                 <div className="inkStroke-text" style={{
                     color: StrCast(this.layoutDoc.textColor, "black"),
                     pointerEvents: this.props.isDocumentActive?.() ? "all" : undefined,
@@ -352,6 +362,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                 }}>
                     <FormattedTextBox
                         {...OmitKeys(this.props, ['children']).omit}
+                        setContentView={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
                         yPadding={10}
                         xPadding={10}
                         fieldKey={"text"}
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index e0b947211..de307416f 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -89,7 +89,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     @observable _scroll: number = 0;
 
     // ensures that clip doesn't get trimmed so small that controls cannot be adjusted anymore 
-    get minTrimLength() { return Math.max(this._timeline?.getBoundingClientRect() ? 0.05 * this.clipDuration : 0, 0.5) }
+    get minTrimLength() { return Math.max(this._timeline?.getBoundingClientRect() ? 0.05 * this.clipDuration : 0, 0.5); }
 
     @computed get trimStart() { return this.IsTrimming !== TrimScope.None ? this._trimStart : this.clipStart; }
     @computed get trimDuration() { return this.trimEnd - this.trimStart; }
@@ -101,7 +101,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 
     @computed get currentTime() { return NumCast(this.layoutDoc._currentTimecode); }
 
-    @computed get zoomFactor() { return this._zoomFactor }
+    @computed get zoomFactor() { return this._zoomFactor; }
 
 
     constructor(props: any) {
@@ -548,7 +548,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     dictationHeight = () => (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100;
 
     @computed get timelineContentHeight() { return this.props.PanelHeight() * this.dictationHeightPercent / 100; }
-    @computed get timelineContentWidth() { return this.props.PanelWidth() * this.zoomFactor - 4 };  // subtract size of container border
+    @computed get timelineContentWidth() { return this.props.PanelWidth() * this.zoomFactor - 4; }  // subtract size of container border
 
     dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight);
 
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 1d142d595..e3d4f86c1 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -232,7 +232,7 @@ export class CollectionLinearView extends CollectionSubView() {
                             Currently playing:
                             {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) =>
                                 <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, true)}>
-                                    {clip.title + (i == CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? "" : ",")}
+                                    {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? "" : ",")}
                                 </span>)}
                         </span>
                     </span>}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 968dbc54d..e28e5b453 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -81,7 +81,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     // mehek: not 100% sure but i think due to the order in which things are loaded this is necessary ^^
     // if you get rid of it and set the value to 0 the timeline and waveform will set their bounds incorrectly
 
-    @computed get miniPlayer() { return this.props.PanelHeight() < 50 } // used to collapse timeline when node is shrunk
+    @computed get miniPlayer() { return this.props.PanelHeight() < 50; } // used to collapse timeline when node is shrunk
     @computed get links() { return DocListCast(this.dataDoc.links); }
     @computed get pauseTime() { return this._pauseEnd - this._pauseStart; } // total time paused to update the correct recording time
     @computed get mediaState() { return this.layoutDoc.mediaState as media_state; }
@@ -212,7 +212,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         if (!CollectionStackedTimeline.CurrentlyPlaying) {
             CollectionStackedTimeline.CurrentlyPlaying = [];
         }
-        if (CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc) == -1) {
+        if (CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc) === -1) {
             CollectionStackedTimeline.CurrentlyPlaying.push(this.layoutDoc);
         }
     }
@@ -364,7 +364,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     // sets <audio> ref for updating time
     setRef = (e: HTMLAudioElement | null) => {
         e?.addEventListener("timeupdate", this.timecodeChanged);
-        e?.addEventListener("ended", () => { this._finished = true; this.Pause() });
+        e?.addEventListener("ended", () => { this._finished = true; this.Pause(); });
         this._ele = e;
     }
 
@@ -417,10 +417,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     @action
     timelineWhenChildContentsActiveChanged = (isActive: boolean) =>
-        this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive);
+        this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive)
 
     timelineScreenToLocal = () =>
-        this.props.ScreenToLocalTransform().translate(0, -AudioBox.bottomControlsHeight);
+        this.props.ScreenToLocalTransform().translate(0, -AudioBox.bottomControlsHeight)
 
     setPlayheadTime = (time: number) => this._ele!.currentTime = this.layoutDoc._currentTimecode = time;
 
@@ -430,7 +430,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     // timeline dimensions
     timelineWidth = () => this.props.PanelWidth();
-    timelineHeight = () => (this.props.PanelHeight() - (AudioBox.topControlsHeight + AudioBox.bottomControlsHeight))
+    timelineHeight = () => (this.props.PanelHeight() - (AudioBox.topControlsHeight + AudioBox.bottomControlsHeight));
 
     // ends trim, hides trim controls and displays new clip
     @undoBatch
@@ -493,7 +493,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             this._dropDisposer = DragManager.MakeDropTarget(r,
                 (e, de) => {
                     const [xp, yp] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
-                    de.complete.docDragData && this.timeline!.internalDocDrop(e, de, de.complete.docDragData, xp);
+                    de.complete.docDragData && this.timeline.internalDocDrop(e, de, de.complete.docDragData, xp);
                 },
                 this.layoutDoc, undefined);
         }
@@ -529,7 +529,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     <FontAwesomeIcon icon="microphone" />
                     RECORD
                 </div>}
-        </div>
+        </div>;
     }
 
     // UI for playback, displayed for imported or recorded clips, hides timeline and collapses controls when node is shrunk vertically
@@ -563,7 +563,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     <input type="range" step="0.1" min="0" max="1" value={this._muted ? 0 : this._volume}
                         className="toolbar-slider volume"
                         onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); }}
-                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => { this.setVolume(Number(e.target.value)) }}
+                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.setVolume(Number(e.target.value))}
                     />
                 </div>
             </div>
@@ -596,7 +596,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             </div>
 
 
-        </div>
+        </div>;
     }
 
     // gets CollectionStackedTimeline
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index cbc61ffdb..78608b681 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -81,7 +81,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
             </div>;
         };
         const displayDoc = (which: string) => {
-            var whichDoc = Cast(this.dataDoc[which], Doc, null);
+            const whichDoc = Cast(this.dataDoc[which], Doc, null);
             // if (whichDoc?.type === DocumentType.MARKER) whichDoc = Cast(whichDoc.annotationOn, Doc, null);
             const targetDoc = Cast(whichDoc?.annotationOn, Doc, null) ?? whichDoc;
             return whichDoc ? <>
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 46736aa4e..7637d39be 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -182,6 +182,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                             PanelHeight={this.height}
                             focus={DocUtils.DefaultFocus}
                             whenChildContentsActiveChanged={returnFalse}
+                            ignoreAutoHeight={true} // need to ignore autoHeight otherwise autoHeight text boxes will expand beyond the preview panel size.
                             bringToFront={returnFalse}
                             NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined}
                             NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 02ef346fb..ca8dc8515 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -202,7 +202,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     // toggles video full screen
     @action public FullScreen = () => {
-        if (document.fullscreenElement == this._contentRef) {
+        if (document.fullscreenElement === this._contentRef) {
             this._fullScreen = false;
             this.player && this._contentRef && document.exitFullscreen();
         }
@@ -299,7 +299,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         if (Number.isFinite(this.player!.duration)) {
             this.rawDuration = this.player!.duration;
         }
-    })
+    });
 
 
     // updates video time
@@ -333,7 +333,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     setContentRef = (cref: HTMLDivElement | null) => {
         this._contentRef = cref;
         if (cref) {
-            cref.onfullscreenchange = action((e) => this._fullScreen = (document.fullscreenElement == cref));
+            cref.onfullscreenchange = action((e) => this._fullScreen = (document.fullscreenElement === cref));
         }
     }
 
@@ -496,7 +496,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         if (!CollectionStackedTimeline.CurrentlyPlaying) {
             CollectionStackedTimeline.CurrentlyPlaying = [];
         }
-        if (CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc) == -1) {
+        if (CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc) === -1) {
             CollectionStackedTimeline.CurrentlyPlaying.push(this.layoutDoc);
         }
     }
@@ -613,10 +613,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     // stretches vertically or horizontally depending on video orientation so video fits full screen
     fullScreenSize() {
         if (this._videoRef && this._videoRef.videoHeight / this._videoRef.videoWidth > 1) {
-            return { height: "100%" }
+            return { height: "100%" };
         }
         else {
-            return { width: "100%" }
+            return { width: "100%" };
         }
     }
 
@@ -687,7 +687,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     // renders video controls
     @computed get uIButtons() {
         const curTime = NumCast(this.layoutDoc._currentTimecode) - (this.timeline?.clipStart || 0);
-        return <div className="videoBox-ui" style={this._fullScreen || this.heightPercent == 100 ? { fontSize: "40px", minWidth: "80%" } : {}}>
+        return <div className="videoBox-ui" style={this._fullScreen || this.heightPercent === 100 ? { fontSize: "40px", minWidth: "80%" } : {}}>
             <div className="videobox-button"
                 title={this._playing ? "play" : "pause"}
                 onPointerDown={this.onPlayDown}>
@@ -699,12 +699,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     {formatTime(curTime)}
                 </div>
 
-                {this._fullScreen || this.heightPercent == 100 ?
+                {this._fullScreen || this.heightPercent === 100 ?
                     <div className="timeline-slider">
                         <input type="range" step="0.1" min={this.timeline.clipStart} max={this.timeline.clipEnd} value={curTime}
                             className="toolbar-slider time-progress"
                             onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); }}
-                            onChange={(e: React.ChangeEvent<HTMLInputElement>) => { this.setPlayheadTime(Number(e.target.value)) }}
+                            onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.setPlayheadTime(Number(e.target.value))}
                         />
                     </div>
                     :
@@ -740,11 +740,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             </div>
             <input type="range" step="0.1" min="0" max="1" value={this._muted ? 0 : this._volume}
                 className="toolbar-slider volume"
-                onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); }}
-                onChange={(e: React.ChangeEvent<HTMLInputElement>) => { this.setVolume(Number(e.target.value)) }}
+                onPointerDown={(e: React.PointerEvent) => e.stopPropagation()}
+                onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.setVolume(Number(e.target.value))}
             />
 
-            {!this._fullScreen && this.heightPercent != 100 &&
+            {!this._fullScreen && this.heightPercent !== 100 &&
                 <>
                     <div className="videobox-button" title="zoom">
                         <FontAwesomeIcon icon="search-plus" />
@@ -755,7 +755,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         onChange={(e: React.ChangeEvent<HTMLInputElement>) => { this.zoom(Number(e.target.value)); }}
                     />
                 </>}
-        </div>
+        </div>;
     }
 
     // renders CollectionStackedTimeline
diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss
index 079c767b9..e3e2be515 100644
--- a/src/client/views/nodes/button/FontIconBox.scss
+++ b/src/client/views/nodes/button/FontIconBox.scss
@@ -278,6 +278,21 @@
                 background-color: #e3e3e3;
                 box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
                 border-radius: $standard-border-radius;
+                input[type=range]::-webkit-slider-runnable-track {
+                    background: gray;
+                    height: 3px;
+                }
+                input[type=range]::-webkit-slider-thumb {
+                    box-shadow: 1px 1px 1px #000000;
+                    border: 1px solid #000000;
+                    height: 10px;
+                    width: 10px;
+                    border-radius: 5px;
+                    background: #FFFFFF;
+                    cursor: pointer;
+                    -webkit-appearance: none;
+                    margin-top: -4px;
+                }
             }
         }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 6c4ca2d90..75f0978a8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -354,7 +354,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const f = this._editorView.state.selection.from;
             const t = this._editorView.state.selection.to;
             var tr = this._editorView.state.tr as any;
-            const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor
+            const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor;
             tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
             DocListCast(Doc.UserDoc().myPublishedDocs).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks));
             tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index c932259de..1edab16be 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -935,7 +935,7 @@ export namespace Doc {
     }
 
     export function isBrushedHighlightedDegree(doc: Doc) {
-        return Doc.IsHighlighted(doc) ? 6 : Doc.IsBrushedDegree(doc);
+        return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.IsBrushedDegree(doc);
     }
 
     export class DocBrush {
@@ -1009,10 +1009,16 @@ export namespace Doc {
     const isBrushedCache = computedFn(function IsBrushed(doc: Doc) { return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc)); });
     export function IsBrushed(doc: Doc) { return isBrushedCache(doc); }
 
+    export enum DocBrushStatus {
+        unbrushed = 0,
+        protoBrushed = 1,
+        selfBrushed = 2,
+        highlighted = 3
+    }
     // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
     export function IsBrushedDegreeUnmemoized(doc: Doc) {
-        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0;
-        return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0;
+        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return DocBrushStatus.unbrushed;
+        return brushManager.BrushedDoc.has(doc) ? DocBrushStatus.selfBrushed : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
     }
     export function IsBrushedDegree(doc: Doc) {
         return computedFn(function IsBrushDegree(doc: Doc) {
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index c90f3b6b3..7e2aa5681 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -4,6 +4,8 @@ import { RefField } from "./RefField";
 import { DateField } from "./DateField";
 import { ScriptField } from "./ScriptField";
 import { URLField, WebField, ImageField } from "./URLField";
+import { TextField } from "@material-ui/core";
+import { RichTextField } from "./RichTextField";
 
 export type ToType<T extends InterfaceValue> =
     T extends "string" ? string :
@@ -88,6 +90,9 @@ export function BoolCast(field: FieldResult, defaultVal: boolean | null = false)
 export function DateCast(field: FieldResult) {
     return Cast(field, DateField, null);
 }
+export function RTFCast(field: FieldResult) {
+    return Cast(field, RichTextField, null);
+}
 
 export function ScriptCast(field: FieldResult, defaultVal: ScriptField | null = null) {
     return Cast(field, ScriptField, defaultVal);
-- 
cgit v1.2.3-70-g09d2


From ab7948689e384af8779e581708df6fa4225a85a3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 4 Apr 2022 18:35:10 -0400
Subject: fixed autolink. fixed the pileup view to animate properly and
 activate without selection.  added a 'dataTransition" prop to DocumentView to
 allow collections to animate changes in the size of documents.

---
 src/client/documents/Documents.ts                  | 18 ++++++++-------
 src/client/util/CurrentUserUtils.ts                | 12 +++++-----
 .../views/collections/CollectionPileView.tsx       | 27 ++++++++++++----------
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 +
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 19 +++++++++++----
 .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +++++++---
 src/fields/Doc.ts                                  |  1 -
 9 files changed, 58 insertions(+), 36 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index fdfd7bd31..bb60586eb 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -823,7 +823,7 @@ export namespace Docs {
         }
 
         export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: "visible", _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id);
         }
 
         export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
@@ -1344,7 +1344,7 @@ export namespace DocUtils {
         if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", "");
     }
 
-    export function pileup(docList: Doc[], x?: number, y?: number) {
+    export function pileup(docList: Doc[], x?: number, y?: number, create: boolean = true) {
         let w = 0, h = 0;
         runInAction(() => {
             docList.forEach(d => {
@@ -1359,12 +1359,14 @@ export namespace DocUtils {
                 d._timecodeToShow = undefined;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
             });
         });
-        const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - 55, y: (y || 0) - 55, _width: 110, _height: 100, _overflow: "visible" });
-        newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
-        newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
-        newCollection._width = newCollection._height = 110;
-        newCollection._jitterRotation = 10;
-        return newCollection;
+        if (create) {
+            const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - 55, y: (y || 0) - 55, _width: 110, _height: 100, });
+            newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
+            newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
+            newCollection._width = newCollection._height = 110;
+            newCollection._jitterRotation = 10;
+            return newCollection;
+        }
     }
 
     export function LeavePushpin(doc: Doc, annotationField: string) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index f2094407d..af731ce9f 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -292,10 +292,10 @@ export class CurrentUserUtils {
         if (doc["template-icon-view"] === undefined) {
             const iconView = Docs.Create.LabelDocument({
                 title: "icon", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimgray",
-                _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true
+                _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true
             });
             //  Docs.Create.TextDocument("", {
-            //     title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
+            //     title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" })
             // });
             // Doc.GetProto(iconView).icon = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', "");
             iconView.isTemplateDoc = makeTemplate(iconView);
@@ -304,7 +304,7 @@ export class CurrentUserUtils {
         if (doc["template-icon-view-rtf"] === undefined) {
             const iconRtfView = Docs.Create.LabelDocument({
                 title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"),
-                _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true
+                _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true
             });
             iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
             doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView);
@@ -312,20 +312,20 @@ export class CurrentUserUtils {
         if (doc["template-icon-view-button"] === undefined) {
             const iconBtnView = Docs.Create.FontIconDocument({
                 title: "icon_" + DocumentType.BUTTON, _nativeHeight: 30, _nativeWidth: 30,
-                _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true
+                _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true
             });
             iconBtnView.isTemplateDoc = makeTemplate(iconBtnView, true, "icon_" + DocumentType.BUTTON);
             doc["template-icon-view-button"] = new PrefetchProxy(iconBtnView);
         }
         if (doc["template-icon-view-img"] === undefined) {
             const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", {
-                title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true
+                title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true
             });
             iconImageView.isTemplateDoc = makeTemplate(iconImageView, true, "icon_" + DocumentType.IMG);
             doc["template-icon-view-img"] = new PrefetchProxy(iconImageView);
         }
         if (doc["template-icon-view-col"] === undefined) {
-            const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true });
+            const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true });
             iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL);
             doc["template-icon-view-col"] = new PrefetchProxy(iconColView);
         }
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 0a336c544..0ca0a463e 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -35,6 +35,18 @@ export class CollectionPileView extends CollectionSubView() {
 
     layoutEngine = () => StrCast(this.Document._pileLayoutEngine);
 
+    @undoBatch
+    addPileDoc = (doc: Doc | Doc[]) => {
+        (doc instanceof Doc ? [doc] : doc).map((d) => DocUtils.iconify(d));
+        return this.props.addDocument?.(doc) || false;
+    }
+
+    @undoBatch
+    removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
+        (doc instanceof Doc ? [doc] : doc).map(undoBatch((d) => Doc.deiconifyView(d)));
+        return this.props.moveDocument?.(doc, targetCollection, addDoc) || false;
+    }
+
     // returns the contents of the pileup in a CollectionFreeFormView
     @computed get contents() {
         const isStarburst = this.layoutEngine() === "starburst";
@@ -47,14 +59,8 @@ export class CollectionPileView extends CollectionSubView() {
             <CollectionFreeFormView {...this.props}
                 layoutEngine={this.layoutEngine}
                 childDocumentsActive={isStarburst ? returnTrue : undefined}
-                addDocument={undoBatch((doc: Doc | Doc[]) => {
-                    (doc instanceof Doc ? [doc] : doc).map((d) => DocUtils.iconify(d));
-                    return this.props.addDocument?.(doc) || false;
-                })}
-                moveDocument={undoBatch((doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
-                    (doc instanceof Doc ? [doc] : doc).map(undoBatch((d) => Doc.deiconifyView(d)));
-                    return this.props.moveDocument?.(doc, targetCollection, addDoc) || false;
-                })} />
+                addDocument={this.addPileDoc}
+                moveDocument={this.removePileDoc} />
         </div>;
     }
 
@@ -62,19 +68,16 @@ export class CollectionPileView extends CollectionSubView() {
     toggleStarburst = action(() => {
         if (this.layoutEngine() === 'starburst') {
             const defaultSize = 110;
-            this.layoutDoc._overflow = undefined;
-            this.childDocs.forEach(d => DocUtils.iconify(d));
             this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2;
             this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
             this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize);
             this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize);
-            DocUtils.pileup(this.childDocs);
+            DocUtils.pileup(this.childDocs, undefined, undefined, false);
             this.layoutDoc._panX = 0;
             this.layoutDoc._panY = -10;
             this.props.Document._pileLayoutEngine = 'pass';
         } else {
             const defaultSize = 25;
-            this.layoutDoc._overflow = 'visible';
             !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500);
             !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
             if (this.layoutEngine() === 'pass') {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 42e157396..d8f1287cd 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -453,7 +453,7 @@ export function CollectionSubView<X>(moreProps?: X) {
                 if (completed) completed(set);
                 else {
                     if (isFreeformView && generatedDocuments.length > 1) {
-                        addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!));
+                        addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!);
                     } else {
                         generatedDocuments.forEach(addDocument);
                     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index e2ea81392..b2697cf08 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1152,6 +1152,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             renderDepth={this.props.renderDepth + 1}
             replica={entry.replica}
             renderIndex={renderIndex}
+            dataTransition={entry.transition}
             renderCutoffProvider={this.renderCutoffProvider}
             ContainingCollectionView={this.props.CollectionView}
             ContainingCollectionDoc={this.props.Document}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b10b0912f..b8f687489 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -374,7 +374,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const selected = this.marqueeSelect(false);
         SelectionManager.DeselectAll();
         selected.forEach(d => this.props.removeDocument?.(d));
-        const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2);
+        const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2)!;
         this.props.addDocument?.(newCollection);
         this.props.selectDocuments([newCollection]);
         MarqueeOptionsMenu.Instance.fadeOut(true);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 979e86738..a9abf066e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -116,6 +116,7 @@ export interface DocumentViewSharedProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     docViewPath: () => DocumentView[];
+    dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt
     layerProvider: undefined | ((doc: Doc, assign?: boolean) => boolean);
     styleProvider: Opt<StyleProviderFunc>;
     focus: DocFocusFunc;
@@ -182,6 +183,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps {
 @observer
 export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps>() {
     public static SelectAfterContextMenu = true; //  whether a document should be selected after it's contextmenu is triggered.
+    _animateScaleTime = 300; // milliseconds;
     @observable _animateScalingTo = 0;
     @observable _mediaState = 0;
     @observable _pendingDoubleClick = false;
@@ -1078,9 +1080,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     color: StrCast(this.layoutDoc.color, "inherit"),
                     fontFamily: StrCast(this.Document._fontFamily, "inherit"),
                     fontSize: Cast(this.Document._fontSize, "string", null),
-                    transformOrigin: this._animateScalingTo ? "center center" : undefined,
                     transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
-                    transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform 0.5s ease-${this._animateScalingTo < 1 ? "in" : "out"}`,
+                    transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this._animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? "in" : "out"}`,
                 }}>
 
                 {this.innards}
@@ -1247,8 +1248,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         setTimeout(action(() => {
             this.setCustomView(custom, view);
             this.docView && (this.docView._animateScalingTo = 1); // expand it
-            setTimeout(action(() => this.docView && (this.docView._animateScalingTo = 0)), 400);
-        }), 400);
+            setTimeout(action(() => this.docView && (this.docView._animateScalingTo = 0)), this.docView!._animateScaleTime - 10);
+        }), this.docView!._animateScaleTime - 10);
     });
 
     startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource);
@@ -1293,6 +1294,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             {!this.props.Document || !this.props.PanelWidth() ? (null) : (
                 <div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef}
                     style={{
+                        transition: this.props.dataTransition,
                         position: this.props.Document.isInkMask ? "absolute" : undefined,
                         transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
                         width: isButton ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
@@ -1318,6 +1320,15 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
 }
 
+export function deiconifyViewFunc(documentView: DocumentView) {
+    documentView.iconify();
+    //StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc);
+}
+ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
+    documentView.iconify();
+}
+);
+
 ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
     if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout");
     else dv.switchViews(true, detailLayoutKeySuffix);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 75f0978a8..1f7e557d9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -359,18 +359,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             DocListCast(Doc.UserDoc().myPublishedDocs).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks));
             tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
             this._editorView?.dispatch(tr);
-            oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink)).forEach(LinkManager.Instance.deleteLink);
+            oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink);
         }
     }
 
     updateTitle = () => {
+        const title = StrCast(this.dataDoc.title)
         if (!this.props.dontRegisterView &&  // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
-            StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.dataDoc["title-custom"] &&
+            (title.startsWith("-") || title.startsWith("@")) && this._editorView && !this.dataDoc["title-custom"] &&
             (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) {
             let node = this._editorView.state.doc;
             while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild;
             const str = node.textContent;
-            this.dataDoc.title = "-" + str.substr(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : "");
+            const prefix = str.startsWith("@") ? "" : "-";
+            this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : "");
+            if (str.startsWith("@") && str.length > 1) {
+                Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", this.rootDoc);
+            }
         }
     }
 
@@ -909,6 +914,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 }
                 if (this._editorView && selected) {
                     RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
+                    this.autoLink();
                 }
             }), { fireImmediately: true });
 
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 1edab16be..ebdbae344 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1414,7 +1414,6 @@ ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field);
 ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); });
 ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); });
 ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); });
-ScriptingGlobals.add(function deiconifyView(doc: any) { Doc.deiconifyView(doc); });
 ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); });
 ScriptingGlobals.add(function redo() { SelectionManager.DeselectAll(); return UndoManager.Redo(); });
 ScriptingGlobals.add(function DOC(id: string) { console.log("Can't parse a document id in a script"); return "invalid"; });
-- 
cgit v1.2.3-70-g09d2


From 27d37f7c50978da6f3eca87e1aa5bfea786b19c6 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 6 Apr 2022 11:04:53 -0400
Subject: fixed displaying titles on text to not shrink box to 0 height.  fixed
 autoLink to not self link.

---
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 3 ++-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 6f0f0074a..5149d370f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -46,6 +46,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : NumCast(this.Document.zIndex); }
     get Opacity() { return this.dataProvider ? this.dataProvider.opacity : undefined; }
     get Highlight() { return this.dataProvider?.highlight; }
+    @computed get ShowTitle() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as (Opt<string>); }
     @computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); }
     @computed get sizeProvider() { return this.props.sizeProvider?.(this.props.Document, this.props.replica); }
 
@@ -171,7 +172,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             style={{
                 outline: this.Highlight ? "orange solid 2px" : "",
                 width: this.panelWidth(),
-                height: this.layoutDoc.autoHeight && this.rootDoc.type === DocumentType.RTF ? undefined : this.panelHeight(),
+                height: !this.ShowTitle && this.layoutDoc.autoHeight && this.rootDoc.type === DocumentType.RTF ? undefined : this.panelHeight(),
                 transform: this.transform,
                 transition: this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition),
                 zIndex: this.ZInd,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1f7e557d9..dcb312980 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -381,7 +381,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     // creates links between terms in a document and documents which have a matching Id
     hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
-        if (this._editorView && (this._editorView as any).docView) {
+        if (this._editorView && (this._editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) {
             const flattened1 = this.findInNode(this._editorView, this._editorView.state.doc, StrCast(target.title).replace(/^@/, ""));
             var alink: Doc | undefined;
             flattened1.forEach((flat, i) => {
-- 
cgit v1.2.3-70-g09d2


From aeb636371275ed056d7812447984f71ff90b39e0 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 6 Apr 2022 11:44:02 -0400
Subject: set publsihed documents to automatically display a title region

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 8 ++++++++
 1 file changed, 8 insertions(+)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index dcb312980..18be30a88 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1443,6 +1443,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             } catch (e: any) { console.log(e.message); }
             this._lastText = curText;
         }
+        if (StrCast(this.rootDoc.title).startsWith("@") && !this.dataDoc["title-custom"]) {
+            UndoManager.RunInBatch(() => {
+                this.dataDoc["title-custom"] = true;
+                this.dataDoc.showTitle = "title";
+                const tr = this._editorView!.state.tr;
+                this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.rootDoc.title).length + 2))).deleteSelection());
+            }, "titler");
+        }
     }
     onKeyDown = (e: React.KeyboardEvent) => {
         // single line text boxes need to pass through tab/enter/backspace so that their containers can respond (eg, an outline container)
-- 
cgit v1.2.3-70-g09d2


From f05bc3ee02198289dbfbc1b9c8db0b9e0fffd27c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 7 Apr 2022 12:02:04 -0400
Subject: added multiple target capability for autoLinks.   added wiki:<link>

---
 src/client/views/nodes/LinkDocPreview.tsx          | 22 ++++++++++++++++------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 20 +++++++++++++++-----
 .../views/nodes/formattedText/RichTextRules.ts     | 19 +++++++++++++++++++
 src/client/views/nodes/formattedText/marks_rts.ts  | 13 +++++++++----
 4 files changed, 59 insertions(+), 15 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index f5f773595..d5f3f3b4e 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -84,9 +84,16 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                 anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => {
                     if (anchor instanceof Doc && DocListCast(anchor.links).length) {
                         this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0];
-                        this._linkSrc = anchor;
-                        const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
-                        this._targetDoc = linkTarget?.type === DocumentType.MARKER && linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
+                        const automaticLink = this._linkDoc.linkRelationship === "automatic";
+                        if (automaticLink) { // automatic links specify the target in the link info, not the source
+                            const linkTarget = anchor;
+                            this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget);
+                            this._targetDoc = linkTarget;
+                        } else {
+                            this._linkSrc = anchor;
+                            const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
+                            this._targetDoc = linkTarget?.type === DocumentType.MARKER && linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
+                        }
                         this._toolTipText = "";
                     }
                 }));
@@ -99,7 +106,10 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
         setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(() => this._linkDoc && LinkManager.Instance.deleteLink(this._linkDoc)));
     }
     nextHref = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1)));
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => {
+            this._linkDoc = undefined;
+            this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1);
+        }));
     }
 
     followLink = (e: React.PointerEvent) => {
@@ -152,10 +162,10 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
         return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? (null) :
             <div className="linkDocPreview-inner">
                 {!this.props.showHeader ? (null) : this.previewHeader}
-                <div className="linkDocPreview-preview-wrapper">
+                <div className="linkDocPreview-preview-wrapper" style={{ maxHeight: this._toolTipText ? "100%" : undefined, overflow: "auto" }}>
                     {this._toolTipText ? this._toolTipText :
                         <DocumentView ref={(r) => {
-                            const targetanchor = LinkManager.getOppositeAnchor(this._linkDoc!, this._linkSrc!);
+                            const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
                             targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor);
                         }}
                             Document={this._targetDoc!}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 18be30a88..ec88b8d1d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -381,8 +381,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     // creates links between terms in a document and documents which have a matching Id
     hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
-        if (this._editorView && (this._editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) {
-            const flattened1 = this.findInNode(this._editorView, this._editorView.state.doc, StrCast(target.title).replace(/^@/, ""));
+        const editorView = this._editorView;
+        if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) {
+            const flattened1 = this.findInNode(editorView, editorView.state.doc, StrCast(target.title).replace(/^@/, ""));
             var alink: Doc | undefined;
             flattened1.forEach((flat, i) => {
                 const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, StrCast(target.title).replace(/^@/, ""));
@@ -391,9 +392,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) &&
                     Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!);
                 newAutoLinks.add(alink);
-                const allAnchors = [{ href: Doc.localServerPath(this.props.Document), title: "a link", anchorId: this.props.Document[Id] }];
-                const link = this._editorView!.state.schema.marks.autoLinkAnchor.create({ allAnchors, linkDoc: alink[Id], title: "auto link", location });
-                tr = tr.addMark(flattened[i].from, flattened[i].to, link);
+                const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
+                const sel = flattened[i];
+                tr = tr.addMark(sel.from, sel.to, splitter);
+                tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
+                    if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
+                        const allAnchors = [{ href: Doc.localServerPath(target), title: "a link", anchorId: this.props.Document[Id] }];
+                        allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
+                        const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: "auto term", location: "add:right" });
+                        tr = tr.addMark(pos, pos + node.nodeSize, link);
+                    }
+                });
+                tr = tr.removeMark(sel.from, sel.to, splitter);
             });
         }
         return tr;
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index bafae84dc..00c03875b 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -294,6 +294,25 @@ export class RichTextRules {
                     return state.tr.deleteRange(start, end).insert(start, fieldView);
                 }),
 
+
+            // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document 
+            // wiki:title
+            new InputRule(
+                new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/),
+                (state, match, start, end) => {
+                    const title = match[1];
+                    this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))));
+
+                    this.TextBox.makeLinkAnchor(undefined, "add:right", `https://en.wikipedia.org/wiki/${title.trim()}`, "wikipedia reference");
+
+                    const fstate = this.TextBox.EditorView?.state;
+                    if (fstate) {
+                        const tr = fstate?.tr.deleteRange(start, start + 5);
+                        return tr.setSelection(new TextSelection(tr.doc.resolve(end - 5))).insertText(" ");
+                    }
+                    return state.tr;
+                }),
+
             // create an inline view of a document {{ <layoutKey> : <Doc> }}  
             // {{:Doc}} => show default view of document   
             // {{<layout>}} => show layout for this doc   
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 52ef06b7f..1f6ce014f 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -19,13 +19,17 @@ export const marks: { [index: string]: MarkSpec } = {
     },
 
 
-    // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each link has an href URL and a title for use in menus and hover (Dash links have linkIDs & targetIDs). `title`
-    // defaults to the empty string. Rendered and parsed as an `<a>`
+    // :: MarkSpec an autoLinkAnchor. These are automatically generated anchors to "published" documents based on the anchor text matching the
+    // published document's title. 
+    // NOTE: unlike linkAnchors, the autoLinkAnchor's href's indicate the target anchor of the hyperlink and NOT the source.  This is because
+    // automatic links do not create a text selection Marker document for the source anchor, but use the text document itself.  Since 
+    // multiple automatic links can be created each having the same source anchor (the whole document), the target href of the link is needed to
+    // disambiguate links from one another.
+    // Rendered and parsed as an `<a>`
     // element.
     autoLinkAnchor: {
         attrs: {
             allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] },
-            linkDoc: { default: "" },
             location: { default: null },
             title: { default: null },
         },
@@ -44,7 +48,8 @@ export const marks: { [index: string]: MarkSpec } = {
             return ["a", { class: anchorids, "data-targethrefs": targethrefs, "data-linkdoc": node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0];
         }
     },
-    // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each link has an href URL and a title for use in menus and hover (Dash links have linkIDs & targetIDs). `title`
+    // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each linkAnchor specifies an href to the URL of the source selection Marker text,
+    // and a title for use in menus and hover. `title`
     // defaults to the empty string. Rendered and parsed as an `<a>`
     // element.
     linkAnchor: {
-- 
cgit v1.2.3-70-g09d2


From fd58a5f8598815ab1ab3893e60973654a59a52c1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 12 Apr 2022 10:54:28 -0400
Subject: cleaned up errors/warnings.   fixed dragging to turn off pointer
 events on whatever's being dragged.

---
 src/client/util/DocumentManager.ts                             | 10 ++++++----
 src/client/util/DragManager.ts                                 |  2 +-
 src/client/views/StyleProvider.tsx                             |  4 +++-
 src/client/views/collections/CollectionDockingView.tsx         |  4 ++--
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx      |  4 ++--
 src/client/views/nodes/LabelBox.tsx                            |  7 +------
 src/client/views/nodes/PDFBox.tsx                              |  6 +++---
 src/client/views/nodes/VideoBox.tsx                            |  2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx      |  2 +-
 10 files changed, 21 insertions(+), 22 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 6076de055..1d603e18f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -169,10 +169,12 @@ export class DocumentManager {
         const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
         const docView = getFirstDocView(targetDoc, originatingDoc);
         const wasHidden = targetDoc.hidden;  // 
-        if (wasHidden) runInAction(() => {
-            targetDoc.hidden = false;
-            docView?.props.bringToFront(targetDoc);
-        });  // if the target is hidden, un-hide it here.
+        if (wasHidden) {
+            runInAction(() => {
+                targetDoc.hidden = false;
+                docView?.props.bringToFront(targetDoc);
+            });  // if the target is hidden, un-hide it here.
+        }
         const focusAndFinish = (didFocus: boolean) => {
             if (originatingDoc?.isPushpin) {
                 if (!didFocus && !wasHidden) {  // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 94a09eac4..13e5940d6 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -405,7 +405,7 @@ export namespace DragManager {
                 Array.from(pdfBox).filter(pb => pb.width && pb.height).map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0));
             }
             [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele =>
-                ele.hasAttribute("style") && ((ele as any).style.pointerEvents = "none"));
+                (ele as any).style && ((ele as any).style.pointerEvents = "none"));
 
             dragDiv.appendChild(dragElement);
             if (dragElement !== ele) {
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 2ce78f64f..9803f9782 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -149,7 +149,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                                     (doc?._isGroup ? undefined :
                                         StrCast((props?.renderDepth || 0) > 0 ?
                                             Doc.UserDoc().activeCollectionNestedBackground :
-                                            Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? Colors.BLACK : Colors.WHITE))
+                                            Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ?
+                                                Colors.BLACK :
+                                                "linear-gradient(#065fff, #85c1f9)"))
                                     ));
                     break;
                 //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6931d9896..e95371aaa 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -92,7 +92,7 @@ export class CollectionDockingView extends CollectionSubView() {
                 DragManager.CompleteWindowDrag = undefined;
             }
             finishDrag?.(aborted);
-        }
+        };
     }
     @undoBatch
     public CloseFullScreen = () => {
@@ -316,7 +316,7 @@ export class CollectionDockingView extends CollectionSubView() {
             }
             this._goldenLayout.init();
             this._goldenLayout.root.layoutManager.on('itemDropped', this.tabItemDropped);
-            this._goldenLayout.root.layoutManager.on('dragStart', this.tabDragStart)
+            this._goldenLayout.root.layoutManager.on('dragStart', this.tabDragStart);
             this._goldenLayout.root.layoutManager.on('activeContentItemChanged', this.stateChanged);
         }
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 04beed541..9b0acffdf 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1417,7 +1417,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     updateIcon = () => {
         this.props.docViewPath().lastElement().ContentDiv!.style.width = (this.layoutDoc[WidthSym]()).toString();
         this.props.docViewPath().lastElement().ContentDiv!.style.height = (this.layoutDoc[HeightSym]()).toString();
-        var htmlString = this._mainCont && new XMLSerializer().serializeToString(this.props.docViewPath().lastElement().ContentDiv!);
+        const htmlString = this._mainCont && new XMLSerializer().serializeToString(this.props.docViewPath().lastElement().ContentDiv!);
         this.props.docViewPath().lastElement().ContentDiv!.style.width = "";
         this.props.docViewPath().lastElement().ContentDiv!.style.height = "";
         const nativeWidth = this.layoutDoc[WidthSym]();
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7fb2c235a..3e35039e3 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -168,8 +168,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             PanelWidth: this.panelWidth,
             PanelHeight: this.panelHeight,
         };
-        var background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (typeof background === "string" && background && DashColor(background).alpha() !== 1 ? "multiply" : undefined);
+        const background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+        const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (typeof background === "string" && background && (!background.startsWith("linear") && DashColor(background).alpha() !== 1) ? "multiply" : undefined);
         return <div className={"collectionFreeFormDocumentView-container"}
             style={{
                 outline: this.Highlight ? "orange solid 2px" : "",
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 3405a7635..d539ca9b8 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -30,11 +30,6 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
 
     componentDidMount() {
         this.props.setContentView?.(this);
-        console.log("MOUNTING")
-    }
-
-    componentDidUpdate() {
-        console.log("UPDATING")
     }
 
     getTitle() {
@@ -89,7 +84,7 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
             verticalAlign: "center",
             textAlign: "center",
             whiteSpace: "nowrap"
-        })
+        });
     }
     // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
     render() {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 3f1771e68..c73823a2b 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -63,7 +63,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
         if (oldDiv instanceof HTMLCanvasElement) {
             const canvas = oldDiv as HTMLCanvasElement;
-            var img = document.createElement('img'); // create a Image Element
+            const img = document.createElement('img'); // create a Image Element
             img.src = canvas.toDataURL();   //image source
             img.style.width = canvas.style.width;
             img.style.height = canvas.style.height;
@@ -79,8 +79,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
         newDiv.style.width = (this.layoutDoc[WidthSym]()).toString();
         newDiv.style.height = (this.layoutDoc[HeightSym]()).toString();
-        this.replaceCanvases(docViewContent, newDiv)
-        var htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
+        this.replaceCanvases(docViewContent, newDiv);
+        const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
         const nativeWidth = this.layoutDoc[WidthSym]();
         const nativeHeight = this.layoutDoc[HeightSym]();
 
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index b7b8ce064..c31d38b0d 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -265,7 +265,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             this.dataDoc.icon = new ImageField(returnedfilename);
             this.dataDoc["icon-nativeWidth"] = this.layoutDoc[WidthSym]();
             this.dataDoc["icon-nativeHeight"] = this.layoutDoc[HeightSym]();
-        }
+        };
         this.Snapshot(undefined, undefined, makeIcon);
     }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ec88b8d1d..b6713d878 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -364,7 +364,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     updateTitle = () => {
-        const title = StrCast(this.dataDoc.title)
+        const title = StrCast(this.dataDoc.title);
         if (!this.props.dontRegisterView &&  // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
             (title.startsWith("-") || title.startsWith("@")) && this._editorView && !this.dataDoc["title-custom"] &&
             (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) {
-- 
cgit v1.2.3-70-g09d2


From 74ef5f7a8c60dc68a1262077f7502d0535377b88 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 14 Apr 2022 11:49:28 -0400
Subject: fixed following links to documents in iconified collections/text to
 deiconify.  fixed ctrl-x to cut (not iconify).  changed lightboxview to add
 to future docs when stepping into.  added placeholder for indicating how many
 documents are left in lightbox future

---
 src/client/util/DocumentManager.ts                        | 14 +++++++++++++-
 src/client/views/DocumentDecorations.tsx                  |  8 ++++----
 src/client/views/GlobalKeyHandler.ts                      |  2 +-
 src/client/views/LightboxView.tsx                         | 11 ++++-------
 src/client/views/nodes/DocumentView.tsx                   | 15 +++++++++------
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  4 ++--
 6 files changed, 33 insertions(+), 21 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index fb50ecb4a..607a3d6bf 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -197,7 +197,14 @@ export class DocumentManager {
             (wasHidden && annoContainerView);// if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
         const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView;
         if (!docView && annoContainerView) {
-            annoContainerView.focus(targetDoc);  // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
+            if (annoContainerView.props.Document.layoutKey === "layout_icon") {
+                annoContainerView.iconify(() => this.jumpToDocument(
+                    targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc,
+                    finished, originalTarget, noSelect, presZoom));
+                return;
+            } else {
+                annoContainerView.focus(targetDoc);  // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
+            }
         }
         if (focusView) {
             !noSelect && Doc.linkFollowHighlight(focusView.rootDoc); //TODO:glr make this a setting in PresBox
@@ -217,6 +224,11 @@ export class DocumentManager {
                     targetDocContextView.props.focus(targetDocContextView.rootDoc, {
                         willZoom, afterFocus: async () => {
                             targetDocContext._viewTransition = undefined;
+                            if (targetDocContext.layoutKey === "layout_icon") {
+                                targetDocContextView.iconify(() => this.jumpToDocument(
+                                    targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc,
+                                    finished, originalTarget, noSelect, presZoom));
+                            }
                             return ViewAdjustment.doNothing;
                         }
                     });
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index a8d91e0a2..78a8a4c7e 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -145,11 +145,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
     }
 
     _iconifyTimeout: NodeJS.Timeout | undefined;
-    onCloseClick = () => {
+    onCloseClick = (force = false) => {
         if (this.canDelete) {
             const views = SelectionManager.Views().slice().filter(v => v);
-            const icons = this._iconifyTimeout ? views : views.filter(view => view.rootDoc.layoutKey === "layout_icon");
-            const others = this._iconifyTimeout ? [] : views.filter(view => view.rootDoc.layoutKey !== "layout_icon");
+            const icons = this._iconifyTimeout || force ? views : views.filter(view => view.rootDoc.layoutKey === "layout_icon");
+            const others = this._iconifyTimeout || force ? [] : views.filter(view => view.rootDoc.layoutKey !== "layout_icon");
             icons.forEach(iconView => iconView.props.removeDocument?.(iconView.props.Document));
             others.forEach(dv => dv.iconify());
             others.length && (this._iconifyTimeout = setTimeout(() => {
@@ -519,7 +519,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                     left: bounds.x - this._resizeBorderWidth / 2,
                     top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
                 }}>
-                    {!canDelete ? <div /> : topBtn("close", this.hasIcons ? "times" : "window-maximize", undefined, this.onCloseClick, "Close")}
+                    {!canDelete ? <div /> : topBtn("close", this.hasIcons ? "times" : "window-maximize", undefined, e => this.onCloseClick(), "Close")}
                     {titleArea}
                     {!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")}
                     {hideResizers ? (null) :
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 91e0503b3..e3031d2c5 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -275,7 +275,7 @@ export class KeyManager {
                     const pt = SelectionManager.Views()[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
                     const text = `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views().map(dv => dv.Document[Id]).join(":");
                     SelectionManager.Views().length && navigator.clipboard.writeText(text);
-                    DocumentDecorations.Instance.onCloseClick();
+                    DocumentDecorations.Instance.onCloseClick(true);
                     stopPropagation = false;
                     preventDefault = false;
                 }
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index b39b5020c..59ed0dc92 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -63,7 +63,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             }
         }
         if (future) {
-            this._future = future.slice().sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)).sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length);
+            this._future = [...(this._future ?? []), ...(this.LightboxDoc ? [this.LightboxDoc] : []), ...future.slice().sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)).sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length),];
         }
         this._doc = doc;
         this._layoutTemplate = layoutTemplate;
@@ -118,7 +118,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
     addDocTab = LightboxView.AddDocTab;
     @action public static Next() {
         const doc = LightboxView._doc!;
-        const target = LightboxView._docTarget = LightboxView._future?.pop();
+        const target = LightboxView._docTarget = this._future?.pop();
         const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target);
         if (targetDocView && target) {
             const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.() || target).lastElement();
@@ -163,11 +163,8 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc);
         if (docView) {
             LightboxView._docTarget = target;
-            const focusSpeed = 1000;
-            doc._viewTransition = `transform ${focusSpeed}ms`;
             if (!target) docView.ComponentView?.shrinkWrap?.();
             else docView.focus(target, { willZoom: true, scale: 0.9 });
-            setTimeout(() => doc._viewTransition = undefined, focusSpeed);
         }
         else {
             LightboxView.SetLightboxDoc(doc, target);
@@ -232,7 +229,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                             const doc = LightboxView._doc;
                             const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target);
                             if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.();
-                            else target && targetView?.focus(target, { willZoom: true, scale: 0.9, instant: true });
+                            //else target && targetView?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here?  it breaks smooth navigation in lightbox using 'next' button
                         }));
                     })}
                         Document={LightboxView.LightboxDoc}
@@ -272,7 +269,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                     () => LightboxView.LightboxDoc && LightboxView._future?.length ? "" : "none", e => {
                         e.stopPropagation();
                         LightboxView.Next();
-                    })}
+                    }, this.future()?.length.toString())}
                 <LightboxTourBtn navBtn={this.navBtn} future={this.future} stepInto={this.stepInto} tourMap={this.tourMap} />
                 <div className="lightboxView-tabBtn" title={"open in tab"}
                     onClick={e => {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0e22b1b3f..4380cb0bc 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -452,7 +452,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         });
         // after  a timeout, the right _componentView should have been created, so call it to update its view spec values
         setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
-        const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
+        const focusSpeed = this._componentView?.scrollFocus?.(anchor, !options?.instant || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
         const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing;
         this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
             ...options, afterFocus: (didFocus: boolean) =>
@@ -1243,15 +1243,15 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) };
     }
 
-    public iconify() {
+    public iconify(finished?: () => void) {
         this.ComponentView?.updateIcon?.();
         const layoutKey = Cast(this.Document.layoutKey, "string", null);
         if (layoutKey !== "layout_icon") {
-            this.switchViews(true, "icon");
+            this.switchViews(true, "icon", finished);
             if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") this.Document.deiconifyLayout = layoutKey.replace("layout_", "");
         } else {
             const deiconifyLayout = Cast(this.Document.deiconifyLayout, "string", null);
-            this.switchViews(deiconifyLayout ? true : false, deiconifyLayout);
+            this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finished);
             this.Document.deiconifyLayout = undefined;
             this.props.bringToFront(this.rootDoc);
         }
@@ -1262,12 +1262,15 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         Doc.setNativeView(this.props.Document);
         custom && DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
     }
-    switchViews = action((custom: boolean, view: string) => {
+    switchViews = action((custom: boolean, view: string, finished?: () => void) => {
         this.docView && (this.docView._animateScalingTo = 0.1);  // shrink doc
         setTimeout(action(() => {
             this.setCustomView(custom, view);
             this.docView && (this.docView._animateScalingTo = 1); // expand it
-            setTimeout(action(() => this.docView && (this.docView._animateScalingTo = 0)), this.docView!._animateScaleTime - 10);
+            setTimeout(action(() => {
+                this.docView && (this.docView._animateScalingTo = 0);
+                finished?.();
+            }), this.docView!._animateScaleTime - 10);
         }), this.docView!._animateScaleTime - 10);
     });
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b6713d878..ac291801b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -758,7 +758,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
             let tr = state.tr.addMark(sel.from, sel.to, splitter);
             if (sel.from !== sel.to) {
-                const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: "#" + this._editorView?.state.doc.textBetween(sel.from, sel.to), unrendered: true });
+                const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: "#" + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true });
                 const href = targetHref ?? Doc.localServerPath(anchor);
                 if (anchor !== anchorDoc) this.addDocument(anchor);
                 tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
@@ -859,7 +859,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => {
                 autoHeight && this.props.setHeight?.(marginsHeight + Math.max(sidebarHeight, textHeight));
             }, { fireImmediately: true });
-        this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
+        this._disposers.links = reaction(() => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
             newLinks => {
                 this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l));
                 this._cachedLinks = newLinks;
-- 
cgit v1.2.3-70-g09d2


From 239ff2c0423333bb7e154158c1f52ef688875ee7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 24 Apr 2022 14:50:21 -0400
Subject: fixed treeViews to vertically span their containers.  fixed
 foreground coloring of nodes with transparent backgrounds to consider parent
 collection background color

---
 src/client/views/StyleProvider.tsx                 |  5 ++--
 src/client/views/collections/CollectionSubView.tsx | 34 +++++++++++-----------
 .../views/collections/CollectionTreeView.scss      |  6 ++++
 .../views/collections/CollectionTreeView.tsx       |  3 +-
 src/client/views/collections/TreeView.tsx          |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  6 ++++
 .../views/nodes/formattedText/FormattedTextBox.tsx | 16 +++++-----
 7 files changed, 41 insertions(+), 31 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index ba5aaf36a..5c10c2344 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -81,7 +81,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
     const isAnchor = property.includes(":anchor");
     const isAnnotated = property.includes(":annotated");
     const isOpen = property.includes(":open");
-    const fieldKey = (props as any)?.fieldKey ? (props as any).fieldKey + "-" : isCaption ? "caption-" : "";
+    const fieldKey = props?.fieldKey ? props.fieldKey + "-" : isCaption ? "caption-" : "";
     const comicStyle = () => doc && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === "comic";
     const isBackground = () => StrListCast(doc?._layerTags).includes(StyleLayers.Background);
     const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
@@ -114,7 +114,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             if (MainView.Instance.LastButton === doc) return Colors.DARK_GRAY;
             const docColor: Opt<string> = StrCast(doc?.[fieldKey + "color"], StrCast(doc?._color));
             if (docColor) return docColor;
-            const backColor = backgroundCol();
+            const docView = props?.DocumentView?.();
+            const backColor = backgroundCol() || docView?.props.styleProvider?.(docView.props.treeViewDoc, docView.props, "backgroundColor");
             if (!backColor) return undefined;
             return lightOrDark(backColor);
         case StyleProp.Hidden: return BoolCast(doc?.hidden);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d8f1287cd..0eb94c394 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,23 +1,32 @@
-import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx";
+import { action, computed, observable } from "mobx";
+import ReactLoading from 'react-loading';
+import * as rp from 'request-promise';
 import CursorField from "../../../fields/CursorField";
-import { Doc, Opt, Field, DocListCast, AclPrivate, StrListCast } from "../../../fields/Doc";
-import { Id, ToString } from "../../../fields/FieldSymbols";
+import { AclPrivate, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc";
+import { Id } from "../../../fields/FieldSymbols";
 import { List } from "../../../fields/List";
 import { listSpec } from "../../../fields/Schema";
 import { ScriptField } from "../../../fields/ScriptField";
+import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
 import { WebField } from "../../../fields/URLField";
-import { Cast, ScriptCast, NumCast, StrCast } from "../../../fields/Types";
+import { GetEffectiveAcl, TraceMobx } from "../../../fields/util";
 import { GestureUtils } from "../../../pen-gestures/GestureUtils";
-import { Utils, returnFalse, returnEmptyFilter } from "../../../Utils";
+import { returnFalse, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
+import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { Networking } from "../../Network";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { DragManager, dropActionType } from "../../util/DragManager";
 import { ImageUtils } from "../../util/Import & Export/ImageUtils";
 import { InteractionUtils } from "../../util/InteractionUtils";
+import { SelectionManager } from "../../util/SelectionManager";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocComponent } from "../DocComponent";
+import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox";
+import { OverlayView } from "../OverlayView";
+import { CollectionView, CollectionViewProps, CollectionViewType } from "./CollectionView";
 import React = require("react");
-import ReactLoading from 'react-loading';
-import * as rp from 'request-promise';
-import { Networking } from "../../Network";
 
 
 export interface SubCollectionViewProps extends CollectionViewProps {
@@ -472,13 +481,4 @@ export function CollectionSubView<X>(moreProps?: X) {
     return CollectionSubView;
 }
 
-import { DragManager, dropActionType } from "../../util/DragManager";
-import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox";
-import { CollectionView, CollectionViewType, CollectionViewProps } from "./CollectionView";
-import { SelectionManager } from "../../util/SelectionManager";
-import { OverlayView } from "../OverlayView";
-import { GetEffectiveAcl, TraceMobx } from "../../../fields/util";
 
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index b664d9d82..a8eee9c19 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -2,6 +2,7 @@
 
 .collectionTreeView-container {
     transform-origin: top left;
+    height: 100%;
 }
 .collectionTreeView-dropTarget {
     border-width: $COLLECTION_BORDER_WIDTH;
@@ -71,6 +72,11 @@
     display: none;
 }
 
+.collectionTreeView-contents {
+    display: flex;
+    flex-direction: column;
+}
+
 .collectionTreeView-titleBar {
     display: inline-block;
     width: 100%;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e84517f40..76da96174 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -341,14 +341,13 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             <div className="collectionTreeView-contents" key="tree" style={{
                 ...(!titleBar ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}),
                 overflow: "auto",
-                height: this.layoutDoc._autoHeight ? "max-content" : "100%"
+                height: "100%"//this.layoutDoc._autoHeight ? "max-content" : "100%"
             }} >
                 {titleBar}
                 <div className="collectionTreeView-container"
                     style={{
                         transform: this.outlineMode ? `scale(${this.contentScaling})` : "",
                         paddingLeft: `${this.marginX()}px`,
-                        height: "max-content",
                         width: this.outlineMode ? `calc(${100 / this.contentScaling}%)` : ""
                     }}
                     onContextMenu={this.onContextMenu}>
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 342424d41..61c7ff78e 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -225,7 +225,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             title: "-title-",
             treeViewExpandedViewLock: true, treeViewExpandedView: "data",
             _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewType: "outline",
-            x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, backgroundColor: "transparent", _width: 1000, _height: 10
+            x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _width: 1000, _height: 10
         });
         Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
         Doc.GetProto(bullet).data = new List<Doc>([]);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bcf00e88d..504a3afb6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -104,7 +104,10 @@ export interface DocComponentView {
     snapPt?: (pt: { X: number, Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number, Y: number }, distance: number };
     search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
 }
+// These props are passed to both FieldViews and DocumentViews 
 export interface DocumentViewSharedProps {
+    fieldKey?: string; // only used by FieldViews but helpful here to allow styleProviders to access fieldKey of FieldViewProps.  In priniciple, passing a fieldKey to a documentView could override or be the default fieldKey for fieldViews
+    DocumentView?: () => DocumentView;
     renderDepth: number;
     Document: Doc;
     DataDoc?: Doc;
@@ -149,6 +152,8 @@ export interface DocumentViewSharedProps {
     createNewFilterDoc?: () => void;
     updateFilterDoc?: (doc: Doc) => void;
 }
+
+// these props are specific to DocuentViews
 export interface DocumentViewProps extends DocumentViewSharedProps {
     // properties specific to DocumentViews but not to FieldView
     freezeDimensions?: boolean;
@@ -173,6 +178,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     onPointerUp?: () => ScriptField;
 }
 
+// these props are only available in DocumentViewIntenral
 export interface DocumentViewInternalProps extends DocumentViewProps {
     NativeWidth: () => number;
     NativeHeight: () => number;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ac291801b..dc1c67bea 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,23 +1,23 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { isEqual } from "lodash";
-import { action, computed, IReactionDisposer, reaction, runInAction, observable, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { baseKeymap, selectAll } from "prosemirror-commands";
 import { history } from "prosemirror-history";
 import { inputRules } from 'prosemirror-inputrules';
 import { keymap } from "prosemirror-keymap";
 import { Fragment, Mark, Node, Slice } from "prosemirror-model";
-import { ReplaceStep } from 'prosemirror-transform';
 import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym, AclAugment } from "../../../../fields/Doc";
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from "../../../../fields/Doc";
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { RichTextField } from "../../../../fields/RichTextField";
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
-import { Cast, DateCast, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
+import { Cast, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
@@ -29,6 +29,7 @@ import { DictationManager } from '../../../util/DictationManager';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from "../../../util/DragManager";
 import { makeTemplate } from '../../../util/DropConverter';
+import { LinkManager } from '../../../util/LinkManager';
 import { SelectionManager } from "../../../util/SelectionManager";
 import { SnappingManager } from '../../../util/SnappingManager';
 import { undoBatch, UndoManager } from "../../../util/UndoManager";
@@ -38,10 +39,11 @@ import { ContextMenu } from '../../ContextMenu';
 import { ContextMenuProps } from '../../ContextMenuItem';
 import { ViewBoxAnnotatableComponent } from "../../DocComponent";
 import { DocumentButtonBar } from '../../DocumentButtonBar';
+import { Colors } from '../../global/globalEnums';
 import { LightboxView } from '../../LightboxView';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
+import { SidebarAnnos } from '../../SidebarAnnos';
 import { StyleProp } from '../../StyleProvider';
-import { AudioBox } from '../AudioBox';
 import { FieldView, FieldViewProps } from "../FieldView";
 import { LinkDocPreview } from '../LinkDocPreview';
 import { DashDocCommentView } from "./DashDocCommentView";
@@ -60,10 +62,6 @@ import { schema } from "./schema_rts";
 import { SummaryView } from "./SummaryView";
 import applyDevTools = require("prosemirror-dev-tools");
 import React = require("react");
-import { SidebarAnnos } from '../../SidebarAnnos';
-import { Colors } from '../../global/globalEnums';
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { LinkManager } from '../../../util/LinkManager';
 const translateGoogleApi = require("translate-google-api");
 
 export interface FormattedTextBoxProps {
-- 
cgit v1.2.3-70-g09d2


From cb59842fcb79af7ef72443e08c6a479c3f0af343 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 25 Apr 2022 20:20:48 -0400
Subject:  changed freeformview's with engines to not allow children to be
 edited using setHeight prop since engine layouts are temporary and shouldn't
 edit the doc.  fixed tags to work with pivot view. cleaned up pile views to
 be less hacky..

---
 src/client/documents/Documents.ts                  | 27 +++++++++++++---------
 src/client/views/MainView.tsx                      |  1 +
 src/client/views/SidebarAnnos.tsx                  |  2 +-
 .../views/collections/CollectionPileView.tsx       |  4 ++--
 .../views/collections/CollectionStackingView.tsx   |  6 ++---
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/TreeView.tsx          |  1 +
 .../CollectionFreeFormLayoutEngines.tsx            | 26 +++++++++++++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +++-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  5 +---
 src/client/views/nodes/DocumentContentsView.tsx    |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 17 +++++++-------
 src/client/views/nodes/FieldView.tsx               |  2 +-
 src/client/views/nodes/LinkDocPreview.tsx          |  1 +
 src/client/views/nodes/WebBox.tsx                  |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 src/fields/Doc.ts                                  |  2 +-
 19 files changed, 62 insertions(+), 48 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 49e53a214..a19530c92 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -14,7 +14,7 @@ import { Cast, NumCast, StrCast } from "../../fields/Types";
 import { AudioField, ImageField, MapField, PdfField, VideoField, WebField, YoutubeField } from "../../fields/URLField";
 import { SharingPermissions } from "../../fields/util";
 import { Upload } from "../../server/SharedMediaTypes";
-import { OmitKeys, Utils } from "../../Utils";
+import { OmitKeys, Utils, aggregateBounds } from "../../Utils";
 import { YoutubeBox } from "../apis/youtube/YoutubeBox";
 import { DocServer } from "../DocServer";
 import { Networking } from "../Network";
@@ -1352,26 +1352,31 @@ export namespace DocUtils {
         if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", "");
     }
 
-    export function pileup(docList: Doc[], x?: number, y?: number, create: boolean = true) {
+    export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) {
         let w = 0, h = 0;
         runInAction(() => {
             docList.forEach(d => {
                 DocUtils.iconify(d);
-                w = Math.max(d[WidthSym](), w);
-                h = Math.max(d[HeightSym](), h);
+                w = Math.max(NumCast(d._width), w);
+                h = Math.max(NumCast(d._height), h);
             });
-            h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away.  so this is a fallback hack to try to do something reasonable
             docList.forEach((d, i) => {
-                d.x = Math.cos(Math.PI * 2 * i / docList.length) * 10 - w / 2;
-                d.y = Math.sin(Math.PI * 2 * i / docList.length) * 10 - h / 2;
+                d.x = Math.cos(Math.PI * 2 * i / docList.length) * size;
+                d.y = Math.sin(Math.PI * 2 * i / docList.length) * size;
+                d._timecodeToShow = undefined;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+            });
+            const aggBounds = aggregateBounds(docList.map(d => ({ x: NumCast(d.x), y: NumCast(d.y), width: NumCast(d._width), height: NumCast(d._height) })), 0, 0);
+            docList.forEach((d, i) => {
+                d.x = NumCast(d.x) - ((aggBounds.r + aggBounds.x) / 2);
+                d.y = NumCast(d.y) - ((aggBounds.b + aggBounds.y) / 2);
                 d._timecodeToShow = undefined;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
             });
         });
         if (create) {
-            const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - 55, y: (y || 0) - 55, _width: 110, _height: 100, });
-            newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
-            newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
-            newCollection._width = newCollection._height = 110;
+            const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, });
+            newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size;
+            newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size;
+            newCollection._width = newCollection._height = size * 2;
             newCollection._jitterRotation = 10;
             return newCollection;
         }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a67cb3014..68b4710ed 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -297,6 +297,7 @@ export class MainView extends React.Component {
             searchFilterDocs={returnEmptyDoclist}
             ContainingCollectionView={undefined}
             ContainingCollectionDoc={undefined}
+            suppressSetHeight={true}
             renderDepth={-1}
         />;
     }
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index fae385660..43a02d029 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -88,7 +88,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
     removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey);
     docFilters = () => [...StrListCast(this.props.layoutDoc._docFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])];
     showTitle = () => "title";
-    setHeightCallback = (height: number) => this.props.setHeight(height + this.filtersHeight());
+    setHeightCallback = (height: number) => this.props.setHeight?.(height + this.filtersHeight());
     render() {
         const renderTag = (tag: string) => {
             const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:check`);
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 8832b0f4a..4489601db 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -74,13 +74,13 @@ export class CollectionPileView extends CollectionSubView() {
             this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
             this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize);
             this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize);
-            DocUtils.pileup(this.childDocs, undefined, undefined, false);
+            DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false);
             this.layoutDoc._panX = 0;
             this.layoutDoc._panY = -10;
             this.props.Document._pileLayoutEngine = 'pass';
         } else {
             const defaultSize = 25;
-            !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500);
+            !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 250);
             !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
             if (this.layoutEngine() === 'pass') {
                 this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 8634ea139..509005b45 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -141,7 +141,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             () => this.layoutDoc._columnHeaders = new List()
         );
         this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight,
-            autoHeight => autoHeight && this.props.setHeight(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
+            autoHeight => autoHeight && this.props.setHeight?.(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
                 this.headerMargin + (this.isStackingView ?
                     Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))) :
                     this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0)))));
@@ -408,7 +408,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                                 Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
                                     Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))));
                             if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) {
-                                this.props.setHeight(height);
+                                this.props.setHeight?.(height);
                             }
                         }
                     }));
@@ -458,7 +458,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                     this.observer = new _global.ResizeObserver(action((entries: any) => {
                         if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
                             const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
-                            this.props.setHeight(this.headerMargin + height);
+                            this.props.setHeight?.(this.headerMargin + height);
                         }
                     }));
                     this.observer.observe(ref);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 5bdd0c0ac..30e0adf24 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -453,7 +453,7 @@ export function CollectionSubView<X>(moreProps?: X) {
                 if (completed) completed(set);
                 else {
                     if (isFreeformView && generatedDocuments.length > 1) {
-                        addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!);
+                        addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!,);
                     } else {
                         generatedDocuments.forEach(addDocument);
                     }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 76da96174..41970eb96 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -88,7 +88,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace("px", ""));
             const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), this.marginBot());
             this.layoutDoc._autoHeightMargins = bodyHeight;
-            this.props.setHeight(bodyHeight + titleHeight);
+            this.props.setHeight?.(bodyHeight + titleHeight);
         }
     }
     unobserveHeight = (ref: any) => {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 6213850d8..b9710b3f5 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -30,6 +30,7 @@ import { CollectionTreeView } from './CollectionTreeView';
 import { CollectionView, CollectionViewType } from './CollectionView';
 import "./TreeView.scss";
 import React = require("react");
+import { KeyValueBox } from '../nodes/KeyValueBox';
 
 export interface TreeViewProps {
     treeView: CollectionTreeView;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 598960af7..16c7df311 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -111,17 +111,17 @@ export function computerStarburstLayout(
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
     engineProps: any
 ) {
-    const mustFit = pivotDoc[WidthSym]() !== panelDim[0];
+    const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; // if a panel size is set that's not the same as the pivot doc's size, then assume this is in a panel for a content fitting view (like a grid) in which case everything must be scaled to stay within the panel
     const docMap = new Map<string, PoolData>();
-    const burstRadius = mustFit ? panelDim : [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])];
-    const docSize = mustFit ? panelDim[0] * .33 : 75; // assume a icon sized at 75
-    const scaleDim = [burstRadius[0] + docSize, burstRadius[1] + docSize];
+    const docSize = mustFit ? panelDim[0] * .33 : 75; // assume an icon sized at 75
+    const burstRadius = mustFit ? panelDim : [NumCast(pivotDoc._starburstRadius, panelDim[0]) - docSize, NumCast(pivotDoc._starburstRadius, panelDim[1]) - docSize];
+    const scaleDim = [burstRadius[0] * 2 + docSize, burstRadius[1] * 2 + docSize];
     childPairs.forEach(({ layout, data }, i) => {
-        const docSize = layout.layoutKey === "layout_icon" ? mustFit ? panelDim[0] * .33 : 75 : 400; // assume a icon sized at 10750
+        const docSize = layout.layoutKey === "layout_icon" ? (mustFit ? panelDim[0] * .33 : 75) : 400; // assume a icon sized at 75
         const deg = i / childPairs.length * Math.PI * 2;
         docMap.set(layout[Id], {
-            x: Math.cos(deg) * (burstRadius[0] / 3) - docSize / 2,
-            y: Math.sin(deg) * (burstRadius[1] / 3) - docSize * layout[HeightSym]() / layout[WidthSym]() / 2,
+            x: Math.cos(deg) * burstRadius[0] - docSize / 2,
+            y: Math.sin(deg) * burstRadius[1] - docSize * layout[HeightSym]() / layout[WidthSym]() / 2,
             width: docSize,//layout[WidthSym](),
             height: docSize * layout[HeightSym]() / layout[WidthSym](),
             zIndex: NumCast(layout.zIndex),
@@ -129,7 +129,7 @@ export function computerStarburstLayout(
             replica: ""
         });
     });
-    const divider = { type: "div", color: "transparent", x: -burstRadius[0] / 3, y: 0, width: 15, height: 15, payload: undefined };
+    const divider = { type: "div", color: "transparent", x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined };
     return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
 }
 
@@ -267,7 +267,13 @@ export function computePivotLayout(
     });
 
     const dividers = sortedPivotKeys.map((key, i) =>
-        ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
+    ({
+        type: "div", color: "lightGray",
+        x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2,
+        y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander,
+        height: maxColHeight,
+        payload: pivotColumnGroups.get(key)!.filters
+    }));
     groupNames.push(...dividers);
     return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
 }
@@ -404,7 +410,7 @@ function normalizeResults(
     const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
     const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
     const aggBounds = aggregateBounds(extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" })))).filter(e => e.zIndex !== -99), 0, 0);
-    aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x);
+    aggBounds.r = aggBounds.x + Math.max(minWidth, aggBounds.r - aggBounds.x);
     const wscale = panelDim[0] / (aggBounds.r - aggBounds.x);
     let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale;
     if (Number.isNaN(scale)) scale = 1;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index eaef1b49c..a14405a0b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1158,6 +1158,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             replica={entry.replica}
             renderIndex={renderIndex}
             dataTransition={entry.transition}
+            suppressSetHeight={this.layoutEngine ? true : false}
             renderCutoffProvider={this.renderCutoffProvider}
             ContainingCollectionView={this.props.CollectionView}
             ContainingCollectionDoc={this.props.Document}
@@ -1300,10 +1301,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return [] as ViewDefResult[];
     }
 
+    @computed get layoutEngine() { return this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); }
     @computed get doInternalLayoutComputation() {
         TraceMobx();
         const newPool = new Map<string, PoolData>();
-        switch (this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine)) {
+        switch (this.layoutEngine) {
             case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
             case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
             case "pivot": return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3e35039e3..6097425e1 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -17,9 +17,6 @@ import { StyleProp } from "../StyleProvider";
 import "./CollectionFreeFormDocumentView.scss";
 import { DocumentView, DocumentViewProps } from "./DocumentView";
 import React = require("react");
-import { DocumentType } from "../../documents/DocumentTypes";
-import { collectionSchema } from "../../../fields/documentSchemas";
-import { CollectionViewType } from "../collections/CollectionView";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
@@ -174,7 +171,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             style={{
                 outline: this.Highlight ? "orange solid 2px" : "",
                 width: this.panelWidth(),
-                height: !this.ShowTitle && this.layoutDoc.autoHeight && this.rootDoc.type === DocumentType.RTF ? undefined : this.panelHeight(),
+                height: this.panelHeight(),
                 transform: this.transform,
                 transition: this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition),
                 zIndex: this.ZInd,
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 005133eb0..d4e8ffc7f 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -112,7 +112,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
     isSelected: (outsideReaction: boolean) => boolean,
     select: (ctrl: boolean) => void,
     scaling?: () => number,
-    setHeight: (height: number) => void,
+    setHeight?: (height: number) => void,
     layoutKey: string,
 }> {
     @computed get layout(): string {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 504a3afb6..20eabe6a9 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -114,6 +114,7 @@ export interface DocumentViewSharedProps {
     fitContentsToDoc?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document
     ContainingCollectionView: Opt<CollectionView>;
     ContainingCollectionDoc: Opt<Doc>;
+    suppressSetHeight?: boolean;
     thumbShown?: () => boolean;
     isHovering?: () => boolean;
     setContentView?: (view: DocComponentView) => any;
@@ -842,11 +843,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
     contentScaling = () => this.ContentScale;
     onClickFunc = () => this.onClickHandler;
-    setHeight = (height: number) => {
-        if (this.props.renderDepth !== -1) {
-            this.layoutDoc._height = height;
-        }
-    }
+    setHeight = (height: number) => this.layoutDoc._height = height;
     setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
     isContentActive = (outsideReaction?: boolean) => {
         return this.props.isContentActive() === false ? false : (
@@ -890,7 +887,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 setContentView={this.setContentView}
                 scaling={this.contentScaling}
                 PanelHeight={this.panelHeight}
-                setHeight={this.setHeight}
+                setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined}
                 isContentActive={this.isContentActive}
                 ScreenToLocalTransform={this.screenToLocal}
                 rootSelected={this.rootSelected}
@@ -1207,7 +1204,11 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.docView?._componentView?.reverseNativeScaling?.() ? 0 :
             returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions));
     }
-    @computed get shouldNotScale() { return (this.fitWidth && !this.nativeWidth) || this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any); }
+    @computed get shouldNotScale() {
+        return (this.fitWidth && !this.nativeWidth) ||
+            this.props.ContainingCollectionView?.collectionViewType === CollectionViewType.Time ||
+            this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any);
+    }
     @computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : (this.nativeWidth || NumCast(this.layoutDoc.width)); }
     @computed get effectiveNativeHeight() { return this.shouldNotScale ? 0 : (this.nativeHeight || NumCast(this.layoutDoc.height)); }
     @computed get nativeScaling() {
@@ -1324,7 +1325,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                         position: this.props.Document.isInkMask ? "absolute" : undefined,
                         transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
                         width: isButton || isPresTreeElement ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
-                        height: (!this.props.ignoreAutoHeight && this.layoutDoc.autoHeight && this.layoutDoc.type === DocumentType.RTF) || isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
+                        height: isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
                             `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
                     }}>
                     <DocumentViewInternal {...this.props}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 943b9f153..ae9acfe3f 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -22,7 +22,7 @@ export interface FieldViewProps extends DocumentViewSharedProps {
     isDocumentActive?: () => boolean;
     isSelected: (outsideReaction?: boolean) => boolean;
     scaling?: () => number;
-    setHeight: (height: number) => void;
+    setHeight?: (height: number) => void;
 
     // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
     pointerEvents?: string;
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index eca2e3cfd..55f6d6059 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -195,6 +195,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                             ContainingCollectionDoc={undefined}
                             ContainingCollectionView={undefined}
                             renderDepth={-1}
+                            suppressSetHeight={true}
                             PanelWidth={this.width}
                             PanelHeight={this.height}
                             focus={DocUtils.DefaultFocus}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 0fd193977..eeb7b925b 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -164,7 +164,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             autoHeight => {
                 if (autoHeight) {
                     this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]);
-                    this.props.setHeight(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
+                    this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
                 }
             });
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index dc1c67bea..710270be4 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1524,7 +1524,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (children) {
             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
+            if (this.props.setHeight && 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();
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 9aaa6e90f..92a69c02b 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -123,7 +123,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
             autoHeight => {
                 if (autoHeight) {
                     this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]);
-                    this.props.setHeight(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
+                    this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
                 }
             });
 
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 15df673f3..63af8401c 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1108,7 +1108,7 @@ export namespace Doc {
         if (typeof value === "string") {
             value = value.replace(`,${Utils.noRecursionHack}`, "");
         }
-        const fieldVal = doc[key];
+        const fieldVal = key === "#" ? (StrCast(doc.tags).includes(":#" + value + ":") ? StrCast(doc.tags) : undefined) : doc[key];
         if (Cast(fieldVal, listSpec("string"), []).length) {
             const vals = Cast(fieldVal, listSpec("string"), []);
             const docs = vals.some(v => (v as any) instanceof Doc);
-- 
cgit v1.2.3-70-g09d2


From 08217336445cf2b3cd3efbe97e3c83525f02bf1b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 26 Apr 2022 22:34:21 -0400
Subject: added image cropping. made treeView icons show up for file system and
 dashboard, and made them persist invisibly to fix triggering on hover. 
 changed tree view filesystem and dashboard to show expanded icons on hover.

---
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/MarqueeAnnotator.tsx              | 27 ++++++++++++-
 src/client/views/PropertiesView.tsx                |  4 +-
 src/client/views/StyleProvider.scss                |  2 +-
 src/client/views/collections/TreeView.scss         | 23 +++++++++--
 src/client/views/collections/TreeView.tsx          | 16 ++++----
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 19 +++++----
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/ImageBox.tsx                | 45 ++++++++++++++++++++++
 .../views/nodes/formattedText/FormattedTextBox.tsx |  3 +-
 src/client/views/pdf/AnchorMenu.tsx                | 16 +++++++-
 11 files changed, 132 insertions(+), 27 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 68b4710ed..b73074899 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -362,7 +362,7 @@ export class MainView extends React.Component {
                         pinToPres={emptyFunction}
                         docViewPath={returnEmptyDoclist}
                         layerProvider={undefined}
-                        styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards ? DashboardStyleProvider : DefaultStyleProvider}
+                        styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards || this._sidebarContent.proto === Doc.UserDoc().myFilesystem ? DashboardStyleProvider : DefaultStyleProvider}
                         rootSelected={returnTrue}
                         removeDocument={returnFalse}
                         ScreenToLocalTransform={this.mainContainerXf}
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 563261dec..a6b012bd6 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -5,7 +5,7 @@ import { Id } from "../../fields/FieldSymbols";
 import { List } from "../../fields/List";
 import { NumCast } from "../../fields/Types";
 import { GetEffectiveAcl } from "../../fields/util";
-import { Utils } from "../../Utils";
+import { unimplementedFunction, Utils } from "../../Utils";
 import { Docs } from "../documents/Documents";
 import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { DragManager } from "../util/DragManager";
@@ -33,6 +33,7 @@ export interface MarqueeAnnotatorProps {
     getPageFromScroll?: (top: number) => number;
     finishMarquee: (x?: number, y?: number, PointerEvent?: PointerEvent) => void;
     anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
+    anchorMenuCrop?: (anchor: Doc | undefined, addCrop: boolean) => Doc | undefined;
 }
 @observer
 export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
@@ -63,6 +64,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
         doc.addEventListener("pointermove", this.onSelectMove);
         doc.addEventListener("pointerup", this.onSelectEnd);
 
+        AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight("rgba(173, 216, 230, 0.75)", true), true);
         AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight("rgba(173, 216, 230, 0.75)", true));
         AnchorMenu.Instance.Highlight = this.highlight;
         AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => this.highlight("rgba(173, 216, 230, 0.75)", true, savedAnnotations);
@@ -93,6 +95,29 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
                 }
             });
         });
+        /**
+         * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.  
+         * It also initiates a Drag/Drop interaction to place the text annotation.
+         */
+        AnchorMenu.Instance.StartCropDrag = !this.props.anchorMenuCrop ? unimplementedFunction : action((e: PointerEvent, ele: HTMLElement) => {
+            e.preventDefault();
+            e.stopPropagation();
+            var cropRegion: Doc | undefined;
+            const sourceAnchorCreator = () => {
+                cropRegion = this.highlight("rgba(173, 216, 230, 0.75)", true); // hyperlink color
+                cropRegion && this.props.addDocument(cropRegion);
+                return cropRegion;
+            };
+            const targetCreator = (annotationOn: Doc | undefined) => this.props.anchorMenuCrop!(cropRegion, false)!;
+            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
+                dragComplete: e => {
+                    if (!e.aborted && e.linkDocument) {
+                        Doc.GetProto(e.linkDocument).linkRelationship = "cropped image";
+                        Doc.GetProto(e.linkDocument).title = "crop: " + this.props.docView.rootDoc.title;
+                    }
+                }
+            });
+        });
     }
     componentWillUnmount() {
         const doc = (this.props.iframe?.()?.contentDocument ?? document);
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 91cf83619..b63395c76 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1360,12 +1360,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
                     {this.optionsSubMenu}
 
+                    {this.fieldsSubMenu}
+
                     {this.sharingSubMenu}
 
                     {isNovice ? null : this.filtersSubMenu}
 
-                    {isNovice ? null : this.fieldsSubMenu}
-
                     {isNovice ? null : this.layoutSubMenu}
                 </div>;
             }
diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss
index f26ed1f2d..8929954c8 100644
--- a/src/client/views/StyleProvider.scss
+++ b/src/client/views/StyleProvider.scss
@@ -25,5 +25,5 @@
 }
 
 .styleProvider-treeView-icon {
-    display: none;
+    opacity: 0;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index 7c75810d3..4707ebb80 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -21,7 +21,9 @@
     }
 
     .treeView-bulletIcons {
-        width: $TREE_BULLET_WIDTH;
+       // width: $TREE_BULLET_WIDTH; 
+        width: 100%;
+        height: 100%;
 
         .treeView-expandIcon {
             display: none;
@@ -37,10 +39,17 @@
 
         &:hover {
             .treeView-expandIcon {
-                display: unset;
+                display: unset; 
             }
         }
     }
+    .treeView-bulletIcons:hover img {
+        left: 14px;
+        position: absolute;
+        transform-origin: center left;
+        transform: scale(6);
+        pointer-events:none;
+    }
 
     .bullet {
         position: relative;
@@ -60,6 +69,10 @@
     pointer-events: all;
 }
 
+.bullet:hover {
+    z-index: 100;
+}
+
 .treeView-openRight {
     display: none;
     height: 17px;
@@ -123,7 +136,9 @@
         }
 
         >svg {
-            display: none;
+            //display: none;
+            opacity: 0;
+            pointer-events: none;
         }
     }
 }
@@ -150,6 +165,8 @@
         >svg,
         .styleProvider-treeView-icon {
             display: inherit;
+            opacity: unset;
+            pointer-events: unset;
         }
     }
 }
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index b9710b3f5..9108acfcf 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -101,8 +101,9 @@ export class TreeView extends React.Component<TreeViewProps> {
     get displayName() { return "TreeView(" + this.props.document.title + ")"; }  // this makes mobx trace() statements more descriptive
     get defaultExpandedView() {
         return this.doc.viewType === CollectionViewType.Docking ? this.fieldKey :
-            this.props.treeView.fileSysMode ? (this.doc.isFolder ? this.fieldKey : "layout") :
-                this.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.UserDoc().noviceMode ? "layout" : StrCast(this.props.treeView.doc.treeViewExpandedView, "fields");
+            this.props.treeView.dashboardMode ? this.fieldKey :
+                this.props.treeView.fileSysMode ? (this.doc.isFolder ? this.fieldKey : "aliases") :
+                    this.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.UserDoc().noviceMode ? "layout" : StrCast(this.props.treeView.doc.treeViewExpandedView, "fields");
     }
 
     @computed get doc() { return this.props.document; }
@@ -491,15 +492,12 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
 
     @computed get validExpandViewTypes() {
-        if (this.props.treeView.dashboardMode && Doc.UserDoc().noviceMode) {
-            return [this.doc.viewType === CollectionViewType.Docking ? this.fieldKey : "layout"];
-        }
-        const annos = () => DocListCast(this.doc[this.fieldKey + "-annotations"]).length ? "annotations" : "";
-        const links = () => DocListCast(this.doc.links).length ? "links" : "";
-        const data = () => this.childDocs ? this.fieldKey : "";
+        const annos = () => DocListCast(this.doc[this.fieldKey + "-annotations"]).length && !this.props.treeView.dashboardMode ? "annotations" : "";
+        const links = () => DocListCast(this.doc.links).length && !this.props.treeView.dashboardMode ? "links" : "";
+        const data = () => this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : "";
         const aliases = () => this.props.treeView.dashboardMode ? "" : "aliases";
         const fields = () => Doc.UserDoc().noviceMode ? "" : "fields";
-        const layout = this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"];
+        const layout = (this.props.treeView.dashboardMode && Doc.UserDoc().noviceMode) || this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"];
         return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m);
     }
     @action
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bdae5a4f8..f0b3d70a0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -936,8 +936,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (deltaScale * invTransform.Scale > 20) {
             deltaScale = 20 / invTransform.Scale;
         }
-        if (deltaScale * invTransform.Scale < 1 && this.isAnnotationOverlay) {
-            deltaScale = 1 / invTransform.Scale;
+        if (deltaScale * invTransform.Scale < NumCast(this.rootDoc._viewScaleMin, 1) && this.isAnnotationOverlay) {
+            deltaScale = NumCast(this.rootDoc._viewScaleMin, 1) / invTransform.Scale;
         }
 
         const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
@@ -989,8 +989,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || CurrentUserUtils.OverlayDocs.includes(this.Document)) {
             this._viewTransition = panTime;
             const scale = this.getLocalTransform().inverse().Scale;
-            const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
-            const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY));
+            const minScale = NumCast(this.rootDoc._viewScaleMin, 1);
+            const minPanX = NumCast(this.rootDoc._panXMin, 0);
+            const minPanY = NumCast(this.rootDoc._panYMin, 0);
+            const newPanX = Math.min(
+                minPanX + (1 - minScale / scale) * NumCast(this.rootDoc._panXMax, this.nativeWidth), Math.max(minPanX, panX));
+            const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) :
+                minPanY + (1 - minScale / scale) * NumCast(this.rootDoc._panYMax, this.nativeHeight)), Math.max(minPanY, panY));
             !this.Document._verticalScroll && (this.Document._panX = this.isAnnotationOverlay ? newPanX : panX);
             !this.Document._horizontalScroll && (this.Document._panY = this.isAnnotationOverlay ? newPanY : panY);
         }
@@ -1056,9 +1061,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 HistoryUtil.pushState(state);
             }
         }
-        if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) {
-            SelectionManager.DeselectAll();
-        }
+        // if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) {
+        //     SelectionManager.DeselectAll();
+        // }
         if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined) {
             this.props.focus(doc, options);
         } else {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 20eabe6a9..9d738180b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1084,7 +1084,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         TraceMobx();
         const thumb = ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png");
         const isButton = this.props.Document.type === DocumentType.FONTICON;
-        if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || this.hidden) return null;
+        if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || (this.hidden && !this.props.treeViewDoc)) return null;
         return this.docContents ??
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 id={this.props.Document[Id]}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index a1de944d8..aadad5ffa 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -14,6 +14,8 @@ import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
+import { Docs, DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 import { DragManager } from '../../util/DragManager';
@@ -127,6 +129,48 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         this.layoutDoc._height = w;
     });
 
+    crop = (region: Doc | undefined, addCrop?: boolean) => {
+        if (!region) return;
+        const cropping = Doc.MakeCopy(region, true);
+        Doc.GetProto(region).lockedPosition = true;
+        Doc.GetProto(region).title = "region:" + this.rootDoc.title;
+        Doc.GetProto(region).isPushpin = true;
+        this.addDocument(region);
+        const anchx = NumCast(cropping.x);
+        const anchy = NumCast(cropping.y);
+        const anchw = NumCast(cropping._width);
+        const anchh = NumCast(cropping._height);
+        const viewScale = NumCast(this.rootDoc[this.fieldKey + "-nativeWidth"]) / anchw;
+        cropping.title = "crop: " + this.rootDoc.title;
+        cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
+        cropping.y = NumCast(this.rootDoc.y);
+        cropping._width = anchw * (this.props.scaling?.() || 1);
+        cropping._height = anchh * (this.props.scaling?.() || 1);
+        cropping.isLinkButton = undefined;
+        const croppingProto = Doc.GetProto(cropping);
+        croppingProto.annotationOn = undefined;
+        croppingProto.isPrototype = true;
+        croppingProto.proto = Cast(this.rootDoc.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
+        croppingProto.type = DocumentType.IMG;
+        croppingProto.layout = ImageBox.LayoutString("data")
+        croppingProto.data = ObjectField.MakeCopy(this.rootDoc[this.fieldKey] as ObjectField);
+        croppingProto["data-nativeWidth"] = anchw;
+        croppingProto["data-nativeHeight"] = anchh;
+        croppingProto.viewScale = viewScale;
+        croppingProto.viewScaleMin = viewScale;
+        croppingProto.panX = anchx / viewScale;
+        croppingProto.panY = anchy / viewScale;
+        croppingProto.panXMin = (anchx) / viewScale;
+        croppingProto.panXMax = (anchw) / viewScale;
+        croppingProto.panYMin = (anchy) / viewScale;
+        croppingProto.panYMax = (anchh) / viewScale;
+        if (addCrop) {
+            DocUtils.MakeLink({ doc: region }, { doc: cropping }, "cropped image", "");
+        }
+        this.props.bringToFront(cropping);
+        return cropping;
+    }
+
     specificContextMenu = (e: React.MouseEvent): void => {
         const field = Cast(this.dataDoc[this.fieldKey], ImageField);
         if (field) {
@@ -367,6 +411,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     savedAnnotations={this._savedAnnotations}
                     annotationLayer={this._annotationLayer.current}
                     mainCont={this._mainCont.current}
+                    anchorMenuCrop={this.crop}
                 />}
         </div >);
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 710270be4..41e2fc266 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -19,7 +19,7 @@ import { RichTextField } from "../../../../fields/RichTextField";
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { Cast, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../../DocServer";
 import { Docs, DocUtils } from '../../../documents/Documents';
@@ -219,6 +219,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             return undefined;
         });
         AnchorMenu.Instance.onMakeAnchor = this.getAnchor;
+        AnchorMenu.Instance.StartCropDrag = unimplementedFunction;
         /**
          * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.  
          * It also initiates a Drag/Drop interaction to place the text annotation.
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index ad3afb775..29d068817 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -46,8 +46,10 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
 
+    public OnCrop: (e: PointerEvent) => void = unimplementedFunction;
     public OnClick: (e: PointerEvent) => void = unimplementedFunction;
     public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
+    public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
     public Highlight: (color: string, isPushpin: boolean) => Opt<Doc> = (color: string, isPushpin: boolean) => undefined;
     public GetAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
     public Delete: () => void = unimplementedFunction;
@@ -79,6 +81,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         }, returnFalse, e => this.OnClick?.(e));
     }
 
+    cropDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, (e: PointerEvent) => {
+            this.StartCropDrag(e, this._commentCont.current!);
+            return true;
+        }, returnFalse, e => this.OnCrop?.(e));
+    }
+
     @action
     highlightClicked = (e: React.MouseEvent) => {
         if (!this.Highlight(this.highlightColor, false) && this.Pinned) {
@@ -161,7 +170,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                         <FontAwesomeIcon style={{ position: "absolute", transform: "scale(0.5)", transformOrigin: "top left", top: 12, left: 12 }} icon={"link"} size="lg" />
                     </button>
                 </Tooltip>,
-                <LinkPopup key="popup" showPopup={this._showLinkPopup} linkFrom={this.onMakeAnchor} />
+                <LinkPopup key="popup" showPopup={this._showLinkPopup} linkFrom={this.onMakeAnchor} />,
+                AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? <></> : <Tooltip key="crop" title={<div className="dash-tooltip">{"Click/Drag to create cropped image"}</div>}>
+                    <button className="antimodeMenu-button annotate" onPointerDown={this.cropDown} style={{ cursor: "grab" }}>
+                        <FontAwesomeIcon icon="image" size="lg" />
+                    </button>
+                </Tooltip>,
             ] : [
                 <Tooltip key="trash" title={<div className="dash-tooltip">{"Remove Link Anchor"}</div>}>
                     <button className="antimodeMenu-button" onPointerDown={this.Delete}>
-- 
cgit v1.2.3-70-g09d2


From 2192b6cd994e71812d335a9902ef4e4ae296314b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 27 Apr 2022 23:33:19 -0400
Subject: fixed following links (jumpToDocument) to work properly with markers
 and hidden flags in nested arrangements.  pushpin behaviors had been broken
 for scrolling to a location within a text document and hiding the document if
 no scroll was needed.  unhiding hidden images/pdfs wasn't working when
 following a pushpin to an annotation on them.

---
 src/client/util/DocumentManager.ts                 | 26 +++++++++++-----------
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  7 ++++--
 3 files changed, 19 insertions(+), 16 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index cc3196baa..9febd4302 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -167,15 +167,15 @@ export class DocumentManager {
     ): Promise<void> => {
         originalTarget = originalTarget ?? targetDoc;
         const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
-        const docView = getFirstDocView(targetDoc, originatingDoc);
+        var docView = getFirstDocView(targetDoc, originatingDoc);
         const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null);
         const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? targetDoc : targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field
-        const wasHidden = resolvedTarget.hidden;
+        var wasHidden = resolvedTarget.hidden;
         if (wasHidden) {
             runInAction(() => {
-                resolvedTarget.hidden = false;
+                resolvedTarget.hidden = false;  // if the target is hidden, un-hide it here.
                 docView?.props.bringToFront(resolvedTarget);
-            });  // if the target is hidden, un-hide it here.
+            });
         }
         const focusAndFinish = (didFocus: boolean) => {
             const finalTargetDoc = docView?.Document ?? resolvedTarget;
@@ -188,16 +188,15 @@ export class DocumentManager {
                 !noSelect && docView?.select(false);
             }
             finished?.();
-            return false;
         };
-        const annoContainerView = annotatedDoc && getFirstDocView(annotatedDoc);
+        const annoContainerView = (!wasHidden || resolvedTarget !== annotatedDoc) && annotatedDoc && getFirstDocView(annotatedDoc);
         const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
         const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext : undefined;
         const targetDocContext = contextDoc || annotatedDoc;
         const targetDocContextView = (targetDocContext && getFirstDocView(targetDocContext)) ||
             (wasHidden && annoContainerView);// if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
         const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView;
-        if (!docView && annoContainerView) {
+        if ((!docView && targetDoc.type !== DocumentType.MARKER) && annoContainerView) {
             if (annoContainerView.props.Document.layoutKey === "layout_icon") {
                 annoContainerView.iconify(() => this.jumpToDocument(
                     targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc,
@@ -221,6 +220,8 @@ export class DocumentManager {
                 createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc)));
             } else {  // otherwise try to get a view of the context of the target
                 if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first..
+                    wasHidden = wasHidden || targetDocContextView.rootDoc.hidden;
+                    targetDocContextView.rootDoc.hidden = false; // make sure context isn't hidden
                     targetDocContext._viewTransition = "transform 500ms";
                     targetDocContextView.props.focus(targetDocContextView.rootDoc, {
                         willZoom, afterFocus: async () => {
@@ -240,24 +241,24 @@ export class DocumentManager {
                         finished?.();
                     } else { // no timecode means we need to find the context view and focus on our target
                         const findView = (delay: number) => {
-                            const retryDocView = getFirstDocView(targetDoc);  // test again for the target view snce we presumably created the context above by focusing on it
-                            if (retryDocView) {   // we found the target in the context
+                            const retryDocView = getFirstDocView(resolvedTarget);  // test again for the target view snce we presumably created the context above by focusing on it
+                            if (retryDocView) {   // we found the target in the context. 
                                 Doc.linkFollowHighlight(retryDocView.rootDoc);
-                                retryDocView.props.focus(targetDoc, {
+                                retryDocView.focus(targetDoc, {
                                     willZoom, afterFocus: (didFocus: boolean) =>
                                         new Promise<ViewAdjustment>(res => {
                                             !noSelect && focusAndFinish(didFocus);
                                             res(ViewAdjustment.doNothing);
                                         })
                                 }); // focus on the target in the context
-                            } else if (delay > 1500) {
+                            } else if (delay > 1000) {
                                 // we didn't find the target, so it must have moved out of the context.  Go back to just creating it.
                                 if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.rootDoc);
                                 if (targetDoc.layout) { // there will no layout for a TEXTANCHOR type document
                                     createViewFunc(Doc.BrushDoc(targetDoc), finished); //  create a new view of the target
                                 }
                             } else {
-                                setTimeout(() => findView(delay + 250), 250);
+                                setTimeout(() => findView(delay + 200), 200);
                             }
                         };
                         findView(0);
@@ -270,7 +271,6 @@ export class DocumentManager {
             }
         }
     }
-
 }
 export function DocFocusOrOpen(doc: any, collectionDoc?: Doc) {
     const cv = collectionDoc && DocumentManager.Instance.getDocumentView(collectionDoc);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 88000ecbd..9d738180b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -460,7 +460,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         // after  a timeout, the right _componentView should have been created, so call it to update its view spec values
         setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
         const focusSpeed = this._componentView?.scrollFocus?.(anchor, !options?.instant || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
-        const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing;
+        const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing;
         this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
             ...options, afterFocus: (didFocus: boolean) =>
                 new Promise<ViewAdjustment>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : ViewAdjustment.doNothing), focusSpeed ?? 0))
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 41e2fc266..756d6c2d6 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -813,6 +813,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         };
 
         let start = 0;
+        this._didScroll = false; // assume we don't need to scroll.  if we do, this will get set to true in handleScrollToSelextion when we dispatch the setSelection below
         if (this._editorView && textAnchorId) {
             const editor = this._editorView;
             const ret = findAnchorFrag(editor.state.doc.content, editor);
@@ -832,7 +833,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }
         }
 
-        return this._focusSpeed;
+        return this._didScroll ? this._focusSpeed : undefined; // if we actually scrolled, then return some focusSpeed
     }
 
     // if the scroll height has changed and we're in autoHeight mode, then we need to update the textHeight component of the doc.
@@ -1128,6 +1129,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }
         });
     }
+    _didScroll = false;
     setupEditor(config: any, fieldKey: string) {
         const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null) || StrCast(this.dataDoc[this.props.fieldKey]);
         const rtfField = Cast((!curText && this.layoutDoc[this.props.fieldKey]) || this.dataDoc[fieldKey], RichTextField);
@@ -1142,7 +1144,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     const scrollRef = self._scrollRef.current;
                     const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined;
                     const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
-                    if ((topOff || botOff) && scrollRef) {
+                    if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
                         const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
                         const scrollPos = scrollRef.scrollTop + shift * self.props.ScreenToLocalTransform().Scale;
                         if (this._focusSpeed !== undefined) {
@@ -1150,6 +1152,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         } else {
                             scrollRef.scrollTo({ top: scrollPos });
                         }
+                        this._didScroll = true;
                     }
                     return true;
                 },
-- 
cgit v1.2.3-70-g09d2


From 778432158e5f0bcdcb2e68ff6872a5c9335ef3af Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 28 Apr 2022 10:49:32 -0400
Subject: made sharing menu a developer feature.  made link relationships
 support non-symmetry

---
 src/client/util/LinkManager.ts                            |  9 ++++++---
 src/client/views/PropertiesView.tsx                       |  2 +-
 src/client/views/nodes/DocumentLinksButton.tsx            | 11 +++++++++--
 src/client/views/nodes/LinkDocPreview.tsx                 |  2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 10 ++++++----
 5 files changed, 23 insertions(+), 11 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 0f3da0548..7ba7a1d4c 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -29,6 +29,7 @@ export class LinkManager {
     public static currentLink: Opt<Doc>;
     public static get Instance() { return LinkManager._instance; }
     public static addLinkDB = (linkDb: any) => LinkManager.userLinkDBs.push(linkDb);
+    public static AutoKeywords = "keywords:Usages";
     static links: Doc[] = [];
     constructor() {
         LinkManager._instance = this;
@@ -149,9 +150,11 @@ export class LinkManager {
     public getRelatedGroupedLinks(anchor: Doc): Map<string, Array<Doc>> {
         const anchorGroups = new Map<string, Array<Doc>>();
         this.relatedLinker(anchor).forEach(link => {
-            if (!link.linkRelationship || link?.linkRelationship !== "-ungrouped-") {
-                const group = anchorGroups.get(StrCast(link.linkRelationship));
-                anchorGroups.set(StrCast(link.linkRelationship), group ? [...group, link] : [link]);
+            if (link.linkRelationship && link.linkRelationship !== "-ungrouped-") {
+                const relation = StrCast(link.linkRelationship);
+                const anchorRelation = relation.indexOf(":") ? relation.split(":")[Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), anchor) ? 0 : 1] : relation;
+                const group = anchorGroups.get(anchorRelation);
+                anchorGroups.set(anchorRelation, group ? [...group, link] : [link]);
             } else {
                 // if link is in no groups then put it in default group
                 const group = anchorGroups.get("*");
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index b63395c76..21c688421 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1362,7 +1362,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
                     {this.fieldsSubMenu}
 
-                    {this.sharingSubMenu}
+                    {isNovice ? null : this.sharingSubMenu}
 
                     {isNovice ? null : this.filtersSubMenu}
 
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 9b79db319..d2e6e8579 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -17,6 +17,7 @@ import { DocumentView } from "./DocumentView";
 import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
 import { TaskCompletionBox } from "./TaskCompletedBox";
 import React = require("react");
+import { DocumentType } from "../../documents/DocumentTypes";
 
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
@@ -158,7 +159,9 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         } else if (startLink !== endLink) {
             endLink = endLinkView?.docView?._componentView?.getAnchor?.() || endLink;
             startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.() || startLink;
-            const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "link", undefined, undefined, true);
+            const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink },
+                DocumentLinksButton.AnnotationId ? "hypothes.is annotation" :
+                    endLink.type === DocumentType.RTF ? "Commentary:Comments On" : "link", undefined, undefined, true);
 
             LinkManager.currentLink = linkDoc;
 
@@ -272,7 +275,11 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
         //render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu
         return (!Array.from(this.filteredLinks).length && !this.props.AlwaysOn) ? (null) :
-            <div className="documentLinksButton-wrapper" style={{ transform: this.props.InMenu ? undefined : `scale(${(this.props.ContentScaling?.() || 1) * this.props.View.screenToLocalTransform().Scale})` }} >
+            <div className="documentLinksButton-wrapper"
+                style={{
+                    transform: this.props.InMenu ? undefined :
+                        `scale(${(this.props.ContentScaling?.() || 1) * this.props.View.screenToLocalTransform().Scale})`
+                }} >
                 {
                     (this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) ||
                         (!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ?
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 28915674d..375434933 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -85,7 +85,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                 anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => {
                     if (anchor instanceof Doc && DocListCast(anchor.links).length) {
                         this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0];
-                        const automaticLink = this._linkDoc.linkRelationship === "automatic";
+                        const automaticLink = this._linkDoc.linkRelationship === LinkManager.AutoKeywords;
                         if (automaticLink) { // automatic links specify the target in the link info, not the source
                             const linkTarget = anchor;
                             this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 756d6c2d6..29117794e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -349,7 +349,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     autoLink = () => {
         if (this._editorView) {
             const newAutoLinks = new Set<Doc>();
-            const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === "automatic");
+            const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === LinkManager.AutoKeywords);
             const f = this._editorView.state.selection.from;
             const t = this._editorView.state.selection.to;
             var tr = this._editorView.state.tr as any;
@@ -382,14 +382,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
         const editorView = this._editorView;
         if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) {
-            const flattened1 = this.findInNode(editorView, editorView.state.doc, StrCast(target.title).replace(/^@/, ""));
+            const autoLinkTerm = StrCast(target.title).replace(/^@/, "");
+            const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm);
             var alink: Doc | undefined;
             flattened1.forEach((flat, i) => {
-                const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, StrCast(target.title).replace(/^@/, ""));
+                const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, autoLinkTerm);
                 this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
                 alink = alink ?? (DocListCast(this.Document.links).find(link =>
                     Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) &&
-                    Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!);
+                    Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target },
+                        LinkManager.AutoKeywords)!);
                 newAutoLinks.add(alink);
                 const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
                 const sel = flattened[i];
-- 
cgit v1.2.3-70-g09d2

