From 19adcca1a89405a47da57fee64fe5fde4720c16a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 5 May 2025 14:05:32 -0400
Subject: got rid of dash _xPadding/_yPadding in favor of just using
 _xMargin/_yMargin.  cleaned up linearView field names

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 8 ++++----
 1 file changed, 4 insertions(+), 4 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 c51f6c38b..abf44b54d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1164,7 +1164,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     };
 
     @computed get tagsHeight() {
-        return this.DocumentView?.().showTags ? Math.max(0, 20 - Math.max(this._props.yPadding ?? 0, NumCast(this.layoutDoc._yMargin))) * this.ScreenToLocalBoxXf().Scale : 0;
+        return this.DocumentView?.().showTags ? Math.max(0, 20 - Math.max(this._props.yMargin ?? 0, NumCast(this.layoutDoc._yMargin))) * this.ScreenToLocalBoxXf().Scale : 0;
     }
 
     @computed get contentScaling() {
@@ -1880,7 +1880,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         }
     };
     tryUpdateScrollHeight = () => {
-        const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
+        const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yMargin || 0);
         const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
         if (this.EditorView && children && !SnappingManager.IsDragging) {
             const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0;
@@ -2112,8 +2112,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const rounded = StrCast(this.layoutDoc._layout_borderRounding) === '100%' ? '-rounded' : '';
         setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
 
-        const paddingX = Math.max(NumCast(this.layoutDoc._xMargin), 0, this._props.xPadding ?? 0, this._props.screenXPadding?.(this._props.DocumentView?.()) ?? 0);
-        const paddingY = Math.max(NumCast(this.layoutDoc._yMargin), 0, this._props.yPadding ?? 0); // prettier-ignore
+        const paddingX = Math.max(NumCast(this.layoutDoc._xMargin), 0, this._props.xMargin ?? 0, this._props.screenXPadding?.(this._props.DocumentView?.()) ?? 0);
+        const paddingY = Math.max(NumCast(this.layoutDoc._yMargin), 0, this._props.yMargin ?? 0); // prettier-ignore
         const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._header_height}px' >
         return this.isLabel ? (
             <LabelBox {...this._props} />
-- 
cgit v1.2.3-70-g09d2


From 74836a59c4af0a3c240c5e3435359935282ce049 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 5 May 2025 14:53:49 -0400
Subject: fixed keeping focus on labelBox when text butons are clicked.

---
 src/client/views/nodes/LabelBox.tsx                | 35 ++++++++++++----------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 11 ++++---
 src/client/views/nodes/trails/PresSlideBox.tsx     |  4 +--
 3 files changed, 28 insertions(+), 22 deletions(-)

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

diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index f901c32fc..4cbe01b82 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -27,7 +27,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
     private dropDisposer?: DragManager.DragDropDisposer;
     private _timeout: NodeJS.Timeout | undefined;
     private _divRef: HTMLDivElement | null = null;
-    private _reaction: IReactionDisposer | undefined;
+    private _disposers: { [key: string]: IReactionDisposer } = {};
 
     constructor(props: FieldViewProps) {
         super(props);
@@ -43,7 +43,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     componentDidMount() {
         this._props.setContentViewBox?.(this);
-        this._reaction = reaction(
+        this._disposers.active = reaction(
             () => this.Title,
             () => document.activeElement !== this._divRef && this._forceRerender++
         );
@@ -51,7 +51,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
     componentWillUnMount() {
         this._timeout && clearTimeout(this._timeout);
         this.setText(this._divRef?.innerText ?? '');
-        this._reaction?.();
+        Object.values(this._disposers).forEach(disposer => disposer());
     }
 
     @observable _forceRerender = 0;
@@ -171,20 +171,21 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
     render() {
         TraceMobx();
         const boxParams = this.fitTextToBox(undefined); // this causes mobx to trigger re-render when data changes
+        const [xmargin, ymargin] = [NumCast(this.layoutDoc._xMargin), NumCast(this.layoutDoc._uMargin)];
         return (
             <div className="labelBox-outerDiv" ref={this.createDropTarget} style={{ boxShadow: this.boxShadow }}>
                 <div
                     className="labelBox-mainButton"
                     style={{
                         backgroundColor: this.backgroundColor,
-                        color: StrCast(this.layoutDoc._text_fontColor, StrCast(this.layoutDoc._color)),
-                        fontFamily: StrCast(this.layoutDoc._text_fontFamily, StrCast(Doc.UserDoc().fontFamily)) || 'inherit',
+                        color: StrCast(this.layoutDoc[`${this.fieldKey}_fontColor`], StrCast(this.layoutDoc._color)),
+                        fontFamily: StrCast(this.layoutDoc[`${this.fieldKey}_fontFamily`], StrCast(Doc.UserDoc().fontFamily)) || 'inherit',
                         letterSpacing: StrCast(this.layoutDoc.letterSpacing),
-                        textTransform: StrCast(this.layoutDoc[this.fieldKey + '_transform']) as Property.TextTransform,
-                        paddingLeft: NumCast(this.layoutDoc._xMargin),
-                        paddingRight: NumCast(this.layoutDoc._xMargin),
-                        paddingTop: NumCast(this.layoutDoc._yMargin),
-                        paddingBottom: NumCast(this.layoutDoc._yMargin),
+                        textTransform: StrCast(this.layoutDoc[`${this.fieldKey}_transform`]) as Property.TextTransform,
+                        paddingLeft: xmargin,
+                        paddingRight: xmargin,
+                        paddingTop: ymargin,
+                        paddingBottom: ymargin,
                         width: this._props.PanelWidth(),
                         height: this._props.PanelHeight(),
                         whiteSpace: boxParams.multiLine ? 'pre-wrap' : 'pre',
@@ -192,8 +193,8 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     <div
                         key={this._forceRerender}
                         style={{
-                            width: this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin),
-                            height: this._props.PanelHeight() - 2 * NumCast(this.layoutDoc._yMargin),
+                            width: this._props.PanelWidth() - 2 * xmargin,
+                            height: this._props.PanelHeight() - 2 * ymargin,
                             outline: 'unset !important',
                         }}
                         onKeyDown={e => {
@@ -214,12 +215,14 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             this._divRef?.removeEventListener('focusout', this.keepFocus);
                             this._divRef?.addEventListener('focusout', this.keepFocus);
                         }}
-                        onBlur={() => {
+                        onBlur={e => {
                             this._divRef?.removeEventListener('focusout', this.keepFocus);
                             this.setText(this._divRef?.innerText ?? '');
-                            RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
-                            FormattedTextBox.LiveTextUndo?.end();
-                            FormattedTextBox.LiveTextUndo = undefined;
+                            if (!FormattedTextBox.tryKeepingFocus(e.relatedTarget, () => this._divRef?.focus())) {
+                                RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
+                                FormattedTextBox.LiveTextUndo?.end();
+                                FormattedTextBox.LiveTextUndo = undefined;
+                            }
                         }}
                         dangerouslySetInnerHTML={{
                             __html: `<span class="textFitted textFitAlignVert" style="display: inline-block; text-align: center; font-size: 100px; height: 0px;">${this.Title?.startsWith('#') ? '' : (this.Title ?? '')}</span>`,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index abf44b54d..824ac97da 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1750,19 +1750,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
      * When a text box loses focus, it might be because a text button was clicked (eg, bold, italics) or color picker.
      * In these cases, force focus back onto the text box.
      * @param target
+     * @returns true if focus was kept on the text box, false otherwise
      */
-    tryKeepingFocus = (target: Element | null) => {
+    public static tryKeepingFocus(target: Element | null, refocusFunc?: () => void) {
         for (let newFocusEle = target instanceof HTMLElement ? target : null; newFocusEle; newFocusEle = newFocusEle?.parentElement) {
             // bcz: HACK!! test if parent of new focused element is a UI button (should be more specific than testing className)
             if (['fonticonbox', 'antimodeMenu-cont', 'popup-container'].includes(newFocusEle?.className ?? '')) {
-                return this.EditorView?.focus(); // keep focus on text box
+                refocusFunc?.(); // keep focus on text box
+                return true;
             }
         }
-    };
+        return false;
+    }
 
     @action
     onBlur = (e: React.FocusEvent) => {
-        this.tryKeepingFocus(e.relatedTarget);
+        FormattedTextBox.tryKeepingFocus(e.relatedTarget, () => this.EditorView?.focus());
         if (this.ProseRef?.children[0] !== e.nativeEvent.target) return;
         if (!(this.EditorView?.state.selection instanceof NodeSelection) || this.EditorView.state.selection.node.type !== this.EditorView.state.schema.nodes.footnote) {
             const stordMarks = this.EditorView?.state.storedMarks?.slice();
diff --git a/src/client/views/nodes/trails/PresSlideBox.tsx b/src/client/views/nodes/trails/PresSlideBox.tsx
index 3440b29dd..55a655c7a 100644
--- a/src/client/views/nodes/trails/PresSlideBox.tsx
+++ b/src/client/views/nodes/trails/PresSlideBox.tsx
@@ -559,8 +559,8 @@ export class PresSlideBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 style={{
                     backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent',
                     opacity: this._dragging ? 0.3 : 1,
-                    paddingLeft: NumCast(this.layoutDoc._xMargin, this._props._xMargin),
-                    paddingRight: NumCast(this.layoutDoc._xMargin, this._props._xMargin),
+                    paddingLeft: NumCast(this.layoutDoc._xMargin, this._props.xMargin),
+                    paddingRight: NumCast(this.layoutDoc._xMargin, this._props.xMargin),
                     paddingTop: NumCast(this.layoutDoc._yPadding, this._props.yMargin),
                     paddingBottom: NumCast(this.layoutDoc._yPadding, this._props.yMargin),
                 }}
-- 
cgit v1.2.3-70-g09d2


From 299398cb7e21259de3bf7597995840b84e7e0590 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 9 May 2025 23:01:36 -0400
Subject: added user templates to template_user global field. fixed schemas to
 not erase field values when setting a new column name. fixed templates to
 render properly when switching by fixing contentsRef to be observable. fixed
 templates with fields in stacks to autoHeight update correctly by .  fixed
 images in templates to set their nativeDim after rendering.  fixed text views
 of descriptions to auto-updated by setting the modification date of the
 description field.

---
 src/client/documents/DocUtils.ts                   | 20 +++++++++----------
 src/client/util/DropConverter.ts                   |  4 +++-
 .../CollectionStackingViewFieldColumn.tsx          |  6 ++++--
 src/client/views/collections/CollectionSubView.tsx | 13 ++++++++++--
 .../collectionSchema/CollectionSchemaView.tsx      |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  6 +++---
 src/client/views/nodes/ImageBox.tsx                |  5 +++--
 .../views/nodes/formattedText/FormattedTextBox.tsx |  3 ++-
 src/fields/Doc.ts                                  | 23 +++++++++++++++++++---
 src/server/SharedMediaTypes.ts                     |  3 +++
 10 files changed, 60 insertions(+), 25 deletions(-)

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

diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index 9135899d7..36e03daed 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -642,8 +642,15 @@ export namespace DocUtils {
         return dd;
     }
 
-    export function assignImageInfo(result: Upload.FileInformation, protoIn: Doc) {
+    export function assignUploadInfo(result: Upload.FileInformation, protoIn: Doc) {
         const proto = protoIn;
+
+        if (Upload.isTextInformation(result)) {
+            proto.text = result.rawText;
+        }
+        if (Upload.isVideoInformation(result)) {
+            proto.data_duration = result.duration;
+        }
         if (Upload.isImageInformation(result)) {
             const maxNativeDim = Math.max(result.nativeHeight, result.nativeWidth);
             const exifRotation = StrCast(result.exifData?.data?.Orientation).toLowerCase();
@@ -677,15 +684,8 @@ export namespace DocUtils {
         const pathname = result.accessPaths.agnostic.client;
         const doc = await DocUtils.DocumentFromType(type, pathname, full, overwriteDoc);
         if (doc) {
-            const proto = Doc.GetProto(doc);
-            proto.text = result.rawText;
-            !(result instanceof Error) && DocUtils.assignImageInfo(result, proto);
-            if (Upload.isVideoInformation(result)) {
-                proto.data_duration = result.duration;
-            }
-            if (overwriteDoc) {
-                Doc.removeCurrentlyLoading(overwriteDoc);
-            }
+            DocUtils.assignUploadInfo(result, Doc.GetProto(doc));
+            overwriteDoc && Doc.removeCurrentlyLoading(overwriteDoc);
             generatedDocuments.push(doc);
         }
         return doc;
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index b6b111930..1d4779d8a 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -41,7 +41,7 @@ function makeTemplate(doc: Doc, first: boolean = true): boolean {
     if (first && !docs.length) {
         // bcz: feels hacky : if the root level document has items, it's not a field template
         isTemplate = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData], true) || isTemplate;
-    } else if (docData instanceof RichTextField || docData instanceof ImageField) {
+    } else if (docData instanceof RichTextField || docData instanceof ImageField || (docData === undefined && doc.type === DocumentType.IMG)) {
         if (!StrCast(layoutDoc.title).startsWith('-')) {
             isTemplate = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData], true);
         }
@@ -86,6 +86,8 @@ export function makeUserTemplateButtonOrImage(doc: Doc, image?: string) {
     dbox.dragFactory = layoutDoc;
     dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined;
     dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory)');
+    const userTemplatesDoc = DocCast(Doc.UserDoc().template_user);
+    userTemplatesDoc && Doc.AddDocToList(userTemplatesDoc, 'data', layoutDoc);
     return dbox;
 }
 
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 994669734..345f60e75 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -71,6 +71,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
     }
 
     _ele: HTMLElement | null = null;
+    _eleMasonrySingle = React.createRef<HTMLDivElement>();
 
     protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) => {
         const dragData = de.complete.docDragData;
@@ -92,13 +93,13 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
     createColumnDropRef = (ele: HTMLDivElement | null) => {
         this.dropDisposer?.();
         if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Doc, this.onInternalPreDrop.bind(this));
-        else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
+        else if (this._eleMasonrySingle.current) this.props.refList.splice(this.props.refList.indexOf(this._eleMasonrySingle.current), 1);
         this._ele = ele;
     };
 
     @action
     componentDidMount() {
-        this._ele && this.props.refList.push(this._ele);
+        this._eleMasonrySingle.current && this.props.refList.push(this._eleMasonrySingle.current);
         this._disposers.collapser = reaction(
             () => this._props.headingObject?.collapsed,
             collapsed => { this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false; }, // prettier-ignore
@@ -363,6 +364,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
                         }}>
                         <div
                             key={`${heading}-stack`}
+                            ref={this._eleMasonrySingle}
                             className="collectionStackingView-masonrySingle"
                             style={{
                                 padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e79d0a76d..4ac755b9d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -9,7 +9,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, DateCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
+import { BoolCast, Cast, DateCast, DocCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
@@ -561,7 +561,16 @@ export function CollectionSubView<X>() {
                             }
                             const loading = Docs.Create.LoadingDocument(file, options);
                             Doc.addCurrentlyLoading(loading);
-                            DocUtils.uploadFileToDoc(file, {}, loading);
+                            DocUtils.uploadFileToDoc(file, {}, loading).then(d => {
+                                if (d && d?.type === DocumentType.IMG) {
+                                    const imgTemplate = DocListCast(DocCast(Doc.UserDoc().template_user)?.data).find(d => d.title === 'shower');
+                                    if (imgTemplate) {
+                                        d.layout_fieldKey = 'layout_shower';
+                                        d.layout_shower = imgTemplate;
+                                    }
+                                }
+                            });
+
                             return loading;
                         })
                     ))
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index c06391f35..6442385c0 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -359,7 +359,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     @action
     addNewKey = (key: string, defaultVal: FieldType | undefined) => {
         this.childDocs.forEach(doc => {
-            doc[DocData][key] = defaultVal;
+            if (doc[DocData][key] === undefined) doc[DocData][key] = defaultVal;
         });
     };
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bdb97d7bb..f9c21451e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -740,7 +740,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
     }
     widgetOverlayFunc = () => (this.widgetDecorations ? this.widgetOverlay : null);
     viewingAiEditor = () => (this._props.showAIEditor && this._componentView?.componentAIView?.() !== undefined ? this.aiEditor : null);
-    _contentsRef = React.createRef<DocumentContentsView>();
+    @observable _contentsRef: DocumentContentsView | undefined = undefined;
     @computed get viewBoxContents() {
         TraceMobx();
         const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
@@ -756,7 +756,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                     }}>
                     <DocumentContentsView
                         {...this._props}
-                        ref={this._contentsRef}
+                        ref={action((r: DocumentContentsView) => (this._contentsRef = r))}
                         layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')}
                         pointerEvents={this.contentPointerEvents}
                         setContentViewBox={this.setContentView}
@@ -1168,7 +1168,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
      * @returns boolean whether sub-component Doc is in synch with the layoutDoc that this view thinks its rendering
      */
     IsInvalid = (renderDoc?: Doc): boolean => {
-        const docContents = this._docViewInternal?._contentsRef.current;
+        const docContents = this._docViewInternal?._contentsRef;
         return !(
             (!renderDoc ||
                 (docContents?.layoutDoc === renderDoc && //
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index b648f2adb..f7ad5c7e2 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -152,7 +152,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this._disposers.path = reaction(
             () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width), height: this.layoutDoc._height }),
             ({ nativeSize, width, height }) => {
-                if (!this.layoutDoc._layout_nativeDimEditable || !height) {
+                if (!this.layoutDoc._layout_nativeDimEditable || !height || this.layoutDoc.layout_resetNativeDim) {
+                    this.layoutDoc.layout_resetNativeDim = undefined; // template images need to reset their dimensions when they are rendered with content.  afterwards, remove this flag.
                     this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth;
                 }
             },
@@ -1048,7 +1049,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     alert('Error uploading files - possibly due to unsupported file types');
                 } else {
                     this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client);
-                    !(result instanceof Error) && DocUtils.assignImageInfo(result, this.dataDoc);
+                    !(result instanceof Error) && DocUtils.assignUploadInfo(result, this.dataDoc);
                 }
                 disposer();
             } else {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 824ac97da..57720baae 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1224,13 +1224,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             incomingValue => {
                 if (this.EditorView && this.ApplyingChange !== this.fieldKey) {
                     if (incomingValue?.data) {
-                        const updatedState = JSON.parse(incomingValue.data.Data);
+                        const updatedState = JSON.parse(incomingValue.data.Data.replace(/\n/g, ''));
                         if (JSON.stringify(this.EditorView.state.toJSON()) !== JSON.stringify(updatedState)) {
                             this.EditorView.updateState(EditorState.fromJSON(this.config, updatedState));
                             this.tryUpdateScrollHeight();
                         }
                     } else if (this.EditorView.state.doc.textContent !== (incomingValue?.str ?? '')) {
                         selectAll(this.EditorView.state, tx => this.EditorView?.dispatch(tx.insertText(incomingValue?.str ?? '')));
+                        this.tryUpdateScrollHeight();
                     }
                 }
             },
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ba94f0504..990b6606f 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -25,6 +25,7 @@ import { ComputedField, ScriptField } from './ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, ImageCastWithSuffix, NumCast, RTFCast, StrCast, ToConstructor, toList } from './Types';
 import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util';
 import { gptImageLabel } from '../client/apis/gpt/GPT';
+import { DateField } from './DateField';
 
 export let ObjGetRefField: (id: string, force?: boolean) => Promise<Doc | undefined>;
 export let ObjGetRefFields: (ids: string[]) => Promise<Map<string, Doc | undefined>>;
@@ -1109,6 +1110,10 @@ export namespace Doc {
             Cast(templateFieldValue, listSpec(Doc), [])?.map(d => d instanceof Doc && MakeMetadataFieldTemplate(d, templateDoc));
             Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue);
         }
+        if (templateField.type === DocumentType.IMG) {
+            // bcz: should be a better way .. but, if the image is a template, then we can't expect to know the aspect ratio. When the image is replaced by data and rendered, we want to recomputed the native dimensions.
+            templateField[DocData].layout_resetNativeDim = true;
+        }
         // get the layout string that the template uses to specify its layout
         const templateFieldLayoutString = StrCast(Doc.LayoutField(templateField[DocLayout]));
 
@@ -1184,13 +1189,15 @@ export namespace Doc {
         return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
     }
     export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) {
-        return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeWidth'], useWidth ? NumCast(doc._width) : 0));
+        // if this is a field template, then don't use the doc's nativeWidth/height
+        return !doc ? 0 : NumCast(doc.isTemplateForField ? undefined : doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeWidth'], !doc.isTemplateForField && useWidth ? NumCast(doc._width) : 0));
     }
     export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
         if (!doc) return 0;
         const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) / NumCast(doc._width)) * NumCast(doc._height); // divide before multiply to avoid floating point errrorin case nativewidth = width
         const dheight = NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeHeight'], useHeight ? NumCast(doc._height) : 0);
-        return NumCast(doc._nativeHeight, nheight || dheight);
+        // if this is a field template, then don't use the doc's nativeWidth/height
+        return NumCast(doc.isTemplateForField ? undefined : doc._nativeHeight, nheight || dheight);
     }
 
     export function OutpaintingWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) {
@@ -1504,7 +1511,13 @@ export namespace Doc {
                 case DocumentType.RTF: return RTFCast(tdoc[Doc.LayoutDataKey(tdoc)])?.Text ?? StrCast(tdoc[Doc.LayoutDataKey(tdoc)]);
                 default:               return StrCast(tdoc.title).startsWith("Untitled") ? "" : StrCast(tdoc.title);
         }}); // prettier-ignore
-        return docText(doc).then(text => (doc['$' + Doc.LayoutDataKey(doc) + '_description'] = text));
+        return docText(doc).then(
+            action(text => {
+                // set the time when the date changes.  This also allows a live textbox view to react to the update, otherwise, it wouldn't take effect until the next time the view is rerendered.
+                doc['$' + Doc.LayoutDataKey(doc) + '_description_modificationDate'] = new DateField();
+                return (doc['$' + Doc.LayoutDataKey(doc) + '_description'] = text);
+            })
+        );
     }
 
     // prettier-ignore
@@ -1820,3 +1833,7 @@ ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, ran
 ScriptingGlobals.add(function toJavascriptString(str: string) {
     return Field.toJavascriptString(str as FieldType);
 });
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function getDescription(doc: Doc) {
+    return Doc.getDescription(doc);
+});
diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts
index 9aa4b120f..43a9ce963 100644
--- a/src/server/SharedMediaTypes.ts
+++ b/src/server/SharedMediaTypes.ts
@@ -19,6 +19,9 @@ export enum AudioAnnoState {
 }
 
 export namespace Upload {
+    export function isTextInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.ImageInformation {
+        return 'rawText' in uploadResponse;
+    }
     export function isImageInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.ImageInformation {
         return 'nativeWidth' in uploadResponse;
     }
-- 
cgit v1.2.3-70-g09d2


From 87484331f805f583c98226c5a8c6b1ec5fc88925 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 13 May 2025 13:37:54 -0400
Subject: fixed setting up link anchors for video to be added to Doc. fixed
 imageTemplate to work with templates in tabs.  fixed link following from link
 menu.  cleaned up getView with sidebars to open them when needed properly..

---
 src/client/documents/DocUtils.ts                   |  2 +-
 src/client/util/DocumentManager.ts                 |  2 +-
 src/client/views/MarqueeAnnotator.tsx              |  3 +-
 src/client/views/SidebarAnnos.tsx                  | 29 +++++++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  7 +--
 src/client/views/linking/LinkMenuItem.tsx          | 18 +++----
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/MapBox/MapBox.tsx           | 59 +++++++---------------
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   | 51 ++++++++++---------
 src/client/views/nodes/PDFBox.tsx                  | 17 +++----
 src/client/views/nodes/VideoBox.tsx                | 17 ++++---
 src/client/views/nodes/WebBox.tsx                  | 26 +++++-----
 .../views/nodes/formattedText/FormattedTextBox.tsx | 17 +++----
 13 files changed, 124 insertions(+), 126 deletions(-)

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

diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index 36e03daed..41a61b154 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -724,7 +724,7 @@ export namespace DocUtils {
                                 _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200,
                                 _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35,
                                 _layout_autoHeight: true,
-                                backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor),
+                                backgroundColor: backgroundColor ?? StrCast(Doc.UserDoc().textBackgroundColor),
                                 borderColor: Doc.UserDoc().borderColor as string,
                                 borderWidth: Doc.UserDoc().borderWidth as number,
                                 text_centered: BoolCast(Doc.UserDoc().textCentered),
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index fc8f22cf3..422613968 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -250,7 +250,7 @@ export class DocumentManager {
         const options = optionsIn;
         Doc.MyRecentlyClosed && Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
         const docContextPath = DocumentManager.GetContextPath(targetDoc, true);
-        if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false;
+        if (docContextPath.some(doc => doc !== targetDoc && doc.hidden)) options.toggleTarget = false;
         let activatedTab = false;
         if (DocumentView.activateTabView(docContextPath[0])) {
             options.toggleTarget = false;
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index b2e42652d..d354dd2c0 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -194,14 +194,13 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
         AnchorMenu.Instance.StartDrag = action((e: PointerEvent, ele: HTMLElement) => {
             e.preventDefault();
             e.stopPropagation();
-            const sourceAnchorCreator = () => this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color
-
             const targetCreator = (annotationOn: Doc | undefined) => {
                 const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
                 target.layout_fitWidth = true;
                 DocumentView.SetSelectOnLoad(target);
                 return target;
             };
+            const sourceAnchorCreator = () => this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                 dragComplete: dragEv => {
                     if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) {
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 573c28ccf..71b479a22 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ClientUtils, returnFalse, returnOne, returnZero } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
-import { Doc, DocListCast, Field, FieldResult, FieldType, StrListCast } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldResult, FieldType, Opt, StrListCast } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
@@ -19,6 +19,7 @@ import { StyleProp } from './StyleProp';
 import { CollectionStackingView } from './collections/CollectionStackingView';
 import { DocumentView } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
+import { FocusViewOptions } from './nodes/FocusViewOptions';
 
 interface ExtraProps {
     fieldKey: string;
@@ -149,15 +150,33 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
     };
     makeDocUnfiltered = (doc: Doc) => {
         if (DocListCast(this._props.Doc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
-            if (this.childFilters()) {
+            if (this.childFilters().length) {
                 // if any child filters exist, get rid of them
                 this._props.layoutDoc._childFilters = new List<string>();
+                return true;
             }
-            return true;
         }
         return false;
     };
 
+    public static getView(sidebar: SidebarAnnos | null, sidebarShown: boolean, toggleSidebar: () => void, doc: Doc, options: FocusViewOptions) {
+        if (!sidebarShown) {
+            options.didMove = true;
+            toggleSidebar();
+        }
+        options.didMove = sidebar?.makeDocUnfiltered(doc) || options.didMove;
+
+        if (!doc.hidden) {
+            if (!options.didMove && options.toggleTarget) {
+                options.toggleTarget = false;
+                options.didMove = doc.hidden = true;
+            }
+        } else {
+            options.didMove = !(doc.hidden = false);
+        }
+        return new Promise<Opt<DocumentView>>(res => DocumentView.addViewRenderedCb(doc, res));
+    }
+
     get sidebarKey() {
         return this._props.fieldKey + '_sidebar';
     }
@@ -181,8 +200,8 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
     layout_showTitle = () => 'title';
     setHeightCallback = (height: number) => this._props.setHeight?.(height + this.filtersHeight());
     sortByLinkAnchorY = (a: Doc, b: Doc) => {
-        const ay = Doc.Links(a).length && DocCast(Doc.Links(a)[0].link_anchor_1).y;
-        const by = Doc.Links(b).length && DocCast(Doc.Links(b)[0].link_anchor_1).y;
+        const ay = Doc.Links(a).length && DocCast(Doc.Links(a)[0].link_anchor_1)?.y;
+        const by = Doc.Links(b).length && DocCast(Doc.Links(b)[0].link_anchor_1)?.y;
         return NumCast(ay) - NumCast(by);
     };
     render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bb3c59eae..77615c650 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1738,10 +1738,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         );
 
         if (addAsAnnotation) {
-            if (Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
-                Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), [])?.push(anchor);
+            const fieldKey = this._props.isAnnotationOverlay ? this._props.fieldKey : this._props.fieldKey + '_annotations';
+            if (Cast(this.dataDoc[fieldKey], listSpec(Doc), null) !== undefined) {
+                Cast(this.dataDoc[fieldKey], listSpec(Doc), [])?.push(anchor);
             } else {
-                this.dataDoc[this._props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
+                this.dataDoc[fieldKey] = new List<Doc>([anchor]);
             }
         }
         return anchor;
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index c984a7053..6c1ad568d 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -105,11 +105,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                     LinkManager.Instance.currentLinkAnchor = LinkManager.Instance.currentLink ? this.sourceAnchor : undefined;
 
                     if ((SnappingManager.PropertiesWidth ?? 0) < 100) {
-                        setTimeout(
-                            action(() => {
-                                SnappingManager.SetPropertiesWidth(250);
-                            })
-                        );
+                        setTimeout(action(() => SnappingManager.SetPropertiesWidth(250)));
                     }
                 }
             })
@@ -136,14 +132,14 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                     this._props.itemHandler?.(this._props.linkDoc);
                 } else {
                     const focusDoc =
-                        Cast(this._props.linkDoc.link_anchor_1, Doc, null)?.annotationOn === this._props.sourceDoc
-                            ? Cast(this._props.linkDoc.link_anchor_1, Doc, null)
-                            : Cast(this._props.linkDoc.link_anchor_2, Doc, null)?.annotationOn === this._props.sourceDoc
-                              ? Cast(this._props.linkDoc.link_anchor_12, Doc, null)
-                              : undefined;
+                        DocCast(this._props.linkDoc.link_anchor_1)?.annotationOn === this._props.sourceDoc
+                            ? DocCast(this._props.linkDoc.link_anchor_1)
+                      : DocCast(this._props.linkDoc.link_anchor_2)?.annotationOn === this._props.sourceDoc
+                            ? DocCast(this._props.linkDoc.link_anchor_2)
+                      : undefined; // prettier-ignore
 
                     if (focusDoc) this._props.docView._props.focus(focusDoc, { instant: true });
-                    DocumentView.FollowLink(this._props.linkDoc, this._props.sourceDoc, false);
+                    DocumentView.FollowLink(this._props.linkDoc, focusDoc ?? this._props.sourceDoc, false);
                 }
             }
         );
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 05706fe6b..b0415ef18 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1412,7 +1412,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     }
     public static setDefaultImageTemplate(checkResult?: boolean) {
         if (checkResult) return Doc.UserDoc().defaultImageLayout;
-        const view = DocumentView.Selected()[0]?._props.renderDepth > 0 ? DocumentView.Selected()[0] : undefined;
+        const view = DocumentView.Selected()[0]?._props.renderDepth > 0 || DocumentView.Selected()[0]?.Document.isTemplateDoc ? DocumentView.Selected()[0] : undefined;
         undoable(() => {
             const tempDoc = DocumentView.getTemplate(view);
             Doc.UserDoc().defaultImageLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined;
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index a563b7c1b..a279ccc48 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -13,7 +13,7 @@ import { CirclePicker, ColorResult } from 'react-color';
 import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl/mapbox';
 import { ClientUtils, setupMoveUpEvents } from '../../../../ClientUtils';
 import { emptyFunction } from '../../../../Utils';
-import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, LinkedTo, StrListCast } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { DocCast, NumCast, StrCast, toList } from '../../../../fields/Types';
@@ -111,12 +111,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     // this list contains pushpins and configs
     @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } // prettier-ignore
-    @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } // prettier-ignore
+    @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.sidebarKey]); } // prettier-ignore
     @computed get allPushpins() { return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); } // prettier-ignore
     @computed get allRoutes() { return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE); } // prettier-ignore
     @computed get SidebarShown() { return !!this.layoutDoc._layout_showSidebar; } // prettier-ignore
     @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } // prettier-ignore
-    @computed get SidebarKey() {  return this.fieldKey + '_sidebar'; } // prettier-ignore
+    @computed get sidebarKey() {  return this.fieldKey + '_sidebar'; } // prettier-ignore
     @computed get sidebarColor() {
         return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
     }
@@ -260,7 +260,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     removeMapDocument = (doc: Doc | Doc[], annotationKey?: string) => {
         this.allAnnotations
-            .filter(anno => toList(doc).includes(DocCast(anno.mapPin)))
+            .filter(anno => toList(doc).includes(DocCast(anno.mapPin)!))
             .forEach(anno => {
                 anno.mapPin = undefined;
             });
@@ -339,27 +339,19 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     startAnchorDrag = (e: PointerEvent, ele: HTMLElement) => {
         e.preventDefault();
         e.stopPropagation();
-
-        const sourceAnchorCreator = action(() => {
-            const note = this.getAnchor(true);
-            if (note && this._selectedPinOrRoute) {
-                note.latitude = this._selectedPinOrRoute.latitude;
-                note.longitude = this._selectedPinOrRoute.longitude;
-                note.map = this._selectedPinOrRoute.map;
-            }
-            return note as Doc;
-        });
-
         const targetCreator = (annotationOn: Doc | undefined) => {
             const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
+            target.layout_fitWidth = true;
             DocumentView.SetSelectOnLoad(target);
             return target;
         };
+
+        const sourceAnchorCreator = () => this.getAnchor(true);
         const docView = this.DocumentView?.();
         docView &&
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                 dragComplete: dragEv => {
-                    if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) {
+                    if (!dragEv.aborted && dragEv.annoDragData?.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) {
                         dragEv.annoDragData.linkSourceDoc.followLinkToggle = dragEv.annoDragData.dropDocument.annotationOn === this.Document;
                         dragEv.annoDragData.linkSourceDoc.followLinkZoom = false;
                     }
@@ -368,17 +360,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     createNoteAnnotation = () => {
-        const createFunc = undoable(
-            action(() => {
-                const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]);
-                if (note && this._selectedPinOrRoute) {
-                    note.latitude = this._selectedPinOrRoute.latitude;
-                    note.longitude = this._selectedPinOrRoute.longitude;
-                    note.map = this._selectedPinOrRoute.map;
-                }
-            }),
-            'create note annotation'
-        );
+        const createFunc = undoable(() => this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]), 'create note annotation');
         if (!this.layoutDoc.layout_showSidebar) {
             this.toggleSidebar();
             setTimeout(createFunc);
@@ -428,14 +410,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         }
     };
 
-    getView = (doc: Doc, options: FocusViewOptions) => {
-        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
-            this.toggleSidebar();
-            options.didMove = true;
+    getView = async (doc: Doc, options: FocusViewOptions) => {
+        if (DocListCast(this.dataDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
+            SidebarAnnos.getView(this._sidebarRef.current, this.SidebarShown, this.toggleSidebar, doc, options);
         }
-        return new Promise<Opt<DocumentView>>(res => {
-            DocumentView.addViewRenderedCb(doc, dv => res(dv));
-        });
+        return undefined;
     };
 
     /*
@@ -476,13 +455,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     @action
     deleteSelectedPinOrRoute = undoable(() => {
-        if (this._selectedPinOrRoute) {
+        const selPin = DocCast(this._selectedPinOrRoute);
+        if (selPin) {
             // Removes filter
-            Doc.setDocFilter(this.Document, 'latitude', NumCast(this._selectedPinOrRoute.latitude), 'remove');
-            Doc.setDocFilter(this.Document, 'longitude', NumCast(this._selectedPinOrRoute.longitude), 'remove');
-            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this._selectedPinOrRoute))}`, 'remove');
+            Doc.setDocFilter(this.Document, 'latitude', NumCast(selPin.latitude), 'remove');
+            Doc.setDocFilter(this.Document, 'longitude', NumCast(selPin.longitude), 'remove');
+            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(selPin)}`, 'remove');
 
-            this.removePushpinOrRoute(this._selectedPinOrRoute);
+            this.removePushpinOrRoute(selPin);
         }
         MapAnchorMenu.Instance.fadeOut(true);
         document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -1299,7 +1279,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     <SidebarAnnos
                         ref={this._sidebarRef}
                         {...this._props}
-                        fieldKey={this.fieldKey}
                         Doc={this.Document}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index e0efab576..0beefcb67 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -145,7 +145,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
     removeMapDocument = (docsIn: Doc | Doc[], annotationKey?: string) => {
         const docs = toList(docsIn);
         this.allAnnotations
-            .filter(anno => docs.includes(DocCast(anno.mapPin)))
+            .filter(anno => docs.includes(DocCast(anno.mapPin)!))
             .forEach(anno => {
                 anno.mapPin = undefined;
             });
@@ -224,6 +224,12 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
     startAnchorDrag = (e: PointerEvent, ele: HTMLElement) => {
         e.preventDefault();
         e.stopPropagation();
+        const targetCreator = (annotationOn: Doc | undefined) => {
+            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
+            target.layout_fitWidth = true;
+            DocumentView.SetSelectOnLoad(target);
+            return target;
+        };
 
         const sourceAnchorCreator = action(() => {
             const note = this.getAnchor(true);
@@ -235,11 +241,6 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
             return note as Doc;
         });
 
-        const targetCreator = (annotationOn: Doc | undefined) => {
-            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
-            DocumentView.SetSelectOnLoad(target);
-            return target;
-        };
         const docView = this.DocumentView?.();
         docView &&
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
@@ -362,22 +363,22 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
 
     @action
     deselectPin = () => {
-        if (this.selectedPin) {
+        const selPin = DocCast(this.selectedPin);
+        if (selPin) {
             // Removes filter
-            Doc.setDocFilter(this.Document, 'latitude', NumCast(this.selectedPin.latitude), 'remove');
-            Doc.setDocFilter(this.Document, 'longitude', NumCast(this.selectedPin.longitude), 'remove');
-            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+            Doc.setDocFilter(this.Document, 'latitude', NumCast(selPin.latitude), 'remove');
+            Doc.setDocFilter(this.Document, 'longitude', NumCast(selPin.longitude), 'remove');
+            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(selPin)}`, 'remove');
 
-            const temp = this.selectedPin;
             if (!this._unmounting) {
-                this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp));
+                this._bingMap.current.entities.remove(this.map_docToPinMap.get(selPin));
             }
-            const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(temp.latitude, temp.longitude));
-            this.MicrosoftMaps.Events.addHandler(newpin, 'click', () => this.pushpinClicked(temp as Doc));
+            const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(selPin.latitude, selPin.longitude));
+            this.MicrosoftMaps.Events.addHandler(newpin, 'click', () => this.pushpinClicked(selPin));
             if (!this._unmounting) {
                 this._bingMap.current.entities.push(newpin);
             }
-            this.map_docToPinMap.set(temp, newpin);
+            this.map_docToPinMap.set(selPin, newpin);
             this.selectedPin = undefined;
             this.bingSearchBarContents = this.Document.map;
         }
@@ -388,9 +389,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
             this.toggleSidebar();
             options.didMove = true;
         }
-        return new Promise<Opt<DocumentView>>(res => {
-            DocumentView.addViewRenderedCb(doc, dv => res(dv));
-        });
+        return new Promise<Opt<DocumentView>>(res => DocumentView.addViewRenderedCb(doc, res));
     };
     /*
      * Pushpin onclick
@@ -535,13 +534,14 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
 
     @action
     deleteSelectedPin = undoable(() => {
-        if (this.selectedPin) {
+        const selPin = this.selectedPin;
+        if (selPin) {
             // Removes filter
-            Doc.setDocFilter(this.Document, 'latitude', NumCast(this.selectedPin.latitude), 'remove');
-            Doc.setDocFilter(this.Document, 'longitude', NumCast(this.selectedPin.longitude), 'remove');
-            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+            Doc.setDocFilter(this.Document, 'latitude', NumCast(selPin.latitude), 'remove');
+            Doc.setDocFilter(this.Document, 'longitude', NumCast(selPin.longitude), 'remove');
+            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(selPin)}`, 'remove');
 
-            this.removePushpin(this.selectedPin);
+            this.removePushpin(selPin);
         }
         MapAnchorMenu.Instance.fadeOut(true);
         document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -638,7 +638,10 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
         this._disposers.highlight = reaction(
             () => this.allAnnotations.map(doc => doc[Highlight]),
             () => {
-                const allConfigPins = this.allAnnotations.map(doc => ({ doc, pushpin: DocCast(doc.mapPin) })).filter(pair => pair.pushpin);
+                const allConfigPins = this.allAnnotations
+                    .map(doc => ({ doc, pushpin: DocCast(doc.mapPin) }))
+                    .filter(pair => pair.pushpin)
+                    .map(pair => ({ doc: pair.doc, pushpin: pair.pushpin! }));
                 allConfigPins.forEach(({ pushpin }) => {
                     if (!pushpin[Highlight] && this.map_pinHighlighted.get(pushpin)) {
                         this.recolorPin(pushpin);
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 55e6d5596..e4050a3c3 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -10,7 +10,7 @@ import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { ComputedField } from '../../../fields/ScriptField';
-import { Cast, FieldValue, NumCast, StrCast, toList } from '../../../fields/Types';
+import { Cast, DocCast, FieldValue, NumCast, StrCast, toList } from '../../../fields/Types';
 import { ImageField, PdfField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction } from '../../../Utils';
@@ -27,7 +27,6 @@ import { Colors } from '../global/globalEnums';
 import { PDFViewer } from '../pdf/PDFViewer';
 import { PinDocView, PinProps } from '../PinFuncs';
 import { SidebarAnnos } from '../SidebarAnnos';
-import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { FocusViewOptions } from './FocusViewOptions';
 import { ImageBox } from './ImageBox';
@@ -56,6 +55,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy> = undefined;
     @observable private _pageControls = false;
 
+    @computed get sidebarKey() {
+        return this.fieldKey + '_sidebar';
+    }
     @computed get pdfUrl() {
         return Cast(this.dataDoc[this._props.fieldKey], PdfField);
     }
@@ -232,14 +234,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.y, NumCast(anchor.config_scrollTop)), options);
     };
 
-    getView = (doc: Doc, options: FocusViewOptions) => {
-        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
-            options.didMove = true;
-            this.toggleSidebar(false);
+    getView = async (doc: Doc, options: FocusViewOptions) => {
+        if (DocListCast(this.dataDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
+            SidebarAnnos.getView(this._sidebarRef.current, this.SidebarShown, () => this.toggleSidebar(false), doc, options);
         }
-        return new Promise<Opt<DocumentView>>(res => {
-            DocumentView.addViewRenderedCb(doc, dv => res(dv));
-        });
+        return undefined;
     };
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index fa099178c..b3cb0e1db 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -338,12 +338,17 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
         const timecode = Cast(this.layoutDoc._layout_currentTimecode, 'number', null);
         const marquee = AnchorMenu.Instance.GetAnchor?.(undefined, addAsAnnotation);
+        const docAnchor = () =>
+            Docs.Create.ConfigDocument({
+                title: '#' + timecode,
+                _timecodeToShow: timecode,
+                annotationOn: this.Document,
+            });
         if (!addAsAnnotation && marquee) marquee.backgroundColor = 'transparent';
-        const anchor =
-            addAsAnnotation && marquee
-                ? CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode || undefined, undefined, marquee, addAsAnnotation) || this.Document
-                : Docs.Create.ConfigDocument({ title: '#' + timecode, _timecodeToShow: timecode, annotationOn: this.Document });
+        const visibleAnchor = () => addAsAnnotation && marquee && (CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode || undefined, undefined, marquee, addAsAnnotation) || this.Document);
+        const anchor = visibleAnchor() || docAnchor();
         PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true, pannable: true } }, this.Document);
+        addAsAnnotation && this.addDocument(anchor);
         return anchor;
     };
 
@@ -376,9 +381,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             }
             return this._stackedTimeline.getView(doc, options);
         }
-        return new Promise<Opt<DocumentView>>(res => {
-            DocumentView.addViewRenderedCb(doc, dv => res(dv));
-        });
+        return new Promise<Opt<DocumentView>>(res => DocumentView.addViewRenderedCb(doc, res));
     };
 
     // extracts video thumbnails and saves them as field of doc
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 838dbea9d..779ccab40 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -13,7 +13,7 @@ import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
 import { RefField } from '../../../fields/RefField';
 import { listSpec } from '../../../fields/Schema';
-import { Cast, NumCast, StrCast, toList, WebCast } from '../../../fields/Types';
+import { Cast, DocCast, NumCast, StrCast, toList, WebCast } from '../../../fields/Types';
 import { ImageField, WebField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, stringHash } from '../../../Utils';
@@ -104,6 +104,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @computed get webField() {
         return Cast(this.Document[this._props.fieldKey], WebField)?.url;
     }
+    @computed get sidebarKey() {
+        return this.fieldKey + '_sidebar';
+    }
 
     constructor(props: FieldViewProps) {
         super(props);
@@ -308,18 +311,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     @action
-    getView = (doc: Doc /* , options: FocusViewOptions */) => {
-        if (Doc.AreProtosEqual(doc, this.Document))
-            return new Promise<Opt<DocumentView>>(res => {
-                res(this.DocumentView?.());
-            });
+    getView = async (doc: Doc, options: FocusViewOptions) => {
+        if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this.DocumentView?.()));
+
         if (this.Document.layout_fieldKey === 'layout_icon') this.DocumentView?.().iconify();
         const webUrl = WebCast(doc.config_data)?.url;
         if (this._url && webUrl && webUrl.href !== this._url) this.setData(webUrl.href);
-        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false);
-        return new Promise<Opt<DocumentView>>(res => {
-            DocumentView.addViewRenderedCb(doc, dv => res(dv));
-        });
+
+        if (DocListCast(this.dataDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
+            return SidebarAnnos.getView(this._sidebarRef.current, this.SidebarShown, () => this.toggleSidebar(false), doc, options);
+        }
+        return undefined;
     };
 
     sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
@@ -454,7 +456,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     iframeDown = (e: PointerEvent) => {
         this._textAnnotationCreator = undefined;
         const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
-        if (sel?.empty && !(e.target as any).textContent)
+        if (sel?.empty && !(e.target as HTMLElement).textContent)
             sel.empty(); // Chrome
         else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
 
@@ -565,7 +567,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 'click',
                 undoable(
                     action((e: MouseEvent) => {
-                        let eleHref = (e.target as any)?.outerHTML?.split('"="')[1]?.split('"')[0];
+                        let eleHref = (e.target as HTMLElement)?.outerHTML?.split('"="')[1]?.split('"')[0];
                         for (let ele = e.target as HTMLElement | Element | null; ele; ele = ele.parentElement) {
                             if ('href' in ele) {
                                 eleHref = (typeof ele.href === 'string' ? ele.href : eleHref) || (ele.parentElement && 'href' in ele.parentElement ? (ele.parentElement.href as string) : eleHref);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 57720baae..bab97f681 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -270,13 +270,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             e.preventDefault();
             e.stopPropagation();
             const targetCreator = (annotationOn?: Doc) => {
-                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn);
+                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
+                target.layout_fitWidth = true;
                 DocumentView.SetSelectOnLoad(target);
                 return target;
             };
 
+            const sourceAnchorCreator = () => this.getAnchor(true);
+
             const docView = this.DocumentView?.();
-            docView && DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
+            docView && DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY);
         });
 
         AnchorMenu.Instance.AddDrawingAnnotation = (drawing: Doc) => {
@@ -1070,15 +1073,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     getView = (doc: Doc, options: FocusViewOptions) => {
         if (DocListCast(this.dataDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
-            if (!this.SidebarShown) {
-                this.toggleSidebar(false);
-                options.didMove = true;
-            }
-            setTimeout(() => this._sidebarRef?.current?.makeDocUnfiltered(doc));
+            return SidebarAnnos.getView(this._sidebarRef.current, this.SidebarShown, () => this.toggleSidebar(false), doc, options);
         }
-        return new Promise<Opt<DocumentView>>(res => {
-            DocumentView.addViewRenderedCb(doc, dv => res(dv));
-        });
+        return new Promise<Opt<DocumentView>>(res => DocumentView.addViewRenderedCb(doc, res));
     };
     focus = (textAnchor: Doc, options: FocusViewOptions) => {
         const focusSpeed = options.zoomTime ?? 500;
-- 
cgit v1.2.3-70-g09d2

