From 88caa55967b1dbf670b156dd08efc4f559067af7 Mon Sep 17 00:00:00 2001
From: srichman333 <sarah_n_richman@brown.edu>
Date: Mon, 19 Feb 2024 16:19:05 -0500
Subject: ai (updates from sophie, then added dataviz summary)

---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 34 +++++++++++++++++-----
 1 file changed, 27 insertions(+), 7 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 f2c4c6c8f..8d5b0218d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -8,7 +8,7 @@ import { history } from 'prosemirror-history';
 import { inputRules } from 'prosemirror-inputrules';
 import { keymap } from 'prosemirror-keymap';
 import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
-import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
+import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transaction } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
 import * as React from 'react';
 import { BsMarkdownFill } from 'react-icons/bs';
@@ -972,9 +972,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
 
     animateRes = (resIndex: number, newText: string) => {
+        if (!this._editorView) return;
         if (resIndex < newText.length) {
-            const marks = this._editorView?.state.storedMarks ?? [];
-            this._editorView?.dispatch(this._editorView.state.tr.setStoredMarks(marks).insertText(newText[resIndex]).setStoredMarks(marks));
+            const marks = this._editorView.state.storedMarks ?? [];
+            this._editorView.dispatch(this._editorView.state.tr.insertText(newText[resIndex]).setStoredMarks(marks));
             setTimeout(() => {
                 this.animateRes(resIndex + 1, newText);
             }, 20);
@@ -983,15 +984,34 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
     askGPT = action(async () => {
         try {
-            let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
+         //   let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
+
+            let data = {
+                "Weight":[69,69,65,72,67,73,70,75,74,65,73,70,74,68,74,65,69,75,67,74,66,70,69,68,67,71,67,70,74,71,73,66,72,73,68,69,69,74,70,73],
+                "Size":[4.39,4.21,4.09,5.85,4.7,5.68,5.56,5.11,5.36,4.27,5.79,5.47,5.53,4.47,5.22,4.48,4.66,5.25,4.18,5.5,4.13,4.83,4.61,4.08,4.25,5.35,4.01,4.22,5.25,5.26,5.78,4.68,5.72,5.17,4.83,4.11,4.76,5.48,5.59,5.03],
+                "Class":["orange","orange","orange","apple","orange","apple","apple","apple","apple","orange","apple","apple","apple","orange","apple","orange","orange","apple","orange","apple","orange","orange","orange","orange","orange","apple","orange","orange","apple","apple","apple","orange","apple","apple","orange","orange","orange","apple","apple","apple"]
+                }
+            let input = JSON.stringify(data);
+
+            
+            let res = await gptAPICall(input, GPTCallType.DATA);
+            console.log('GPT Result: ', res);
+            console.log(res);
             if (!res) {
-                console.error('GPT call failed');
                 this.animateRes(0, 'Something went wrong.');
             } else {
-                this.animateRes(0, res);
+                if (!this._editorView) return;
+                // No animation
+                // this._editorView.dispatch(this._editorView.state.tr.insertText(res));
+
+                // Animation
+                // Set selection at end
+                const sel = Selection.atEnd(this._editorView.state.doc);
+                this._editorView.dispatch(this._editorView.state.tr.setSelection(sel));
+                this.animateRes(0, '\n\n' + res);
             }
         } catch (err) {
-            console.error('GPT call failed');
+            console.error(err);
             this.animateRes(0, 'Something went wrong.');
         }
     });
-- 
cgit v1.2.3-70-g09d2


From b7de39e877c6fc9d3604dbc363999ac91ce3fa62 Mon Sep 17 00:00:00 2001
From: srichman333 <sarah_n_richman@brown.edu>
Date: Tue, 27 Feb 2024 16:52:00 -0500
Subject: textbox ask ai fix

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 14 +-------------
 1 file changed, 1 insertion(+), 13 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 a1e4bfacc..e5362762e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -984,19 +984,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
     askGPT = action(async () => {
         try {
-         //   let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
-
-            let data = {
-                "Weight":[69,69,65,72,67,73,70,75,74,65,73,70,74,68,74,65,69,75,67,74,66,70,69,68,67,71,67,70,74,71,73,66,72,73,68,69,69,74,70,73],
-                "Size":[4.39,4.21,4.09,5.85,4.7,5.68,5.56,5.11,5.36,4.27,5.79,5.47,5.53,4.47,5.22,4.48,4.66,5.25,4.18,5.5,4.13,4.83,4.61,4.08,4.25,5.35,4.01,4.22,5.25,5.26,5.78,4.68,5.72,5.17,4.83,4.11,4.76,5.48,5.59,5.03],
-                "Class":["orange","orange","orange","apple","orange","apple","apple","apple","apple","orange","apple","apple","apple","orange","apple","orange","orange","apple","orange","apple","orange","orange","orange","orange","orange","apple","orange","orange","apple","apple","apple","orange","apple","apple","orange","orange","orange","apple","apple","apple"]
-                }
-            let input = JSON.stringify(data);
-
-            
-            let res = await gptAPICall(input, GPTCallType.DATA);
-            console.log('GPT Result: ', res);
-            console.log(res);
+            let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
             if (!res) {
                 this.animateRes(0, 'Something went wrong.');
             } else {
-- 
cgit v1.2.3-70-g09d2


From 108e27f0d08a8d6d3b3fcd456d6cca999c077e8b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 31 Mar 2024 23:09:53 -0400
Subject: fixed scrolling to targets in text views.  fixed referencing fields
 on another doc from text box.  fixed '@name' technique for publishing
 documents to also remove documents and work anywhere a title is set.

---
 src/client/documents/Documents.ts                  |  5 ++++
 src/client/util/Scripting.ts                       |  2 +-
 src/client/views/DocumentButtonBar.tsx             |  2 +-
 src/client/views/DocumentDecorations.tsx           |  6 ----
 .../views/collections/CollectionDockingView.tsx    | 25 ++++++++++++++--
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |  8 ++---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 34 ++++++++++------------
 .../views/nodes/formattedText/RichTextRules.ts     | 13 +++------
 src/client/views/nodes/formattedText/nodes_rts.ts  |  1 -
 src/fields/Doc.ts                                  | 13 +++++++--
 src/fields/util.ts                                 | 16 +++++++++-
 12 files changed, 78 insertions(+), 49 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 6a0d45543..eb15c332f 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1516,6 +1516,9 @@ export namespace DocUtils {
             return linkDoc;
         });
 
+        const a = source.layout_unrendered ? 'link_anchor_1.annotationOn' : 'link_anchor_1';
+        const b = target.layout_unrendered ? 'link_anchor_2.annotationOn' : 'link_anchor_2';
+
         return makeLink(
             Docs.Create.LinkDocument(
                 source,
@@ -1529,6 +1532,8 @@ export namespace DocUtils {
                     link_displayLine: linkSettings.link_displayLine,
                     link_relationship: linkSettings.link_relationship,
                     link_description: linkSettings.link_description,
+                    x: ComputedField.MakeFunction(`(this.${a}.x+this.${b}.x)/2`) as any,
+                    y: ComputedField.MakeFunction(`(this.${a}.y+this.${b}.y)/2`) as any,
                     link_autoMoveAnchors: true,
                     _lockedPosition: true,
                     _layout_showCaption: '', // removed since they conflict with showing a link with a LinkBox (ie, line, not comparison box)
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 31222aa50..422e708bc 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -250,7 +250,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
     const outputText = host.readFile('file.js');
 
     const diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
-
+    if (script.startsWith('@')) options.typecheck = true; // need the compilation to fail so that the script will return itself as a string (instead of nothing)
     const result = Run(outputText, paramNames, diagnostics, script, options);
 
     if (options.globals) {
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index d65e0b406..15ce4c15f 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -416,7 +416,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                 )}
 
                 {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== doc ? <div className="documentButtonBar-button">{this.endLinkButton} </div> : null}
-                {Doc.noviceMode ? null : <div className="documentButtonBar-button">{this.templateButton}</div>}
+                <div className="documentButtonBar-button">{this.templateButton}</div>
                 {!SelectionManager.Views?.some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
                 <div className="documentButtonBar-button">{this.pinButton}</div>
                 <div className="documentButtonBar-button">{this.recordButton}</div>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 951e0912c..9e469ed1f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -131,12 +131,6 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                     SelectionManager.Views.forEach(d => {
                         if (titleFieldKey === 'title') {
                             d.dataDoc.title_custom = !this._accumulatedTitle.startsWith('-');
-                            if (StrCast(d.Document.title).startsWith('@') && !this._accumulatedTitle.startsWith('@')) {
-                                Doc.RemFromMyPublished(d.Document);
-                            }
-                            if (!StrCast(d.Document.title).startsWith('@') && this._accumulatedTitle.startsWith('@')) {
-                                Doc.AddToMyPublished(d.Document);
-                            }
                         }
                         KeyValueBox.SetField(d.Document, titleFieldKey, this._accumulatedTitle);
                     }),
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 25bfdb588..b2897a9b7 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -3,14 +3,14 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
 import * as GoldenLayout from '../../../client/goldenLayout';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
 import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
-import { ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
-import { GetEffectiveAcl, inheritParentAcls } from '../../../fields/util';
+import { GetEffectiveAcl, inheritParentAcls, SetPropSetterCb } from '../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, emptyFunction, incrementTitleCopy } from '../../../Utils';
 import { DocServer } from '../../DocServer';
 import { Docs } from '../../documents/Documents';
@@ -32,6 +32,7 @@ import './CollectionDockingView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm';
 import { CollectionSubView } from './CollectionSubView';
 import { TabDocView } from './TabDocView';
+import { ComputedField } from '../../../fields/ScriptField';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
@@ -313,8 +314,26 @@ export class CollectionDockingView extends CollectionSubView() {
         }
     };
 
+    /**
+     * This publishes Docs having titles starting with '@' to Doc.myPublishedDocs
+     * Once published, any text that uses the 'title' in its body will automatically
+     * be linked to this published document.
+     * @param target
+     * @param title
+     */
+    titleChanged = (target: any, value: any) => {
+        const title = Field.toString(value);
+        if (title.startsWith('@') && !title.substring(1).match(/[\(\)\[\]@]/) && title.length > 1) {
+            const embedding = DocListCast(target.proto_embeddings).lastElement();
+            embedding && Doc.AddToMyPublished(embedding);
+        } else if (!title.startsWith('@')) {
+            DocListCast(target.proto_embeddings).forEach(doc => Doc.RemFromMyPublished(doc));
+        }
+    };
+
     componentDidMount: () => void = async () => {
         this._unmounting = false;
+        SetPropSetterCb('title', this.titleChanged); // this overrides any previously assigned callback for the property
         if (this._containerRef.current) {
             this._lightboxReactionDisposer = reaction(
                 () => LightboxView.LightboxDoc,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 6b3a56b0b..6eca91e9d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -499,7 +499,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             _layout_showSidebar: true,
             title: 'overview',
         });
-        const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
+        const portal = Docs.Create.FreeformDocument(selected, { title: 'summarized documents', x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
         DocUtils.MakeLink(summary, portal, { link_relationship: 'summary of:summarized by' });
 
         portal.hidden = true;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 22a0cbe5e..763939a68 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -87,7 +87,6 @@ export class DashFieldView {
                 hideValue={node.attrs.hideValue}
                 editable={node.attrs.editable}
                 nodeSelected={this.NodeSelected}
-                dataDoc={node.attrs.dataDoc}
                 tbox={tbox}
             />
         );
@@ -119,7 +118,6 @@ interface IDashFieldViewInternal {
     height: number;
     editable: boolean;
     nodeSelected: () => boolean;
-    dataDoc: boolean;
     node: any;
     getPos: any;
     unclickable: () => boolean;
@@ -139,7 +137,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
         makeObservable(this);
         this._fieldKey = this._props.fieldKey;
         this._textBoxDoc = this._props.tbox.Document;
-        const setDoc = action((doc: Doc) => (this._dashDoc = this._props.dataDoc ? doc[DocData] : doc));
+        const setDoc = action((doc: Doc) => (this._dashDoc = doc));
 
         if (this._props.docId) {
             DocServer.GetRefField(this._props.docId).then(dashDoc => dashDoc instanceof Doc && setDoc(dashDoc));
@@ -168,7 +166,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
             setTimeout(() => !this._props.tbox.ProseRef?.contains(document.activeElement) && this._props.tbox._props.onBlur?.());
         }
     });
-    selectedCell = (): [Doc, number] => [this._dashDoc!, 0];
+    selectedCell = (): [Doc, number] | undefined => (this._dashDoc ? [this._dashDoc, 0] : undefined);
     columnWidth = () => Math.min(this._props.tbox._props.PanelWidth(), Math.max(50, this._props.tbox._props.PanelWidth() - 100)); // try to leave room for the fieldKey
 
     // set the display of the field's value (checkbox for booleans, span of text for strings)
@@ -284,7 +282,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                 )}
                 {this._props.fieldKey.startsWith('#') || this._hideValue ? null : this.fieldValueContent}
                 {!this.values.length ? null : (
-                    <select className="dashFieldView-select" tabIndex={-1} defaultValue={Field.toKeyValueString(this._dashDoc!, this._fieldKey)} onChange={this.selectVal}>
+                    <select className="dashFieldView-select" tabIndex={-1} defaultValue={this._dashDoc && Field.toKeyValueString(this._dashDoc, this._fieldKey)} onChange={this.selectVal}>
                         <option value="-unset-">-unset-</option>
                         {this.values.map(val => (
                             <option value={val.value}>{val.label}</option>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2deecb6ad..80e56efe0 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -478,7 +478,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text);
         if (
             !this._props.dontRegisterView && // (this.Document.isTemplateForField === "text" || !this.Document.isTemplateForField) && // only update the title if the data document's data field is changing
-            (title.startsWith('-') || title.startsWith('@')) &&
+            title.startsWith('-') &&
             this._editorView &&
             !this.dataDoc.title_custom &&
             (Doc.LayoutFieldKey(this.Document) === this.fieldKey || this.fieldKey === 'text')
@@ -486,14 +486,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             let node = this._editorView.state.doc;
             while (node.firstChild && node.firstChild.type.name !== 'text') node = node.firstChild;
             const str = node.textContent;
-            const prefix = str.startsWith('@') ? '' : '-';
+            const prefix = '-';
 
             const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title));
             if (!(cfield instanceof ComputedField)) {
                 this.dataDoc.title = (prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '')).trim();
-                if (str.startsWith('@') && str.length > 1) {
-                    Doc.AddToMyPublished(this.Document);
-                }
             }
         }
     };
@@ -510,7 +507,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
         const editorView = this._editorView;
         if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) {
-            const autoLinkTerm = StrCast(target.title).replace(/^@/, '');
+            const autoLinkTerm = Field.toString(target.title as Field).replace(/^@/, '');
             var alink: Doc | undefined;
             this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
                 if (
@@ -1312,15 +1309,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         this._disposers.scroll = reaction(
             () => NumCast(this.layoutDoc._layout_scrollTop),
             pos => {
-                if (!this._ignoreScroll && this._scrollRef.current && !this._props.dontSelectOnLoad) {
+                if (!this._ignoreScroll && this.ProseRef && !this._props.dontSelectOnLoad) {
                     const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
                     const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
                     const durationSecStr = viewTrans.match(/([0-9.]*)s/);
                     const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
                     if (duration) {
-                        this._scrollStopper = smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0), 'ease', this._scrollStopper);
+                        this._scrollStopper = smoothScroll(duration, this.ProseRef, Math.abs(pos || 0), 'ease', this._scrollStopper);
                     } else {
-                        this._scrollRef.current.scrollTo({ top: pos });
+                        this.ProseRef.scrollTo({ top: pos });
                     }
                 }
             },
@@ -1418,7 +1415,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 handleScrollToSelection: editorView => {
                     const docPos = editorView.coordsAtPos(editorView.state.selection.to);
                     const viewRect = self._ref.current!.getBoundingClientRect();
-                    const scrollRef = self._scrollRef.current;
+                    const scrollRef = self.ProseRef;
                     const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined;
                     const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
                     if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
@@ -1750,20 +1747,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
         }
         FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
+
+        // this is the markdown for @<published name> document publishing to Doc.myPublishedDocs
+        const match = RTFCast(this.Document[this.fieldKey]).Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
+        if (match) {
+            this.dataDoc.title_custom = true;
+            this.dataDoc.title = match[1]; // this triggers the collectionDockingView to publish this Doc
+            this.EditorView?.dispatch(this.EditorView?.state.tr.deleteRange(0, match[1].length + 1));
+        }
+
         this.endUndoTypingBatch();
 
         FormattedTextBox.LiveTextUndo?.end();
         FormattedTextBox.LiveTextUndo = undefined;
 
         const state = this._editorView!.state;
-        if (StrCast(this.Document.title).startsWith('@') && !this.dataDoc.title_custom) {
-            UndoManager.RunInBatch(() => {
-                this.dataDoc.title_custom = true;
-                this.dataDoc.layout_showTitle = 'title';
-                const tr = this._editorView!.state.tr;
-                this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.Document.title).length + 2))).deleteSelection());
-            }, 'titler');
-        }
         // if the text box blurs and none of its contents are focused(), then pass the blur along
         setTimeout(() => !this.ProseRef?.contains(document.activeElement) && this._props.onBlur?.());
     };
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 5e53a019e..e8cf9e992 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -295,8 +295,8 @@ export class RichTextRules {
 
             // create a hyperlink to a titled document
             // @(<doctitle>)
-            new InputRule(new RegExp(/(^|\s)@\(([a-zA-Z_@\.\? \-0-9]+)\)/), (state, match, start, end) => {
-                const docTitle = match[2];
+            new InputRule(new RegExp(/@\(([a-zA-Z_@\.\? \-0-9]+)\)/), (state, match, start, end) => {
+                const docTitle = match[1];
                 const prefixLength = '@('.length;
                 if (docTitle) {
                     const linkToDoc = (target: Doc) => {
@@ -342,12 +342,7 @@ export class RichTextRules {
                     const assign = match[4] === ':' ? (match[4] = '') : match[4];
                     const value = match[5];
                     const dataDoc = value === undefined ? !fieldKey.startsWith('_') : !assign?.startsWith('=');
-                    const getTitledDoc = (docTitle: string) => {
-                        if (!DocServer.FindDocByTitle(docTitle)) {
-                            Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true }));
-                        }
-                        return DocServer.FindDocByTitle(docTitle);
-                    };
+                    const getTitledDoc = (docTitle: string) => DocServer.FindDocByTitle(docTitle);
                     // if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' )
                     if (value?.includes(',') && !value.startsWith('((')) {
                         const values = value.split(',');
@@ -359,7 +354,7 @@ export class RichTextRules {
                         if (fieldKey === this.TextBox.fieldKey) return this.TextBox.EditorView!.state.tr;
                     }
                     const target = docTitle ? getTitledDoc(docTitle) : undefined;
-                    const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false, hideValue: false, dataDoc });
+                    const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false, hideValue: false });
                     return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
                 },
                 { inCode: true }
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index e335044ea..cab3a6ef5 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -266,7 +266,6 @@ export const nodes: { [index: string]: NodeSpec } = {
             hideKey: { default: false },
             hideValue: { default: false },
             editable: { default: true },
-            dataDoc: { default: false },
         },
         leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as Field),
         group: 'inline',
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 200896e25..9973232bf 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -86,7 +86,8 @@ export namespace Field {
         // this is a bit hacky, but we treat '^@' references to a published document
         // as a kind of macro to include the content of those documents
         Doc.MyPublishedDocs.forEach(doc => {
-            const regex = new RegExp(`^\\^${doc.title}\\s`, 'm');
+            const regexMultilineFlag = 'm';
+            const regex = new RegExp(`^\\^${StrCast(doc.title).replace(/[\(\)]*/g, '')}\\s`, regexMultilineFlag); // need to remove characters that can cause the regular expression to be invalid
             const sections = (Cast(doc.text, RichTextField, null)?.Text ?? '').split('--DOCDATA--');
             if (script.match(regex)) {
                 script = script.replace(regex, sections[0]) + (sections.length > 1 ? sections[1] : '');
@@ -218,8 +219,14 @@ export class Doc extends RefField {
     public static IsInMyOverlay(doc: Doc)     { return Doc.MyOverlayDocs.includes(doc); } // prettier-ignore
     public static AddToMyOverlay(doc: Doc)    { Doc.ActiveDashboard?.myOverlayDocs ? Doc.AddDocToList(Doc.ActiveDashboard, 'myOverlayDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
     public static RemFromMyOverlay(doc: Doc)  { Doc.ActiveDashboard?.myOverlayDocs ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myOverlayDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
-    public static AddToMyPublished(doc: Doc)  { Doc.ActiveDashboard?.myPublishedDocs ? Doc.AddDocToList(Doc.ActiveDashboard, 'myPublishedDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
-    public static RemFromMyPublished(doc: Doc){ Doc.ActiveDashboard?.myPublishedDocs ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myPublishedDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+    public static AddToMyPublished(doc: Doc)  {
+        doc[DocData].title_custom = true;
+        doc[DocData].layout_showTitle = 'title'; 
+        Doc.ActiveDashboard?.myPublishedDocs ? Doc.AddDocToList(Doc.ActiveDashboard, 'myPublishedDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+    public static RemFromMyPublished(doc: Doc){ 
+        doc[DocData].title_custom = false;
+        doc[DocData].layout_showTitle = undefined;
+        Doc.ActiveDashboard?.myPublishedDocs ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myPublishedDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
     public static IsComicStyle(doc?: Doc)     { return doc && Doc.ActiveDashboard && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic' ; } // prettier-ignore
 
     constructor(id?: FieldId, forceSave?: boolean) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index c2ec3f13a..ad592391e 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,4 +1,4 @@
-import { $mobx, action, observable, runInAction, trace } from 'mobx';
+import { $mobx, action, observable, runInAction, trace, values } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { returnZero } from '../Utils';
 import { DocServer } from '../client/DocServer';
@@ -16,6 +16,7 @@ import { RichTextField } from './RichTextField';
 import { SchemaHeaderField } from './SchemaHeaderField';
 import { ComputedField } from './ScriptField';
 import { DocCast, ScriptCast, StrCast } from './Types';
+import { BaseException } from 'pdfjs-dist/types/src/shared/util';
 
 function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
@@ -56,6 +57,8 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
         delete curValue[FieldChanged];
     }
 
+    if (typeof prop === 'string' && _propSetterCB.has(prop)) _propSetterCB.get(prop)!(target[SelfProxy], value);
+
     const effectiveAcl = GetEffectiveAcl(target);
 
     const writeMode = DocServer.getFieldWriteMode(prop as string);
@@ -282,6 +285,17 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
     dataDocChanged && updateCachedAcls(dataDoc);
 }
 
+export var _propSetterCB = new Map<string, ((target: any, value: any) => void) | undefined>();
+/**
+ * sets a callback function to be called whenever a value is assigned to the specified field key.
+ * For example, this is used to "publish" documents with titles that start with '@'
+ * @param prop
+ * @param setter
+ */
+export function SetPropSetterCb(prop: string, setter: ((target: any, value: any) => void) | undefined) {
+    _propSetterCB.set(prop, setter);
+}
+
 //
 // target should be either a Doc or ListImpl.  receiver should be a Proxy<Doc> Or List.
 //
-- 
cgit v1.2.3-70-g09d2


From e4854bdb361f1d9e0ae85ee46327e2919117fc48 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 1 Apr 2024 11:14:24 -0400
Subject: revert back to old style of forced text scrolling - doesn't work for
 pres trails.

---
 src/Utils.ts                                               |  5 +++--
 src/client/views/nodes/formattedText/FormattedTextBox.scss |  1 -
 src/client/views/nodes/formattedText/FormattedTextBox.tsx  | 14 +++++++-------
 3 files changed, 10 insertions(+), 10 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 38325a463..21c91278f 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -653,13 +653,14 @@ export function smoothScroll(duration: number, element: HTMLElement | HTMLElemen
     const animateScroll = () => {
         const currentDate = new Date().getTime();
         const currentTime = currentDate - startDate;
-        elements.map((element, i) => (element.scrollTop = easeFunc(transition, currentTime, starts[i], to - starts[i], duration)));
+        const setScrollTop = (element: HTMLElement, value: number) => (element.scrollTop = value);
+        elements.forEach((element, i) => currentTime && setScrollTop(element, easeFunc(transition, Math.min(currentTime, duration), starts[i], to - starts[i], duration)));
 
         if (!_stop) {
             if (currentTime < duration) {
                 requestAnimationFrame(animateScroll);
             } else {
-                elements.forEach(element => (element.scrollTop = to));
+                elements.forEach(element => setScrollTop(element, to));
             }
         }
     };
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 00d890860..03ff0436b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -156,7 +156,6 @@ audiotag:hover {
 .formattedTextBox-inner,
 .formattedTextBox-inner-minimal {
     height: 100%;
-    overflow: auto;
     white-space: pre-wrap;
     .ProseMirror:hover {
         background: rgba(200, 200, 200, 0.2);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 80e56efe0..a78d8363e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -91,7 +91,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     private _sidebarRef = React.createRef<SidebarAnnos>();
     private _sidebarTagRef = React.createRef<React.Component>();
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
-    private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
+    private _scrollRef: HTMLDivElement | null = null;
     private _editorView: Opt<EditorView>;
     public _applyingChange: string = '';
     private _inDrop = false;
@@ -1415,14 +1415,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 handleScrollToSelection: editorView => {
                     const docPos = editorView.coordsAtPos(editorView.state.selection.to);
                     const viewRect = self._ref.current!.getBoundingClientRect();
-                    const scrollRef = self.ProseRef;
+                    const scrollRef = self._scrollRef;
                     const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined;
                     const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
                     if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
                         const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
                         const scrollPos = scrollRef.scrollTop + shift * self.ScreenToLocalBoxXf().Scale;
                         if (this._focusSpeed !== undefined) {
-                            scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed, scrollRef, scrollPos, 'ease', this._scrollStopper));
+                            setTimeout(() => scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper)));
                         } else {
                             scrollRef.scrollTo({ top: scrollPos });
                         }
@@ -1823,10 +1823,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash.
     };
     onScroll = (e: React.UIEvent) => {
-        if (!LinkInfo.Instance?.LinkInfo && this._scrollRef.current) {
+        if (!LinkInfo.Instance?.LinkInfo && this._scrollRef) {
             if (!this._props.dontSelectOnLoad) {
                 this._ignoreScroll = true;
-                this.layoutDoc._layout_scrollTop = this._scrollRef.current.scrollTop;
+                this.layoutDoc._layout_scrollTop = this._scrollRef.scrollTop;
                 this._ignoreScroll = false;
                 e.stopPropagation();
                 e.preventDefault();
@@ -2031,7 +2031,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             // prevent default if selected || child is active  but this doc isn't scrollable
             if (
                 !Number.isNaN(height) &&
-                (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
+                (this._scrollRef?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
                 (this._props.rootSelected?.() || this.isAnyChildContentActive())
             ) {
                 e.preventDefault();
@@ -2109,7 +2109,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     onDoubleClick={this.onDoubleClick}>
                     <div
                         className="formattedTextBox-outer"
-                        ref={this._scrollRef}
+                        ref={r => (this._scrollRef = r)}
                         style={{
                             width: this._props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
                             overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
-- 
cgit v1.2.3-70-g09d2


From 5e865b0a96c7b13e538d159baea0c10416bef921 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 1 Apr 2024 11:21:05 -0400
Subject: forgot these from last - fixes forced text scroll from pres trail.

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 6 +++---
 1 file changed, 3 insertions(+), 3 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 a78d8363e..269a114bb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1309,15 +1309,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         this._disposers.scroll = reaction(
             () => NumCast(this.layoutDoc._layout_scrollTop),
             pos => {
-                if (!this._ignoreScroll && this.ProseRef && !this._props.dontSelectOnLoad) {
+                if (!this._ignoreScroll && this._scrollRef && !this._props.dontSelectOnLoad) {
                     const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
                     const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
                     const durationSecStr = viewTrans.match(/([0-9.]*)s/);
                     const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
                     if (duration) {
-                        this._scrollStopper = smoothScroll(duration, this.ProseRef, Math.abs(pos || 0), 'ease', this._scrollStopper);
+                        this._scrollStopper = smoothScroll(duration, this._scrollRef, Math.abs(pos || 0), 'ease', this._scrollStopper);
                     } else {
-                        this.ProseRef.scrollTo({ top: pos });
+                        this._scrollRef.scrollTo({ top: pos });
                     }
                 }
             },
-- 
cgit v1.2.3-70-g09d2


From 83deb55fa4e99e25cebd4a9d4eb882c2fddee28b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 1 Apr 2024 13:44:16 -0400
Subject: fixed header template to be marginally functional.

---
 src/Utils.ts                                       |  3 +-
 src/client/documents/Documents.ts                  |  7 ++--
 src/client/util/CurrentUserUtils.ts                | 25 +++++++------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 42 +++++++++-------------
 src/fields/Doc.ts                                  |  3 +-
 5 files changed, 35 insertions(+), 45 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 21c91278f..a64c7c8a7 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -654,10 +654,9 @@ export function smoothScroll(duration: number, element: HTMLElement | HTMLElemen
         const currentDate = new Date().getTime();
         const currentTime = currentDate - startDate;
         const setScrollTop = (element: HTMLElement, value: number) => (element.scrollTop = value);
-        elements.forEach((element, i) => currentTime && setScrollTop(element, easeFunc(transition, Math.min(currentTime, duration), starts[i], to - starts[i], duration)));
-
         if (!_stop) {
             if (currentTime < duration) {
+                elements.forEach((element, i) => currentTime && setScrollTop(element, easeFunc(transition, Math.min(currentTime, duration), starts[i], to - starts[i], duration)));
                 requestAnimationFrame(animateScroll);
             } else {
                 elements.forEach(element => setScrollTop(element, to));
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index eb15c332f..20e74a3e1 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -254,9 +254,9 @@ export class DocumentOptions {
     dontRegisterView?: BOOLt = new BoolInfo('are views of this document registered so that they can be found when following links, etc', false);
     _undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
     undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
-    _headerHeight?: NUMt = new NumInfo('height of document header used for displaying title', false);
-    _headerFontSize?: NUMt = new NumInfo('font size of header of custom notes', false);
-    _headerPointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume');
+    _header_height?: NUMt = new NumInfo('height of document header used for displaying title', false);
+    _header_fontSize?: NUMt = new NumInfo('font size of header of custom notes', false);
+    _header_pointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume');
     _lockedPosition?: BOOLt = new BoolInfo("lock the x,y coordinates of the document so that it can't be dragged");
     _lockedTransform?: BOOLt = new BoolInfo('lock the freeform_panx,freeform_pany and scale parameters of the document so that it be panned/zoomed');
 
@@ -284,6 +284,7 @@ export class DocumentOptions {
     layout_boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow
     layout_maxShown?: NUMt = new NumInfo('maximum number of children to display at one time (see multicolumnview)');
     _layout_autoHeight?: BOOLt = new BoolInfo('whether document automatically resizes vertically to display contents');
+    _layout_autoHeightMargins?: NUMt = new NumInfo('Margin heights to be added to the computed auto height of a Doc');
     _layout_curPage?: NUMt = new NumInfo('current page of a PDF or other? paginated document', false);
     _layout_currentTimecode?: NUMt = new NumInfo('the current timecode of a time-based document (e.g., current time of a video)  value is in seconds', false);
     _layout_centered?: BOOLt = new BoolInfo('whether text should be vertically centered in Doc');
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b06801066..16e0cb14a 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -222,20 +222,19 @@ export class CurrentUserUtils {
         };
         const headerBtnHgt = 10;
         const headerTemplate = (opts:DocumentOptions) => {
-            const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "Untitled Header", 
+            const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "Header Template", 
                 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)'/>` +
-                "    <FormattedTextBox {...props} dontScale='true' fieldKey={'header'} dontSelectOnLoad='true' ignoreAutoHeight='true' fontSize='{this._headerFontSize||9}px' height='{(this._headerHeight||0)}px' backgroundColor='{this._headerColor || MySharedDocs().userColor||`lightGray`}' />" +
-                `    <HTMLdiv fontSize='${headerBtnHgt - 1}px' height='${headerBtnHgt}px' backgroundColor='yellow' onClick={‘(this._headerHeight=scale*Math.min(Math.max(0,this._height-30),this._headerHeight===0?50:0)) + (this._layout_autoHeightMargins=this._headerHeight ? this._headerHeight+${headerBtnHgt}:0)’}  >Metadata</HTMLdiv>` +
-                "</HTMLdiv>"
+                `<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._header_height||0}px)'  />
+                    <FormattedTextBox {...props} dontScale='true' fieldKey={'header'} dontSelectOnLoad='true' ignoreAutoHeight='true' fontSize='{this._header_fontSize||9}px' height='{(this._header_height||0)}px' backgroundColor='{this._header_color || "lightGray"}' />
+                    <HTMLdiv fontSize='${headerBtnHgt - 1}px' height='${headerBtnHgt}px' backgroundColor='yellow' onClick={‘(this._header_height=Math.min(Math.max(0,this._height-30),this._header_height===0?50:0)) + (this._layout_autoHeightMargins=this._header_height ? this._header_height+${headerBtnHgt}:0)’} > Metadata</HTMLdiv>
+                </HTMLdiv>`
             }, "header");
-
             // "<div style={'height:100%'}>" +
-            // "    <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)'/>" +
+            // "    <FormattedTextBox {...props} fieldKey={'header'} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} pointerEvents='{this._header_pointerEvents||`none`}' fontSize='{this._header_fontSize}px' height='{this._header_height}px' background='{this._header_color}' />" +
+            // "    <FormattedTextBox {...props} fieldKey={'text'} position='absolute' top='{(this._header_height)*scale}px' height='calc({100/scale}% - {this._header_height}px)'/>" +
             // "</div>";
-            MakeTemplate(Doc.GetProto(header));
+            MakeTemplate(header);
             return header; 
         }
         const slideView = (opts:DocumentOptions) => {
@@ -243,9 +242,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, title: "Untitled Slide View"});
+                    ], {...opts, title: "Slide View Template"});
 
-            MakeTemplate(Doc.GetProto(slide));
+            MakeTemplate(slide);
             return slide; 
         }
         const plotlyApi = () => {
@@ -364,7 +363,7 @@ pie title Minerals in my tap water
             {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title_custom: true,  waitForDoubleClickToClick: 'never'}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}},
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts),   opts: { _width: 300, _height: 300 }},
-            {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeView_HideUnrendered: true}},
+            {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 120, _header_pointerEvents: "all", _header_height: 50, _header_fontSize: 9,_layout_autoHeightMargins: 50, _layout_autoHeight: true, treeView_HideUnrendered: true}},
             {key: "ViewSlide",  creator: slideView,                                         opts: { _width: 400, _height: 300, _xMargin: 3, _yMargin: 3,}},
             {key: "Trail",      creator: Docs.Create.PresDocument,                          opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: dropActionType.embed, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }},
             {key: "Tab",        creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }},
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 269a114bb..2e8444379 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -884,7 +884,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             event: undoBatch(() => {
                 this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
                 this.Document.layout_fieldKey = 'layout_meta';
-                setTimeout(() => (this.layoutDoc._headerHeight = this.layoutDoc._layout_autoHeightMargins = 50), 50);
+                setTimeout(() => (this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50), 50);
             }),
             icon: 'eye',
         });
@@ -1305,25 +1305,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             );
             if (this._recordingDictation) setTimeout(this.recordDictation);
         }
-        var quickScroll: string | undefined = '';
         this._disposers.scroll = reaction(
             () => NumCast(this.layoutDoc._layout_scrollTop),
             pos => {
-                if (!this._ignoreScroll && this._scrollRef && !this._props.dontSelectOnLoad) {
-                    const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
-                    const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
-                    const durationSecStr = viewTrans.match(/([0-9.]*)s/);
-                    const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
-                    if (duration) {
-                        this._scrollStopper = smoothScroll(duration, this._scrollRef, Math.abs(pos || 0), 'ease', this._scrollStopper);
-                    } else {
-                        this._scrollRef.scrollTo({ top: pos });
-                    }
+                if (!this._ignoreScroll && this._scrollRef) {
+                    const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]+)(m?)s/);
+                    const duration = Number(durationStr?.[1]) * (durationStr?.[2] ? 1 : 1000);
+                    this._scrollStopper = smoothScroll(duration || 0, this._scrollRef, Math.abs(pos || 0), 'ease', this._scrollStopper);
                 }
             },
             { fireImmediately: true }
         );
-        quickScroll = undefined;
         this.tryUpdateScrollHeight();
         setTimeout(this.tryUpdateScrollHeight, 250);
     }
@@ -1749,7 +1741,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
 
         // this is the markdown for @<published name> document publishing to Doc.myPublishedDocs
-        const match = RTFCast(this.Document[this.fieldKey]).Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
+        const match = RTFCast(this.Document[this.fieldKey])?.Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
         if (match) {
             this.dataDoc.title_custom = true;
             this.dataDoc.title = match[1]; // this triggers the collectionDockingView to publish this Doc
@@ -1824,13 +1816,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     };
     onScroll = (e: React.UIEvent) => {
         if (!LinkInfo.Instance?.LinkInfo && this._scrollRef) {
-            if (!this._props.dontSelectOnLoad) {
-                this._ignoreScroll = true;
-                this.layoutDoc._layout_scrollTop = this._scrollRef.scrollTop;
-                this._ignoreScroll = false;
-                e.stopPropagation();
-                e.preventDefault();
-            }
+            this._ignoreScroll = true;
+            this.layoutDoc._layout_scrollTop = this._scrollRef.scrollTop;
+            this._ignoreScroll = false;
+            e.stopPropagation();
+            e.preventDefault();
         }
     };
     tryUpdateScrollHeight = () => {
@@ -2026,7 +2016,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
         if (this._props.isContentActive()) {
             const scale = this._props.NativeDimScaling?.() || 1;
-            const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
+            const styleFromLayoutString = Doc.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' >
             const height = Number(styleFromLayoutString.height?.replace('px', ''));
             // prevent default if selected || child is active  but this doc isn't scrollable
             if (
@@ -2059,7 +2049,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
         const paddingX = NumCast(this.layoutDoc._xMargin, this._props.xPadding || 0);
         const paddingY = NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
-        const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
+        const styleFromLayoutString = Doc.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 styleFromLayoutString?.height === '0px' ? null : (
             <div
                 className="formattedTextBox"
@@ -2111,7 +2101,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                         className="formattedTextBox-outer"
                         ref={r => (this._scrollRef = r)}
                         style={{
-                            width: this._props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
+                            width: this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
                             overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
                         }}
                         onScroll={this.onScroll}
@@ -2128,8 +2118,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                             }}
                         />
                     </div>
-                    {this.noSidebar || this._props.dontSelectOnLoad || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
-                    {this.noSidebar || this.Document._layout_noSidebar || this._props.dontSelectOnLoad || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
+                    {this.noSidebar || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
+                    {this.noSidebar || this.Document._layout_noSidebar || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
                     {this.audioHandle}
                     {this.layoutDoc._layout_enableAltContentUI && !this.layoutDoc._chromeHidden ? this.overlayAlternateIcon : null}
                 </div>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 9973232bf..48214cf25 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1162,7 +1162,8 @@ export namespace Doc {
         return doc[StrCast(doc.layout_fieldKey, 'layout')];
     }
     export function LayoutFieldKey(doc: Doc, templateLayoutString?: string): string {
-        return StrCast(templateLayoutString || Doc.Layout(doc).layout).split("'")[1]; // bcz: TODO check on this .  used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey
+        const match = StrCast(templateLayoutString || Doc.Layout(doc).layout).match(/fieldKey={'([^']+)'}/);
+        return match?.[1] || ''; // bcz: TODO check on this .  used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey
     }
     export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
         return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
-- 
cgit v1.2.3-70-g09d2


From 75cbf3ffc1893d02b48ef65ec834e9ae597398bf Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 1 Apr 2024 20:23:24 -0400
Subject: fixed text toggle buttons to highlight based on selection.  enabled
 background colors for text lists.  cleaned up text insertion point setting on
 pointer up.  fixed autoHeight for text boxes with 'auto' nodes like ordered
 lists.

---
 src/client/util/CurrentUserUtils.ts                |   2 +-
 src/client/views/global/globalScripts.ts           |  50 +++---
 .../views/nodes/formattedText/DashFieldView.scss   |   1 +
 .../nodes/formattedText/FormattedTextBox.scss      |  14 ++
 .../views/nodes/formattedText/FormattedTextBox.tsx |  47 ++---
 .../formattedText/ProsemirrorExampleTransfer.ts    |   2 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     | 199 ++++-----------------
 .../views/nodes/formattedText/RichTextRules.ts     |   4 +-
 src/client/views/nodes/formattedText/nodes_rts.ts  |  30 ++--
 9 files changed, 113 insertions(+), 236 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b73111062..38ebc86e7 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -725,7 +725,7 @@ pie title Minerals in my tap water
               btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
             { title: "Font Size",toolTip: "Font size (%size)",    btnType: ButtonType.NumberDropdownButton,              toolType:"fontSize", ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 6 },
             { title: "Color",    toolTip: "Font color (%color)",  btnType: ButtonType.ColorButton,  icon: "font",        toolType:"fontColor",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'}},
-            { title: "Highlight",toolTip: "Font highlight",       btnType: ButtonType.ColorButton,  icon: "highlighter", toolType:"highlight",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
+            { title: "Highlight",toolTip: "Font highlight",       btnType: ButtonType.ColorButton,  icon: "highlighter", toolType:"highlight",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'}},
             { title: "Bold",     toolTip: "Bold (Ctrl+B)",        btnType: ButtonType.ToggleButton, icon: "bold",        toolType:"bold",     ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
             { title: "Italic",   toolTip: "Italic (Ctrl+I)",      btnType: ButtonType.ToggleButton, icon: "italic",      toolType:"italics",  ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
             { title: "Under",    toolTip: "Underline (Ctrl+U)",   btnType: ButtonType.ToggleButton, icon: "underline",   toolType:"underline",ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index dab642499..497ab98d8 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -151,26 +151,26 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
     const map: Map<'font'|'fontColor'|'highlight'|'fontSize'|'alignment', { checkResult: () => any; setDoc: () => void;}> = new Map([
         ['font', {
             checkResult: () => RichTextMenu.Instance?.fontFamily,
-            setDoc: () => value && RichTextMenu.Instance.setFontFamily(value),
+            setDoc: () => value && RichTextMenu.Instance?.setFontFamily(value),
         }],
         ['highlight', {
-            checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight,
-            setDoc: () => value  && RichTextMenu.Instance.setHighlight(value),
+            checkResult: () => RichTextMenu.Instance?.fontHighlight,
+            setDoc: () => value  && RichTextMenu.Instance?.setHighlight(value),
         }],
         ['fontColor', {
             checkResult: () => RichTextMenu.Instance?.fontColor,
-            setDoc: () => value && RichTextMenu.Instance.setColor(value),
+            setDoc: () => value && RichTextMenu.Instance?.setColor(value),
         }],
         ['alignment', {
-            checkResult: () => RichTextMenu.Instance.textAlign,
-            setDoc: () => value && editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value),
+            checkResult: () => RichTextMenu.Instance?.textAlign,
+            setDoc: () => value && editorView?.state ? RichTextMenu.Instance?.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value),
         }],
         ['fontSize', {
             checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''),
             setDoc: () => {
                 if (typeof value === 'number') value = value.toString();
                 if (value && Number(value).toString() === value) value += 'px';
-                RichTextMenu.Instance.setFontSize(value);
+                RichTextMenu.Instance?.setFontSize(value);
             },
         }],
     ]);
@@ -182,45 +182,45 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
 });
 
 type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal';
-type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }];
+type attrfuncs = [attrname, { checkResult: () => boolean; toggle?: () => any }];
 
 ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) {
     const textView = RichTextMenu.Instance?.TextView;
     const editorView = textView?.EditorView;
     // prettier-ignore
     const alignments:attrfuncs[] = (['left','right','center','vcent'] as ("left"|"center"|"right"|"vcent")[]).map((where) =>
-        [ where, {      checkResult: () =>(editorView ? (where === 'vcent' ?  RichTextMenu.Instance.textVcenter:
-                                                                              (RichTextMenu.Instance.textAlign === where)): 
+        [ where, {      checkResult: () =>(editorView ? (where === 'vcent' ?  RichTextMenu.Instance?.textVcenter ?? false:
+                                                                              (RichTextMenu.Instance?.textAlign === where)): 
                                                          where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered):
                                                                               (Doc.UserDoc().textAlign ===where) ? true:false),
-                        toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance.vcenterToggle(editorView, editorView.dispatch):
-                                                                              RichTextMenu.Instance.align(editorView, editorView.dispatch, where)):
+                        toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(editorView, editorView.dispatch):
+                                                                              RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)):
                                                             where === 'vcent' ?  Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered:
                                                                               (Doc.UserDoc().textAlign = where))}]); // prettier-ignore
     // prettier-ignore
     const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list =>
-        [ list, {       checkResult: () => (editorView ? RichTextMenu.Instance.getActiveListStyle() === list:false),
-                        toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]);
+        [ list, {       checkResult: () => (editorView ? RichTextMenu.Instance?.getActiveListStyle() === list:false),
+                        toggle: () => editorView?.state && RichTextMenu.Instance?.changeListType(list) }]);
     // prettier-ignore
     const attrs:attrfuncs[] = [
         ['dictation', { checkResult: () => textView?._recordingDictation ? true:false,
                         toggle: () => textView && runInAction(() => (textView._recordingDictation = !textView._recordingDictation)) }],
         ['elide',      { checkResult: () => false,
                         toggle: () => editorView ? RichTextMenu.Instance?.elideSelection(): 0}],
-        ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false),
+        ['noAutoLink',{ checkResult: () => ((editorView && RichTextMenu.Instance?.noAutoLink) ?? false),
                         toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}],
-        ['bold',      { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
-                        toggle: editorView ? RichTextMenu.Instance.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}],
-        ['italics',   { checkResult: () => (editorView ? RichTextMenu.Instance.italics : (Doc.UserDoc().fontStyle === 'italics') ? true:false),
-                        toggle: editorView ? RichTextMenu.Instance.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}],
-        ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance.underline : (Doc.UserDoc().textDecoration === 'underline') ? true:false),
-                        toggle: editorView ? RichTextMenu.Instance.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]]
+        ['bold',      { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
+                        toggle: editorView ? RichTextMenu.Instance?.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}],
+        ['italics',   { checkResult: () => (editorView ? RichTextMenu.Instance?.italics ?? false : (Doc.UserDoc().fontStyle === 'italics') ? true:false),
+                        toggle: editorView ? RichTextMenu.Instance?.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}],
+        ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().textDecoration === 'underline') ? true:false),
+                        toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]]
 
     const map = new Map(attrs.concat(alignments).concat(listings));
     if (checkResult) {
         return map.get(charStyle)?.checkResult();
     }
-    undoable(() => map.get(charStyle)?.toggle(), 'toggle ' + charStyle)();
+    undoable(() => map.get(charStyle)?.toggle?.(), 'toggle ' + charStyle)();
 });
 
 export function checkInksToGroup() {
@@ -448,10 +448,10 @@ ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
  */
 ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) {
     SelectionManager.Docs.map(doc => (doc._text_fontFamily = key));
-    const editorView = RichTextMenu.Instance.TextView?.EditorView;
+    const editorView = RichTextMenu.Instance?.TextView?.EditorView;
     if (checkResult) {
-        return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
+        return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc())?.fontFamily);
     }
-    if (editorView) RichTextMenu.Instance.setFontFamily(key);
+    if (editorView) RichTextMenu.Instance?.setFontFamily(key);
     else Doc.UserDoc().fontFamily = key;
 });
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index 74eeb014c..d79df4272 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -26,6 +26,7 @@
         display: inline-block;
         font-weight: normal;
         background: rgba(0, 0, 0, 0.1);
+        cursor: default;
     }
     .dashFieldView-fieldSpan {
         min-width: 8px;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 03ff0436b..3dcc45c96 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -273,6 +273,7 @@ footnote::before {
     height: 20px;
     &::before {
         content: '→';
+        cursor: default;
     }
     &:hover {
         background: orange;
@@ -348,6 +349,7 @@ footnote::before {
     touch-action: none;
     span {
         font-family: inherit;
+        background-color: inherit;
     }
 
     blockquote {
@@ -397,6 +399,7 @@ footnote::before {
             font-family: inherit;
         }
         margin-left: 0;
+        background-color: inherit;
     }
     .decimal2-ol {
         counter-reset: deci2;
@@ -406,6 +409,7 @@ footnote::before {
         }
         font-size: smaller;
         padding-left: 2.1em;
+        background-color: inherit;
     }
     .decimal3-ol {
         counter-reset: deci3;
@@ -415,6 +419,7 @@ footnote::before {
         }
         font-size: smaller;
         padding-left: 2.85em;
+        background-color: inherit;
     }
     .decimal4-ol {
         counter-reset: deci4;
@@ -424,6 +429,7 @@ footnote::before {
         }
         font-size: smaller;
         padding-left: 3.85em;
+        background-color: inherit;
     }
     .decimal5-ol {
         counter-reset: deci5;
@@ -432,6 +438,7 @@ footnote::before {
             font-family: inherit;
         }
         font-size: smaller;
+        background-color: inherit;
     }
     .decimal6-ol {
         counter-reset: deci6;
@@ -440,6 +447,7 @@ footnote::before {
             font-family: inherit;
         }
         font-size: smaller;
+        background-color: inherit;
     }
     .decimal7-ol {
         counter-reset: deci7;
@@ -448,6 +456,7 @@ footnote::before {
             font-family: inherit;
         }
         font-size: smaller;
+        background-color: inherit;
     }
 
     .multi1-ol {
@@ -458,6 +467,7 @@ footnote::before {
         }
         margin-left: 0;
         padding-left: 1.2em;
+        background-color: inherit;
     }
     .multi2-ol {
         counter-reset: multi2;
@@ -467,6 +477,7 @@ footnote::before {
         }
         font-size: smaller;
         padding-left: 2em;
+        background-color: inherit;
     }
     .multi3-ol {
         counter-reset: multi3;
@@ -476,6 +487,7 @@ footnote::before {
         }
         font-size: smaller;
         padding-left: 2.85em;
+        background-color: inherit;
     }
     .multi4-ol {
         counter-reset: multi4;
@@ -485,6 +497,7 @@ footnote::before {
         }
         font-size: smaller;
         padding-left: 3.85em;
+        background-color: inherit;
     }
 
     //.bullet:before, .bullet1:before, .bullet2:before, .bullet3:before, .bullet4:before, .bullet5:before { transition: 0.5s; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content:" " }
@@ -788,6 +801,7 @@ footnote::before {
         height: 20px;
         &::before {
             content: '→';
+            cursor: default;
         }
         &:hover {
             background: orange;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2e8444379..c2f3a6e4b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -291,7 +291,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             });
         };
         AnchorMenu.Instance.Highlight = undoable((color: string) => {
-            this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
+            this._editorView?.state && RichTextMenu.Instance?.setHighlight(color);
             return undefined;
         }, 'highlght text');
         AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
@@ -1577,32 +1577,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         document.removeEventListener('pointerup', this.onSelectEnd);
     };
     onPointerUp = (e: React.PointerEvent): void => {
-        const editor = this._editorView!;
-        const state = editor?.state;
-        if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return;
-        if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
-        else if (this._props.isContentActive() && !e.button) {
-            const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
-            let xpos = pcords?.pos || 0;
-            while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
-                xpos = xpos - 1;
-            }
-            let node: any;
-            try {
-                node = state.doc.nodeAt(xpos);
-            } catch (e) {}
-            if (node?.type !== schema.nodes.dashFieldView) {
-                editor.dispatch(state.tr.setSelection(TextSelection.near(state.doc.resolve(xpos))));
-                let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
-                while (target && !target.dataset?.targethrefs) target = target.parentElement;
-                FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
-            } else if (node) {
-                try {
-                    editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos))));
-                } catch (e) {
-                    editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos - 1))));
-                }
-            }
+        const state = this.EditorView?.state;
+        if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) {
+            if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
+            let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
+            for (let target = e.target as any; target && !target.dataset?.targethrefs; target = target.parentElement);
+            while (target && !target.dataset?.targethrefs) target = target.parentElement;
+            FormattedTextBoxComment.update(this, this.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
         }
     };
     @action
@@ -1788,7 +1769,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
                 (document.activeElement as any).blur?.();
                 SelectionManager.DeselectAll();
-                RichTextMenu.Instance.updateMenu(undefined, undefined, undefined, undefined);
+                RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
                 return;
             case 'Enter':
                 this.insertTime();
@@ -1827,12 +1808,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
         const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
         if (children && !SnappingManager.IsDragging) {
-            const toNum = (val: string) => Number(val.replace('px', '').replace('auto', '0'));
-            const toHgt = (node: Element) => {
+            const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0;
+            const toNum = (val: string) => Number(val.replace('px', ''));
+            const toHgt = (node: Element): number => {
                 const { height, marginTop, marginBottom } = getComputedStyle(node);
-                return toNum(height) + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom));
+                const childHeight = height === 'auto' ? getChildrenHeights(Array.from(node.children)) : toNum(height);
+                return childHeight + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom));
             };
-            const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins);
+            const proseHeight = !this.ProseRef ? 0 : getChildrenHeights(children);
             const scrollHeight = this.ProseRef && proseHeight;
             if (this._props.setHeight && !this._props.suppressSetHeight && scrollHeight && !this._props.dontRegisterView) {
                 // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 92cc65fc5..ec8879487 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -104,7 +104,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
                 !wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
                     const tx25 = updateBullets(tx2, schema);
                     const ol_node = tx25.doc.nodeAt(range!.start)!;
-                    const tx3 = tx25.setNodeMarkup(range!.start, ol_node.type, { ...ol_node.attrs, ...(marks?.[0]?.type === schema.marks.pFontSize ? { fontSize: marks[0].attrs.fontSize } : {}) });
+                    const tx3 = tx25.setNodeMarkup(range!.start, ol_node.type, ol_node.attrs, marks);
                     // when promoting to a list, assume list will format things so don't copy the stored marks.
                     marks && tx3.ensureMarks([...marks]);
                     marks && tx3.setStoredMarks([...marks]);
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 9b6eb8b8d..9c282a1d2 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -27,7 +27,10 @@ const { toggleMark } = require('prosemirror-commands');
 
 @observer
 export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
-    @observable static Instance: RichTextMenu;
+    static _instance: { menu: RichTextMenu | undefined } = observable({ menu: undefined });
+    static get Instance() {
+        return RichTextMenu._instance?.menu;
+    }
     public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
     private _linkToRef = React.createRef<HTMLInputElement>();
 
@@ -67,7 +70,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     constructor(props: AntimodeMenuProps) {
         super(props);
         makeObservable(this);
-        RichTextMenu.Instance = this;
+        RichTextMenu._instance.menu = this;
         this.updateMenu(undefined, undefined, props, this.layoutDoc);
         this._canFade = false;
         this.Pinned = true;
@@ -131,11 +134,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
             if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return;
         }
 
-        // update active marks
-        const activeMarks = this.getActiveMarksOnSelection();
-        this.setActiveMarkButtons(activeMarks);
-
-        // update active font family and size
+        this.setActiveMarkButtons(this.getActiveMarksOnSelection());
         const active = this.getActiveFontStylesOnSelection();
         const activeFamilies = active.activeFamilies;
         const activeSizes = active.activeSizes;
@@ -164,15 +163,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
             const fromRange = numberRange(state.selection.from).reverse();
             const newPos = nodeOl ? fromRange.find(i => state.doc.nodeAt(i)?.type === state.schema.nodes.ordered_list) ?? state.selection.from : state.selection.from;
             const node = (state.selection as NodeSelection).node ?? (newPos >= 0 ? state.doc.nodeAt(newPos) : undefined);
-            if (node?.type === schema.nodes.ordered_list) {
-                let attrs = node.attrs;
-                if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, fontFamily: mark.attrs.family };
-                if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, fontSize: mark.attrs.fontSize };
-                if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, fontColor: mark.attrs.color };
-                const tr = updateBullets(state.tr.setNodeMarkup(newPos, node.type, attrs), state.schema);
-                dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to))));
-            }
-            {
+            if (node?.type === schema.nodes.ordered_list || node?.type === schema.nodes.list_item) {
+                const hasMark = node.marks.some(m => m.type === mark.type);
+                const otherMarks = node.marks.filter(m => m.type !== mark.type);
+                const addAnyway = node.marks.filter(m => m.type === mark.type && Object.keys(m.attrs).some(akey => m.attrs[akey] !== mark.attrs[akey]));
+                const markup = state.tr.setNodeMarkup(newPos, node.type, node.attrs, hasMark && !addAnyway ? otherMarks : [...otherMarks, mark]);
+                dispatch(updateBullets(markup, state.schema));
+            } else {
                 const state = this.view?.state;
                 const tr = this.view?.state.tr;
                 if (tr && state) {
@@ -225,11 +222,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         if (this.view && this.TextView?._props.rootSelected?.()) {
             const state = this.view.state;
             const pos = this.view.state.selection.$from;
-            const marks: Mark[] = [...(state.storedMarks ?? [])];
+            var marks: Mark[] = [...(state.storedMarks ?? [])];
             if (state.storedMarks !== null) {
             } else if (state.selection.empty) {
-                const ref_node = this.reference_node(pos);
-                marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : []));
+                for (let i = 0; i <= pos.depth; i++) {
+                    marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
+                }
             } else {
                 state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
                     node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
@@ -256,41 +254,26 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     //finds all active marks on selection in given group
     getActiveMarksOnSelection() {
-        let activeMarks: MarkType[] = [];
-        if (!this.view || !this.TextView?._props.rootSelected?.()) return activeMarks;
+        if (!this.view || !this.TextView?._props.rootSelected?.()) return [] as MarkType[];
 
-        const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
-        if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
-        //current selection
-        const { empty, ranges, $to } = this.view.state.selection as TextSelection;
         const state = this.view.state;
-        if (!empty) {
-            activeMarks = markGroup.filter(mark => {
-                const has = false;
-                for (let i = 0; !has && i < ranges.length; i++) {
-                    return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark);
-                }
-                return false;
-            });
-        } else {
-            const pos = this.view.state.selection.$from;
-            const ref_node: ProsNode | null = this.reference_node(pos);
-            if (ref_node !== null && ref_node !== this.view.state.doc) {
-                if (ref_node.isText) {
-                } else {
-                    return [];
-                }
-                activeMarks = markGroup.filter(mark_type => {
-                    // if (mark_type === state.schema.marks.pFontSize) {
-                    //     return mark.isINSet
-                    //     ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
-                    // }
-                    const mark = state.schema.mark(mark_type);
-                    return mark.isInSet(ref_node.marks);
-                });
+        var marks: Mark[] = [...(state.storedMarks ?? [])];
+        const pos = this.view.state.selection.$from;
+        if (state.storedMarks !== null) {
+        } else if (state.selection.empty) {
+            for (let i = 0; i <= pos.depth; i++) {
+                marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
             }
+        } else {
+            state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+                node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
+            });
         }
-        return activeMarks;
+        const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
+        return markGroup.filter(mark_type => {
+            const mark = state.schema.mark(mark_type);
+            return mark.isInSet(marks);
+        });
     }
 
     @action
@@ -418,7 +401,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     // TODO: remove doesn't work
     // remove all node type and apply the passed-in one to the selected text
     changeListType = (mapStyle: string) => {
-        const active = this.view?.state && RichTextMenu.Instance.getActiveListStyle();
+        const active = this.view?.state && RichTextMenu.Instance?.getActiveListStyle();
         const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? '' : mapStyle });
         if (!this.view || nodeType?.attrs.mapStyle === '') return;
 
@@ -566,7 +549,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     // todo: add brushes to brushMap to save with a style name
     onBrushNameKeyPress = (e: React.KeyboardEvent) => {
         if (e.key === 'Enter') {
-            RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
+            RichTextMenu.Instance?.brushMarks && RichTextMenu.Instance?._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
             this._brushNameRef.current!.style.background = 'lightGray';
         }
     };
@@ -574,7 +557,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     @action
     clearBrush() {
-        RichTextMenu.Instance.brushMarks = new Set();
+        RichTextMenu.Instance && (RichTextMenu.Instance.brushMarks = new Set());
     }
 
     @action
@@ -710,118 +693,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         }
     };
 
-    linkExtend($start: ResolvedPos, href: string) {
-        const mark = this.view!.state.schema.marks.linkAnchor;
-
-        let startIndex = $start.index();
-        let endIndex = $start.indexAfter();
-
-        while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) startIndex--;
-        while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) endIndex++;
-
-        let startPos = $start.start();
-        let endPos = startPos;
-        for (let i = 0; i < endIndex; i++) {
-            const size = $start.parent.child(i).nodeSize;
-            if (i < startIndex) startPos += size;
-            endPos += size;
-        }
-        return { from: startPos, to: endPos };
-    }
-
-    reference_node(pos: ResolvedPos): ProsNode | null {
-        if (!this.view) return null;
-
-        let ref_node: ProsNode = this.view.state.doc;
-        if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
-            ref_node = pos.nodeBefore;
-        }
-        if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
-            if (!pos.nodeBefore || this.view.state.selection.$from.pos !== this.view.state.selection.$to.pos) {
-                ref_node = pos.nodeAfter;
-            }
-        }
-        if (!ref_node && pos.pos > 0) {
-            let skip = false;
-            for (let i: number = pos.pos - 1; i > 0; i--) {
-                this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => {
-                    if (node.isLeaf && !skip) {
-                        ref_node = node;
-                        skip = true;
-                    }
-                });
-            }
-        }
-        if (!ref_node.isLeaf && ref_node.childCount > 0) {
-            ref_node = ref_node.child(0);
-        }
-        return ref_node;
-    }
-
     render() {
         return null;
-        // TraceMobx();
-        // const row1 = <div className="antimodeMenu-row" key="row 1" style={{ display: this.collapsed ? "none" : undefined }}>{[
-        //     //!this.collapsed ? this.getDragger() : (null),
-        //     // !this.Pinned ? (null) : <div key="frag1"> {[
-        //     //     this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
-        //     //     this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
-        //     //     this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
-        //     //     this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
-        //     //     this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
-        //     //     this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
-        //     //     <div className="richTextMenu-divider" key="divider" />
-        //     // ]}</div>,
-        //     this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
-        //     this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
-        //     this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
-        //     this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
-        //     this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
-        //     this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
-        //     this.createColorButton(),
-        //     this.createHighlighterButton(),
-        //     this.createLinkButton(),
-        //     this.createBrushButton(),
-        //     <div className="collectionMenu-divider" key="divider 2" />,
-        //     this.createButton("align-left", "Align Left", this.activeAlignment === "left", this.alignLeft),
-        //     this.createButton("align-center", "Align Center", this.activeAlignment === "center", this.alignCenter),
-        //     this.createButton("align-right", "Align Right", this.activeAlignment === "right", this.alignRight),
-        //     this.createButton("indent", "Inset More", undefined, this.insetParagraph),
-        //     this.createButton("outdent", "Inset Less", undefined, this.outsetParagraph),
-        //     this.createButton("hand-point-left", "Hanging Indent", undefined, this.hangingIndentParagraph),
-        //     this.createButton("hand-point-right", "Indent", undefined, this.indentParagraph),
-        // ]}</div>;
-
-        // const row2 = <div className="antimodeMenu-row row-2" key="row2">
-        //     {this.collapsed ? this.getDragger() : (null)}
-        //     <div key="row 2" style={{ display: this.collapsed ? "none" : undefined }}>
-        //         <div className="collectionMenu-divider" key="divider 3" />
-        //         {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => {
-        //             this.activeFontSize = val;
-        //             SelectionManager.Views.map(dv => dv.Document._text_fontSize = val);
-        //         })),
-        //         this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => {
-        //             this.activeFontFamily = val;
-        //             SelectionManager.Views.map(dv => dv.Document._text_fontFamily = val);
-        //         })),
-        //         <div className="collectionMenu-divider" key="divider 4" />,
-        //         this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})),
-        //         this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
-        //         this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
-        //         this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule)
-        //         ]}
-        //     </div>
-        //     {/* <div key="collapser">
-        //         {<div key="collapser">
-        //             <button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>
-        //                 <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} />
-        //             </button>
-        //         </div> }
-        //         <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
-        //             <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
-        //         </button>
-        //     </div> */}
-        // </div>;
     }
 }
 
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index e8cf9e992..42665830f 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -246,7 +246,7 @@ export class RichTextRules {
             // activate a style by name using prefix '%<color name>'
             new InputRule(new RegExp(/%[a-zA-Z_]+$/), (state, match, start, end) => {
                 const color = match[0].substring(1, match[0].length);
-                const marks = RichTextMenu.Instance._brushMap.get(color);
+                const marks = RichTextMenu.Instance?._brushMap.get(color);
 
                 if (
                     DocListCast((Doc.UserDoc().template_notes as Doc).data)
@@ -367,7 +367,7 @@ export class RichTextRules {
                     if (count) {
                         const tr = this.TextBox.EditorView?.state.tr.insertText(' ' + (gptval as string));
                         tr && this.TextBox.EditorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(end + 2), tr.doc.resolve(end + 2 + (gptval as string).length))));
-                        RichTextMenu.Instance.elideSelection(this.TextBox.EditorView?.state, true);
+                        RichTextMenu.Instance?.elideSelection(this.TextBox.EditorView?.state, true);
                     }
                     count++;
                 });
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index cab3a6ef5..ceafc3ba1 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -24,6 +24,7 @@ export const nodes: { [index: string]: NodeSpec } = {
     // :: NodeSpec The top level document node.
     doc: {
         content: 'block+',
+        marks: '_',
     },
 
     paragraph: ParagraphNodeSpec,
@@ -331,12 +332,10 @@ export const nodes: { [index: string]: NodeSpec } = {
         ...orderedList,
         content: 'list_item+',
         group: 'block',
+        marks: '_',
         attrs: {
             bulletStyle: { default: 0 },
-            mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet"
-            fontColor: { default: 'inherit' },
-            fontSize: { default: undefined },
-            fontFamily: { default: undefined },
+            mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet",
             visibility: { default: true },
             indent: { default: undefined },
         },
@@ -376,9 +375,10 @@ export const nodes: { [index: string]: NodeSpec } = {
         ],
         toDOM(node: Node) {
             const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
-            const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : '';
-            const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : '';
-            const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : '';
+            const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
+            const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
+            const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
+            const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
             const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : '';
             if (node.attrs.mapStyle === 'bullet') {
                 return [
@@ -386,7 +386,7 @@ export const nodes: { [index: string]: NodeSpec } = {
                     {
                         'data-mapStyle': node.attrs.mapStyle,
                         'data-bulletStyle': node.attrs.bulletStyle,
-                        style: `${fsize} ${ffam} ${fcol} ${marg}`,
+                        style: `${fhigh} ${fsize} ${ffam} ${fcol} ${marg}`,
                     },
                     0,
                 ];
@@ -398,7 +398,7 @@ export const nodes: { [index: string]: NodeSpec } = {
                           class: `${map}-ol`,
                           'data-mapStyle': node.attrs.mapStyle,
                           'data-bulletStyle': node.attrs.bulletStyle,
-                          style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}`,
+                          style: `list-style: none; ${fhigh} ${fsize} ${ffam} ${fcol} ${marg}`,
                       },
                       0,
                   ]
@@ -422,16 +422,22 @@ export const nodes: { [index: string]: NodeSpec } = {
                 },
             },
         ],
-        toDOM(node: any) {
+        toDOM(node: Node) {
+            const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
+            const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
+            const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
+            const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
             const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
             return [
                 'li',
-                { class: `${map}`, 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle },
+                { class: `${map}`, style: `${fhigh} ${fsize} ${ffam} ${fcol} `, 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle },
                 node.attrs.visibility
                     ? 0
                     : [
                           'span',
-                          { style: `position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== 'bullet' ? 'inline-block' : 'list-item'}; text-overflow: ellipsis; white-space: pre` },
+                          {
+                              style: `${fhigh} ${fsize} ${ffam} ${fcol} position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== 'bullet' ? 'inline-block' : 'list-item'}; text-overflow: ellipsis; white-space: pre`,
+                          },
                           `${node.firstChild?.textContent}...`,
                       ],
             ];
-- 
cgit v1.2.3-70-g09d2


From 53fbe74037f03456a678d592d0ae5660c2f0d55e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 10 Apr 2024 21:02:05 -0400
Subject: more notetaking fixes - turn off autoSize, unobserve children.  fixed
 typing a new note to delete placeholder text.   added autoCreate for columns.
  added pivot column selector for notetaking view.

---
 .../collections/CollectionNoteTakingView.scss      |  9 +++
 .../views/collections/CollectionNoteTakingView.tsx | 80 +++++++++++++++++-----
 .../collections/CollectionNoteTakingViewColumn.tsx |  4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 4 files changed, 73 insertions(+), 22 deletions(-)

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

diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss
index 81000e7a5..95fda7b0a 100644
--- a/src/client/views/collections/CollectionNoteTakingView.scss
+++ b/src/client/views/collections/CollectionNoteTakingView.scss
@@ -494,6 +494,15 @@
     .rc-switch-checked .rc-switch-inner {
         left: 8px;
     }
+
+    .collectionNoteTaking-pivotField {
+        display: none;
+    }
+    &:hover {
+        .collectionNoteTaking-pivotField {
+            display: unset;
+        }
+    }
 }
 
 .collectionNoteTakingViewLight {
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 5c52d2398..d8a0aebb1 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -14,9 +14,10 @@ import { Docs, DocUtils } from '../../documents/Documents';
 import { DragManager, dropActionType } from '../../util/DragManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
-import { undoBatch } from '../../util/UndoManager';
+import { undoable, undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
+import { Colors } from '../global/globalEnums';
 import { LightboxView } from '../LightboxView';
 import { DocumentView } from '../nodes/DocumentView';
 import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView';
@@ -26,7 +27,7 @@ import './CollectionNoteTakingView.scss';
 import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn';
 import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider';
 import { CollectionSubView } from './CollectionSubView';
-import { Colors } from '../global/globalEnums';
+import { FieldsDropdown } from '../FieldsDropdown';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 /**
@@ -42,7 +43,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     _masonryGridRef: HTMLDivElement | null = null;
     _draggerRef = React.createRef<HTMLDivElement>();
     @computed get notetakingCategoryField() {
-        return StrCast(this.dataDoc.notetaking_category, 'NotetakingCategory');
+        return StrCast(this.dataDoc.notetaking_column, StrCast(this.layoutDoc.pivotField, 'notetaking_column'));
     }
     public DividerWidth = 16;
     @observable docsDraggedRowCol: number[] = [];
@@ -155,25 +156,34 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         );
     };
 
+    @computed get allFieldValues() {
+        return new Set(this.childDocs.map(doc => StrCast(doc[this.notetakingCategoryField])));
+    }
+
     componentDidMount() {
         super.componentDidMount?.();
         document.addEventListener('pointerup', this.removeDocDragHighlight, true);
-        this._disposers.layout_autoHeight = reaction(
-            () => this.layoutDoc._layout_autoHeight,
-            layout_autoHeight => layout_autoHeight && this._props.setHeight?.(this.headerMargin + Math.max(...this._refList.map(DivHeight)))
+
+        this._disposers.autoColumns = reaction(
+            () => (this.layoutDoc._notetaking_columns_autoCreate ? Array.from(this.allFieldValues) : undefined),
+            columns => undoable(() => columns?.filter(col => !this.colHeaderData.some(h => h.heading === col)).forEach(col => this.addColumn(col)), 'adding columns')(),
+            { fireImmediately: true }
         );
 
         this._disposers.refList = reaction(
             () => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !LightboxView.Contains(this.DocumentView?.()) }),
             ({ refList, autoHeight }) => {
-                if (autoHeight) refList.forEach(r => this.observer.observe(r));
-                else this.observer.disconnect();
+                if (autoHeight) {
+                    refList.forEach(r => this.observer.observe(r));
+                    this._props.setHeight?.(this.headerMargin + Math.max(...this._refList.map(DivHeight)));
+                } else this.observer.disconnect();
             },
             { fireImmediately: true }
         );
     }
 
     componentWillUnmount() {
+        this.observer.disconnect();
         document.removeEventListener('pointerup', this.removeDocDragHighlight, true);
         super.componentWillUnmount();
         Object.keys(this._disposers).forEach(key => this._disposers[key]());
@@ -300,7 +310,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     getDocWidth(d: Doc) {
         const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as Field);
         const existingHeader = this.colHeaderData.find(sh => sh.heading === heading);
-        const existingWidth = existingHeader?.width ? existingHeader.width : 0;
+        const existingWidth = this.layoutDoc._notetaking_columns_autoSize ? 1 / (this.colHeaderData.length ?? 1) : existingHeader?.width ? existingHeader.width : 0;
         const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth;
         const width = d.layout_fitWidth ? maxWidth : NumCast(d._width);
         return Math.min(maxWidth - CollectionNoteTakingViewColumn.ColumnMargin, width < maxWidth ? width : maxWidth);
@@ -502,7 +512,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
 
     editableViewProps = () => ({
         GetValue: () => '',
-        SetValue: this.addGroup,
+        SetValue: this.addColumn,
         contents: '+ Column',
     });
 
@@ -543,18 +553,26 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         />
     );
 
+    @undoBatch
+    remColumn = (value: SchemaHeaderField) => {
+        const colHdrData = Array.from(Cast(this._props.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
+        if (value) {
+            const index = colHdrData.indexOf(value);
+            index !== -1 && colHdrData.splice(index, 1);
+            this.resizeColumns(colHdrData);
+        }
+    };
+
     // addGroup is called when adding a new columnHeader, adding a SchemaHeaderField to our list of
     // columnHeaders and resizing the existing columns to make room for our new one.
     @undoBatch
-    addGroup = (value: string) => {
-        if (this.colHeaderData) {
-            for (const header of this.colHeaderData) {
-                if (header.heading === value) {
-                    alert('You cannot use an existing column name. Please try a new column name');
-                    return value;
-                }
+    addColumn = (value: string) => {
+        this.colHeaderData.forEach(header => {
+            if (header.heading === value) {
+                alert('You cannot use an existing column name. Please try a new column name');
+                return value;
             }
-        }
+        });
         const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
         const newColWidth = 1 / (this.numGroupColumns + 1);
         columnHeaders.push(new SchemaHeaderField(value, undefined, undefined, newColWidth));
@@ -562,11 +580,25 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         return true;
     };
 
+    removeEmptyColumns = undoable(() => {
+        this.colHeaderData.filter(h => !this.allFieldValues.has(h.heading)).forEach(this.remColumn);
+    }, 'remove empty Columns');
+
     onContextMenu = (e: React.MouseEvent): void => {
         // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
         if (!e.isPropagationStopped()) {
             const subItems: ContextMenuProps[] = [];
-            subItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' });
+            subItems.push({
+                description: `${this.layoutDoc._notetaking_columns_autoCreate ? 'Manually' : 'Automatically'} Create columns`,
+                event: () => (this.layoutDoc._notetaking_columns_autoCreate = !this.layoutDoc._notetaking_columns_autoCreate),
+                icon: 'computer',
+            });
+            subItems.push({ description: 'Remove Empty Columns', event: this.removeEmptyColumns, icon: 'computer' });
+            subItems.push({
+                description: `${this.layoutDoc._notetaking_columns_autoSize ? 'Variable Size' : 'Autosize'} Columns`,
+                event: () => (this.layoutDoc._notetaking_columns_autoSize = !this.layoutDoc._notetaking_columns_autoSize),
+                icon: 'plus',
+            });
             subItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), icon: 'plus' });
             subItems.push({ description: 'Clear All', event: () => (this.dataDoc.data = new List([])), icon: 'times' });
             ContextMenu.Instance.addItem({ description: 'Options...', subitems: subItems, icon: 'eye' });
@@ -634,6 +666,16 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 onContextMenu={this.onContextMenu}
                 onWheel={e => this._props.isContentActive() && e.stopPropagation()}>
                 <>{this.renderedSections}</>
+                <div className="collectionNotetaking-pivotField" style={{ right: 0, top: 0, position: 'absolute' }}>
+                    <FieldsDropdown
+                        Document={this.Document}
+                        selectFunc={undoable(fieldKey => {
+                            this.layoutDoc._pivotField = fieldKey;
+                            this.removeEmptyColumns();
+                        }, 'change pivot field')}
+                        placeholder={StrCast(this.layoutDoc._pivotField)}
+                    />
+                </div>
             </div>
         );
     }
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 788490b82..448b11b05 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -4,7 +4,6 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { lightOrDark, returnEmptyString } from '../../../Utils';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
-import { Id } from '../../../fields/FieldSymbols';
 import { RichTextField } from '../../../fields/RichTextField';
 import { listSpec } from '../../../fields/Schema';
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
@@ -68,6 +67,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
 
     // columnWidth returns the width of a column in absolute pixels
     @computed get columnWidth() {
+        if (this._props.Document._notetaking_columns_autoSize) return this._props.availableWidth / (this._props.colHeaderData?.length || 1);
         if (!this._props.colHeaderData || !this._props.headingObject || this._props.colHeaderData.length === 1) return `${(this._props.availableWidth / this._props.PanelWidth()) * 100}%`;
         const i = this._props.colHeaderData.findIndex(hd => hd.heading === this._props.headingObject?.heading && hd.color === this._props.headingObject.color);
         return ((this._props.colHeaderData[i].width * this._props.availableWidth) / this._props.PanelWidth()) * 100 + '%';
@@ -154,7 +154,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
     deleteColumn = () => {
         const colHdrData = Array.from(Cast(this._props.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
         if (this._props.headingObject) {
-            this._props.docList.forEach(d => (d[this._props.pivotField] = undefined));
+            //          this._props.docList.forEach(d => (d[DocData][this._props.pivotField] = undefined));
             colHdrData.splice(colHdrData.indexOf(this._props.headingObject), 1);
             this._props.resizeColumns(colHdrData);
         }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index c2f3a6e4b..5c59f7f60 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1255,7 +1255,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 return !whichData ? undefined : { data: RTFCast(whichData), str: Field.toString(DocCast(whichData) ?? StrCast(whichData)) };
             },
             incomingValue => {
-                if (this._editorView && this._applyingChange !== this.fieldKey) {
+                if (this._editorView && this._applyingChange !== this.fieldKey && incomingValue?.data !== this.dataDoc[this.fieldKey]) {
                     if (incomingValue?.data) {
                         const updatedState = JSON.parse(incomingValue.data.Data);
                         if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) {
-- 
cgit v1.2.3-70-g09d2


From d444d05ebe5eb0d72070ba21e1ae5935930b749f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 17 Apr 2024 11:56:15 -0400
Subject: reverted fix for replacing placeholder text in a new typed note --
 breaks switching from one fieldKey to another

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(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 5c59f7f60..c2f3a6e4b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1255,7 +1255,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 return !whichData ? undefined : { data: RTFCast(whichData), str: Field.toString(DocCast(whichData) ?? StrCast(whichData)) };
             },
             incomingValue => {
-                if (this._editorView && this._applyingChange !== this.fieldKey && incomingValue?.data !== this.dataDoc[this.fieldKey]) {
+                if (this._editorView && this._applyingChange !== this.fieldKey) {
                     if (incomingValue?.data) {
                         const updatedState = JSON.parse(incomingValue.data.Data);
                         if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) {
-- 
cgit v1.2.3-70-g09d2


From 62937027183dc8acf14e489fbb4590aff6fce2cd Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 17 Apr 2024 12:25:17 -0400
Subject: fix problem where placeholder text of note wasn't being overwritten.

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

(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 c2f3a6e4b..a2db2a1cc 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1484,6 +1484,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 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);
+                this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
             }
         }
         if (selectOnLoad) {
-- 
cgit v1.2.3-70-g09d2


From d20746762917fd0378f731fd06e26567dde9e486 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 24 Apr 2024 18:36:52 -0400
Subject: minor tweaks for merging

---
 src/client/apis/gpt/GPT.ts                                | 6 +++---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 9 +++------
 2 files changed, 6 insertions(+), 9 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index c97d29d6f..30194f9f8 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -43,7 +43,7 @@ const gptAPICall = async (inputText: string, callType: GPTCallType, prompt?: any
         };
         const openai = new OpenAI(configuration);
 
-        let usePrompt = prompt? opts.prompt+prompt: opts.prompt;
+        let usePrompt = prompt ? opts.prompt + prompt : opts.prompt;
         let messages: ChatCompletionMessageParam[] = [
             { role: 'system', content: usePrompt },
             { role: 'user', content: inputText },
@@ -51,9 +51,9 @@ const gptAPICall = async (inputText: string, callType: GPTCallType, prompt?: any
 
         const response = await openai.chat.completions.create({
             model: opts.model,
-            messages: messages,
-            temperature: opts.temp,
             max_tokens: opts.maxTokens,
+            temperature: opts.temp,
+            messages,
         });
         const content = response.choices[0].message.content;
         return content;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index bb910737b..36c1de841 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -984,13 +984,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     };
 
     animateRes = (resIndex: number, newText: string) => {
-        if (!this._editorView) return;
         if (resIndex < newText.length) {
-            const marks = this._editorView.state.storedMarks ?? [];
-            this._editorView.dispatch(this._editorView.state.tr.insertText(newText[resIndex]).setStoredMarks(marks));
-            setTimeout(() => {
-                this.animateRes(resIndex + 1, newText);
-            }, 20);
+            const marks = this._editorView?.state.storedMarks ?? [];
+            this._editorView?.dispatch(this._editorView?.state.tr.insertText(newText[resIndex]).setStoredMarks(marks));
+            setTimeout(() => this.animateRes(resIndex + 1, newText), 20);
         }
     };
 
-- 
cgit v1.2.3-70-g09d2


From ae9404e2c80555875f2fd69f4ae55105ae51168b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 24 Apr 2024 18:44:28 -0400
Subject: from last

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 14 +++++---------
 1 file changed, 5 insertions(+), 9 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 36c1de841..a46b19a85 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -996,15 +996,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
             if (!res) {
                 this.animateRes(0, 'Something went wrong.');
-            } else {
-                if (!this._editorView) return;
-                // No animation
-                // this._editorView.dispatch(this._editorView.state.tr.insertText(res));
-
-                // Animation
-                // Set selection at end
-                const sel = Selection.atEnd(this._editorView.state.doc);
-                this._editorView.dispatch(this._editorView.state.tr.setSelection(sel));
+            } else if (this._editorView) {
+                const { dispatch, state } = this._editorView;
+                // for no animation, use: dispatch(state.tr.insertText(res));
+                // for animted response starting at end of text, use:
+                dispatch(state.tr.setSelection(Selection.atEnd(state.doc)));
                 this.animateRes(0, '\n\n' + res);
             }
         } catch (err) {
-- 
cgit v1.2.3-70-g09d2

