From bd9170eaa21a2fbccd0912f50d40cefaf0bbe47f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 6 Mar 2024 12:28:24 -0500
Subject: cleaned up some dropActionType strings.  fixed stackingview scrolling
 when always active.  fixed schemaView keys dropdown menu to be a popup. 
 cleaned up  display of key Finfo's

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 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 2d5239f06..4a1a51558 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -30,7 +30,7 @@ import { Docs, DocUtils } from '../../../documents/Documents';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { DictationManager } from '../../../util/DictationManager';
 import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager } from '../../../util/DragManager';
+import { DragManager, dropActionType } from '../../../util/DragManager';
 import { MakeTemplate } from '../../../util/DropConverter';
 import { LinkManager } from '../../../util/LinkManager';
 import { RTFMarkup } from '../../../util/RTFMarkup';
@@ -624,7 +624,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         docId: draggedDoc[Id],
                         float: 'unset',
                     });
-                    if (!['embed', 'copy'].includes((dropAction ?? '') as any)) {
+                    if (![dropActionType.embed, dropActionType.copy].includes(dropAction ?? dropActionType.move)) {
                         added = dragData.removeDocument?.(draggedDoc) ? true : false;
                     } else {
                         added = true;
-- 
cgit v1.2.3-70-g09d2


From 7164179264d057986bfd5f303c61248f1c189406 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 8 Mar 2024 16:17:02 -0500
Subject: cleaned up MakeTemplate api and currentUserUtils templates for clicks
 etc.

---
 src/client/documents/Documents.ts                  |  9 ++++
 src/client/util/CurrentUserUtils.ts                | 35 ++++++++--------
 src/client/util/DropConverter.ts                   | 48 ++++++++++++++--------
 src/client/views/collections/TreeView.tsx          |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +-
 5 files changed, 60 insertions(+), 38 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 b7d8222b9..e6969d1f3 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1024,6 +1024,15 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.PRES), new List<Doc>(), options);
         }
 
+        /**
+         * Creates a Doc to edit a script and write the compiled script into the specified field.
+         * Typically, this would be used to create a template that can then be applied to some other Doc
+         * in order to customize a behavior, such as onClick.
+         * @param script
+         * @param options
+         * @param fieldKey the field that the compiled script is written into.
+         * @returns the Scripting Doc
+         */
         export function ScriptingDocument(script: Opt<ScriptField> | null, options: DocumentOptions = {}, fieldKey?: string) {
             return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script ? script : undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined });
         }
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index cfba003fc..564a46514 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -105,9 +105,12 @@ export class CurrentUserUtils {
             { opts: { title: "onCheckedClick"},      script: "console.log(heading, checked, containingTreeView)"},
         ];
         const reqdClickList = reqdTempOpts.map(opts => {
+            const title = opts.opts.title?.toString();
             const allOpts = {...reqdClickOpts, ...opts.opts};
-            const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
-            return DocUtils.AssignOpts(clickDoc, allOpts) ?? MakeTemplate(Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, {heading:Doc.name, checked:"boolean", containingTreeView:Doc.name}), allOpts), true, opts.opts.title?.toString());
+            const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === title): undefined;
+            const script = ScriptField.MakeScript(opts.script, {heading:Doc.name, checked:"boolean", containingTreeView:Doc.name});
+            const scriptDoc = Docs.Create.ScriptingDocument(script, allOpts, title)
+            return DocUtils.AssignOpts(clickDoc, allOpts) ?? MakeTemplate(scriptDoc);
         });
 
         const reqdOpts:DocumentOptions = {title: "click editor templates", _height:75, isSystem: true};
@@ -118,13 +121,13 @@ export class CurrentUserUtils {
     static setupNoteTemplates(doc: Doc, field="template_notes") {
         const tempNotes = DocCast(doc[field]);
         const reqdTempOpts:DocumentOptions[] = [
-            { noteType: "Postit", backgroundColor: "yellow", icon: "sticky-note"},
-            { noteType: "Idea", backgroundColor: "pink", icon: "lightbulb" },
-            { noteType: "Topic", backgroundColor: "lightblue", icon: "book-open" }];
+            { title: "Postit", backgroundColor: "yellow",    icon: "sticky-note"},
+            { title: "Idea",   backgroundColor: "pink",      icon: "lightbulb" },
+            { title: "Topic",  backgroundColor: "lightblue", icon: "book-open" }];
         const reqdNoteList = reqdTempOpts.map(opts => {
-            const reqdOpts = {...opts, isSystem:true, title: "text", width:200, layout_autoHeight: true, layout_fitWidth: true};
-            const noteType = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.noteType === opts.noteType): undefined;
-            return DocUtils.AssignOpts(noteType, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts), true, opts.noteType??"Note");
+            const reqdOpts = {...opts, isSystem:true, width:200, layout_autoHeight: true, layout_fitWidth: true};
+            const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.title === opts.title): undefined;
+            return DocUtils.AssignOpts(noteTemp, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts));
         });
 
         const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, isSystem: true };
@@ -149,16 +152,16 @@ export class CurrentUserUtils {
         const templateIconsDoc = DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts));
 
         const makeIconTemplate = (type: DocumentType | undefined, templateField: string, opts:DocumentOptions) => {
-            const iconFieldName = "icon" + (type ? "_" + type : "");
-            const curIcon = DocCast(templateIconsDoc[iconFieldName]);
+            const title = "icon" + (type ? "_" + type : "");
+            const curIcon = DocCast(templateIconsDoc[title]);
             let creator = labelBox;
             switch (opts.iconTemplate) {
                 case DocumentType.IMG : creator = imageBox; break;
                 case DocumentType.FONTICON: creator = fontBox; break;
             }
-            const allopts = {isSystem: true, onClickScriptDisable: "never",  ...opts};
+            const allopts = {isSystem: true, onClickScriptDisable: "never",  ...opts, title};
             return DocUtils.AssignScripts(  (curIcon?.iconTemplate === opts.iconTemplate ? 
-                      DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts, templateField), true, iconFieldName, templateField))),
+                      DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[title] = MakeTemplate(creator(allopts, templateField)))),
                 {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)", });
         };
         const labelBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.LabelDocument({
@@ -211,7 +214,7 @@ export class CurrentUserUtils {
         };
         const headerBtnHgt = 10;
         const headerTemplate = (opts:DocumentOptions) => {
-            const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "text", 
+            const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "Untitled Header", 
                 layout:
                 "<HTMLdiv transformOrigin='top left' width='{100/scale}%' height='{100/scale}%' transform='scale({scale})'>" +
                 `    <FormattedTextBox {...props} dontScale='true' fieldKey={'text'} height='calc(100% - ${headerBtnHgt}px - {this._headerHeight||0}px)'/>` +
@@ -224,7 +227,7 @@ export class CurrentUserUtils {
             // "    <FormattedTextBox {...props} fieldKey={'header'} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} pointerEvents='{this._headerPointerEvents||`none`}' fontSize='{this._headerFontSize}px' height='{this._headerHeight}px' background='{this._headerColor||this.target.mySharedDocs.userColor}' />" +
             // "    <FormattedTextBox {...props} fieldKey={'text'} position='absolute' top='{(this._headerHeight)*scale}px' height='calc({100/scale}% - {this._headerHeight}px)'/>" +
             // "</div>";
-            MakeTemplate(Doc.GetProto(header), true, "Untitled Header");
+            MakeTemplate(Doc.GetProto(header));
             return header; 
         }
         const slideView = (opts:DocumentOptions) => {
@@ -232,9 +235,9 @@ export class CurrentUserUtils {
                     [
                         Docs.Create.MulticolumnDocument([], { title: "hero", _height: 200,  isSystem: true }),
                         Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true,  _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) })
-                    ], opts);
+                    ], {...opts, title: "Untitled Slide View"});
 
-            MakeTemplate(Doc.GetProto(slide), true, "Untitled Slide View");
+            MakeTemplate(Doc.GetProto(slide));
             return slide; 
         }
         const plotlyApi = () => {
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index ba981145d..3df3e36c6 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -12,43 +12,55 @@ import { ButtonType, FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'
 import { DragManager } from './DragManager';
 import { ScriptingGlobals } from './ScriptingGlobals';
 
-export function MakeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = undefined, templateField: string = '') {
-    if (templateField) doc[DocData].title = templateField; /// the title determines which field is being templated
-    doc.isTemplateDoc = makeTemplate(doc, first, rename);
+/**
+ * Converts a Doc to a render template that can be applied to other Docs to customize how they render while
+ * still using the other Doc as the backing data store (ie, dataDoc). During rendering, if a layout Doc is provided
+ * with 'isTemplateDoc' set, then the layout Doc is treated as a template for the rendered Doc.  The template Doc is
+ * "expanded" to create an template instance for the rendered Doc.
+ *
+ *
+ * @param doc the doc to convert to a template
+ * @returns 'doc'
+ */
+export function MakeTemplate(doc: Doc) {
+    doc.isTemplateDoc = makeTemplate(doc, true);
     return doc;
 }
-//
-// converts 'doc' into a template that can be used to render other documents.
-// the title of doc is used to determine which field is being templated, so
-// passing a value for 'rename' allows the doc to be given a meangingful name
-// after it has been converted to
-function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = undefined): boolean {
+
+/**
+ * Recursively converts 'doc' into a template that can be used to render other documents.
+ *
+ * For recurive Docs in the template, their target fieldKey is defined by their title,
+ * not by whatever fieldKey they used in their layout.
+ * @param doc
+ * @param first whether this is the topmost root of the recursive template
+ * @returns whether a template was successfully created
+ */
+function makeTemplate(doc: Doc, first: boolean = true): boolean {
     const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
     if (layoutDoc.layout instanceof Doc) {
-        // its already a template
-        return true;
+        return true; // its already a template
     }
     const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)![0];
     const fieldKey = layout.replace("fieldKey={'", '').replace(/'}$/, '');
     const docs = DocListCast(layoutDoc[fieldKey]);
-    let any = false;
+    let isTemplate = false;
     docs.forEach(d => {
         if (!StrCast(d.title).startsWith('-')) {
-            any = Doc.MakeMetadataFieldTemplate(d, layoutDoc[DocData]) || any;
+            isTemplate = Doc.MakeMetadataFieldTemplate(d, layoutDoc[DocData]) || isTemplate;
         } else if (d.type === DocumentType.COL || d.data instanceof RichTextField) {
-            any = makeTemplate(d, false) || any;
+            isTemplate = makeTemplate(d, false) || isTemplate;
         }
     });
     if (first && !docs.length) {
         // bcz: feels hacky : if the root level document has items, it's not a field template
-        any = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData], true) || any;
+        isTemplate = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData], true) || isTemplate;
     } else if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) {
         if (!StrCast(layoutDoc.title).startsWith('-')) {
-            any = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData], true);
+            isTemplate = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData], true);
         }
     }
-    rename && (doc.title = rename);
-    return any;
+    return isTemplate;
 }
 export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
     data?.draggedDocuments.map((doc, i) => {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 285a789e6..0c0e49411 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -631,7 +631,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             }
             return (
                 <div>
-                    {!docs?.length || this._props.AddToMap /* hack to identify pres box trees */ ? null : (
+                    {!docs?.length || this.treeView.outlineMode || this._props.AddToMap /* hack to identify pres box trees */ ? null : (
                         <div className="treeView-sorting">
                             <IconButton
                                 color={sortings[sorting]?.color}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 4a1a51558..54e3e7b44 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -940,9 +940,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 description: 'Make Default Layout',
                 event: () => {
                     if (!this.layoutDoc.isTemplateDoc) {
-                        const title = StrCast(this.Document.title);
-                        this.Document.title = 'text';
-                        MakeTemplate(this.Document, true, title);
+                        MakeTemplate(this.Document);
                     }
                     Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.Document);
                     Doc.AddDocToList(Cast(Doc.UserDoc().template_notes, Doc, null), 'data', this.Document);
-- 
cgit v1.2.3-70-g09d2


From f1ed1cff137c06afc4d4db8a8778f7827404889b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 9 Mar 2024 19:05:30 -0500
Subject: fixed up default text that uses a template to process an initial
 carriage return properly.  fixed text with inherited templates to be able to
 show fields with a default dashField value from template that can be
 overidden on instance.

---
 src/client/documents/Documents.ts                  | 11 +++---
 src/client/views/MarqueeAnnotator.tsx              |  4 +--
 src/client/views/PreviewCursor.tsx                 |  2 +-
 src/client/views/collections/TreeView.tsx          |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 36 +++++++++++--------
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 .../collectionSchema/SchemaTableCell.tsx           |  2 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |  4 +--
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |  4 +--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 40 ++++++++++------------
 10 files changed, 55 insertions(+), 52 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 e6969d1f3..7a3b965fe 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -3,7 +3,7 @@ import { action, reaction, runInAction } from 'mobx';
 import { basename } from 'path';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc';
-import { Initializing } from '../../fields/DocSymbols';
+import { DocData, Initializing } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { HtmlField } from '../../fields/HtmlField';
 import { InkField, PointData } from '../../fields/InkField';
@@ -1981,10 +1981,8 @@ export namespace DocUtils {
         }
     }
 
-    export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, backgroundColor?: string) {
+    export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) {
         const tbox = Docs.Create.TextDocument('', {
-            _xMargin: noMargins ? 0 : undefined,
-            _yMargin: noMargins ? 0 : undefined,
             annotationOn,
             backgroundColor,
             _width: width || 200,
@@ -1997,11 +1995,14 @@ export namespace DocUtils {
             _layout_enableAltContentUI: BoolCast(Doc.UserDoc().defaultToFlashcards),
             title,
         });
+
         const template = Doc.UserDoc().defaultTextLayout;
         if (template instanceof Doc) {
+            // if a default text template is specified
             tbox._width = NumCast(template._width);
             tbox.layout_fieldKey = 'layout_' + StrCast(template.title);
-            Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = template;
+            Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = template; // set the text doc's layout to render with the text template
+            tbox[DocData].proto = template; // and also set the text doc to inherit from the template (this allows the template to specify default field values)
         }
         return tbox;
     }
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index a4303c3aa..f59042b04 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -88,7 +88,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
 
         const textRegionAnno = Docs.Create.HTMLMarkerDocument([], {
             annotationOn: this.props.Document,
-            text: this.props.selectionText(),
+            text: this.props.selectionText() as any, // text want an RTFfield, but strings are acceptable, too.
             backgroundColor: 'transparent',
             presentation_duration: 2100,
             presentation_transition: 500,
@@ -198,7 +198,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
             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, undefined, annotationOn, 'yellow');
+                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
                 FormattedTextBox.SetSelectOnLoad(target);
                 return target;
             };
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 456b753b4..a94c18295 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -85,7 +85,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
                 } else {
                     FormattedTextBox.PasteOnLoad = e;
                     if (e.clipboardData.getData('dash/pdfAnchor')) e.preventDefault();
-                    UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined, undefined)), 'paste');
+                    UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined)), 'paste');
                 }
             }
             //pasting in images
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 0c0e49411..c6bbcb0a5 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -884,7 +884,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         // prettier-ignore
         switch (property.split(':')[0]) {
             case StyleProp.Opacity:         return this.treeView.outlineMode ? undefined : 1;
-            case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
+            case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : undefined;//StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
             case StyleProp.Highlighting:    if (this.treeView.outlineMode) return undefined;
             case StyleProp.BoxShadow:       return undefined;
             case StyleProp.DocContents:
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4eb946939..9500b918a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -30,7 +30,7 @@ import { SelectionManager } from '../../../util/SelectionManager';
 import { freeformScrollMode } from '../../../util/SettingsManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
-import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
 import { Timeline } from '../../animationtimeline/Timeline';
 import { ContextMenu } from '../../ContextMenu';
 import { GestureOverlay } from '../../GestureOverlay';
@@ -1146,23 +1146,29 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     isContentActive = () => this._props.isContentActive();
 
-    @undoBatch
+    /**
+     * Create a new text note of the same style as the one being typed into.
+     * If the text doc is be part of a larger templated doc, the new Doc will be a copy of the templated Doc
+     *
+     * @param fieldProps render props for the text doc being typed into
+     * @param below whether to place the new text Doc below or to the right of the one being typed into.
+     * @returns whether the new text doc was created and added successfully
+     */
+    createTextDocCopy = undoable((fieldProps: FieldViewProps, below: boolean) => {
+        const textDoc = DocCast(fieldProps.Document.rootDocument, fieldProps.Document);
+        const newDoc = Doc.MakeCopy(textDoc, true);
+        newDoc[DocData][Doc.LayoutFieldKey(newDoc, fieldProps.LayoutTemplateString)] = undefined; // the copy should not copy the text contents of it source, just the render style
+        newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10);
+        newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0);
+        FormattedTextBox.SetSelectOnLoad(newDoc);
+        FormattedTextBox.DontSelectInitialText = true;
+        return this.addDocument?.(newDoc);
+    }, 'copied text note');
+
     onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
         if ((e.metaKey || e.ctrlKey || e.altKey || fieldProps.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
             e.stopPropagation?.();
-            const below = !e.altKey && e.key !== 'Tab';
-            const layout_fieldKey = StrCast(fieldProps.fieldKey);
-            const newDoc = Doc.MakeCopy(fieldProps.Document, true);
-            const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)];
-            newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
-            if (below) newDoc.y = NumCast(fieldProps.Document.y) + NumCast(fieldProps.Document._height) + 10;
-            else newDoc.x = NumCast(fieldProps.Document.x) + NumCast(fieldProps.Document._width) + 10;
-            if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) {
-                newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey];
-            }
-            newDoc[DocData].text = undefined;
-            FormattedTextBox.SetSelectOnLoad(newDoc);
-            return this.addDocument?.(newDoc);
+            return this.createTextDocCopy(fieldProps, !e.altKey && e.key !== 'Tab');
         }
     };
     @computed get childPointerEvents() {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e22b83307..b913e05ad 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -178,7 +178,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views.length < 2) {
             FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this._props.childLayoutString ? e.key : '';
             FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('type new note');
-            this._props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this._props.xPadding === 0));
+            this._props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100));
             e.stopPropagation();
         }
     };
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 46867665d..ed1b519b4 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -134,7 +134,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
                         if (shiftDown && enterKey) {
                             this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value);
                         }
-                        const ret = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value);
+                        const ret = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined);
                         this._props.finishEdit?.();
                         return ret;
                     }, 'edit schema cell')}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index c185c66fc..927e6fad4 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -379,7 +379,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         });
 
         const targetCreator = (annotationOn: Doc | undefined) => {
-            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
+            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
             FormattedTextBox.SetSelectOnLoad(target);
             return target;
         };
@@ -592,7 +592,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         /// this should use SELECTED pushpin for lat/long  if there is a selection, otherwise CENTER
         const anchor = Docs.Create.ConfigDocument({
             title: 'MapAnchor:' + this.Document.title,
-            text: StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location',
+            text: (StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location') as any,
             config_latitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude),
             config_longitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude),
             config_map_zoom: NumCast(this.dataDoc.map_zoom),
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 8a5bd7ce6..3eb051dbf 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -232,7 +232,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
         });
 
         const targetCreator = (annotationOn: Doc | undefined) => {
-            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
+            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
             FormattedTextBox.SetSelectOnLoad(target);
             return target;
         };
@@ -466,7 +466,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
         /// this should use SELECTED pushpin for lat/long  if there is a selection, otherwise CENTER
         const anchor = Docs.Create.ConfigDocument({
             title: 'MapAnchor:' + this.Document.title,
-            text: StrCast(this.selectedPin?.map) || StrCast(this.Document.map) || 'map location',
+            text: (StrCast(this.selectedPin?.map) || StrCast(this.Document.map) || 'map location') as any,
             config_latitude: NumCast((existingPin ?? this.selectedPin)?.latitude ?? this.dataDoc.latitude),
             config_longitude: NumCast((existingPin ?? this.selectedPin)?.longitude ?? this.dataDoc.longitude),
             config_map_zoom: NumCast(this.dataDoc.map_zoom),
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 54e3e7b44..1ff7274f8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -67,7 +67,6 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
 import { RichTextRules } from './RichTextRules';
 import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
-import Select from 'react-select';
 // import * as applyDevTools from 'prosemirror-dev-tools';
 @observer
 export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -100,7 +99,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _recordingStart: number = 0;
     private _ignoreScroll = false;
-    private _lastText = '';
     private _hadDownFocus = false;
     private _focusSpeed: Opt<number>;
     private _keymap: any = undefined;
@@ -306,7 +304,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             e.preventDefault();
             e.stopPropagation();
             const targetCreator = (annotationOn?: Doc) => {
-                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn);
+                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn);
                 FormattedTextBox.SetSelectOnLoad(target);
                 return target;
             };
@@ -1457,39 +1455,37 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
 
         const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()));
-        if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
-            const selLoadChar = FormattedTextBox.SelectOnLoadChar;
+        const selLoadChar = FormattedTextBox.SelectOnLoadChar;
+        if (selectOnLoad) {
             FormattedTextBox.SelectOnLoad = undefined;
+            FormattedTextBox.SelectOnLoadChar = '';
+        }
+        if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
             this._props.select(false);
             if (selLoadChar) {
                 const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
                 const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
                 const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
                 const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
-                const tr = this._editorView.state.tr
-                    .setStoredMarks(storedMarks)
-                    .insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size)
-                    .setStoredMarks(storedMarks);
+                const tr1 = this._editorView.state.tr.setStoredMarks(storedMarks);
+                const tr2 = selLoadChar === 'Enter' ? tr1.insert(this._editorView.state.doc.content.size - 1, schema.nodes.paragraph.create()) : tr1.insertText(selLoadChar, this._editorView.state.doc.content.size - 1);
+                const tr = tr2.setStoredMarks(storedMarks);
+
                 this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
             } else if (curText && !FormattedTextBox.DontSelectInitialText) {
                 selectAll(this._editorView.state, this._editorView?.dispatch);
             }
         }
-        selectOnLoad && this._editorView!.focus();
+        if (selectOnLoad) {
+            FormattedTextBox.DontSelectInitialText = false;
+            this._editorView!.focus();
+        }
         if (this._props.isContentActive()) this.prepareForTyping();
-        if (this._editorView) {
-            const tr = this._editorView.state.tr;
-            const { from, to } = tr.selection;
-            // for some reason, the selection is sometimes lost in the sidebar view when prosemirror syncs the seledtion with the dom, so reset the selection after the document has ben fully instantiated.
-            if (FormattedTextBox.DontSelectInitialText) setTimeout(() => this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)))), 250);
-
-            if (FormattedTextBox.PasteOnLoad) {
-                const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
-                FormattedTextBox.PasteOnLoad = undefined;
-                pdfAnchorId && this.addPdfReference(pdfAnchorId);
-            }
+        if (this._editorView && FormattedTextBox.PasteOnLoad) {
+            const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
+            FormattedTextBox.PasteOnLoad = undefined;
+            pdfAnchorId && this.addPdfReference(pdfAnchorId);
         }
-        FormattedTextBox.DontSelectInitialText = false;
     }
 
     // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
-- 
cgit v1.2.3-70-g09d2

