From a974aa4e6573c8becf93f78610406747fec14c1c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 19 Mar 2024 17:08:46 -0400
Subject: cleaned up user templates to not get changed on reload.  made setting
 a template add it to the template tools list and as a tools button.   fixed
 linking to parts of a template.   fixed disappearing templates caused by
 stacking view set a field with an empty key.   updated field assignment
 syntax in trees, dash field views, and key value box to all use :,:=,=,=:=
 syntax.  added text elide button.  added @(title) syntax for hyperlinking. 
 made using a template both inherit from the template to get default values
 and use the template to render.    fixed submenu placement of context menu. 
 updated RTF markdown doc.

---
 src/client/util/RTFMarkup.tsx | 30 ++++++++++++------------------
 1 file changed, 12 insertions(+), 18 deletions(-)

(limited to 'src/client/util/RTFMarkup.tsx')

diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index f96d8a5df..315daad42 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -3,18 +3,12 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { MainViewModal } from '../views/MainViewModal';
 import { SettingsManager } from './SettingsManager';
-import { Doc } from '../../fields/Doc';
-import { StrCast } from '../../fields/Types';
 
 @observer
 export class RTFMarkup extends React.Component<{}> {
     static Instance: RTFMarkup;
     @observable private isOpen = false; // whether the SharingManager modal is open or not
 
-    // private get linkVisible() {
-    //     return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
-    // }
-
     @action
     public open = () => (this.isOpen = true);
 
@@ -39,6 +33,10 @@ export class RTFMarkup extends React.Component<{}> {
                     <b style={{ fontSize: 'larger' }}>{`wiki:phrase`}</b>
                     {` display wikipedia page for entered text (terminate with carriage return)`}
                 </p>
+                <p>
+                    <b style={{ fontSize: 'larger' }}>{`(( any text ))`}</b>
+                    {` submit text to Chat GPT to have results appended afterward`}
+                </p>
                 <p>
                     <b style={{ fontSize: 'larger' }}>{`#tag `}</b>
                     {`  add hashtag metadata to document. e.g, #idea`}
@@ -47,10 +45,6 @@ export class RTFMarkup extends React.Component<{}> {
                     <b style={{ fontSize: 'larger' }}>{`#, ## ... ###### `}</b>
                     {`  set heading style based on number of '#'s between 1 and 6`}
                 </p>
-                <p>
-                    <b style={{ fontSize: 'larger' }}>{`#tag `}</b>
-                    {`  add hashtag metadata to document. e.g, #idea`}
-                </p>
                 <p>
                     <b style={{ fontSize: 'larger' }}>{`>> `}</b>
                     {`  add a sidebar text document inline`}
@@ -61,7 +55,7 @@ export class RTFMarkup extends React.Component<{}> {
                 </p>
                 <p>
                     <b style={{ fontSize: 'larger' }}>{`cmd-f `}</b>
-                    {`  collapse to an inline footnote)`}
+                    {`  collapse to an inline footnote`}
                 </p>
                 <p>
                     <b style={{ fontSize: 'larger' }}>{`cmd-e `}</b>
@@ -116,20 +110,20 @@ export class RTFMarkup extends React.Component<{}> {
                     {`  start a block of text that begins with a hanging indent`}
                 </p>
                 <p>
-                    <b style={{ fontSize: 'larger' }}>{`[:doctitle]] `}</b>
+                    <b style={{ fontSize: 'larger' }}>{`@(doctitle) `}</b>
                     {`  hyperlink to document specified by it’s title`}
                 </p>
                 <p>
-                    <b style={{ fontSize: 'larger' }}>{`[[fieldname]] `}</b>
-                    {`  display value of fieldname`}
+                    <b style={{ fontSize: 'larger' }}>{`[@(doctitle.)fieldname] `}</b>
+                    {`  display value of fieldname of text document (unless (doctitle.) is used to indicate another document by it's title)`}
                 </p>
                 <p>
-                    <b style={{ fontSize: 'larger' }}>{`[[fieldname=value]] `}</b>
-                    {`  assign value to fieldname of document and display it`}
+                    <b style={{ fontSize: 'larger' }}>{`[@fieldname:value] `}</b>
+                    {`  assign value to fieldname to data document and display it (if '=' is used instead of ':' the value is set on the layout Doc.  if value is wrapped in (()) then it will be sent to ChatGPT and the response will replace the value)`}
                 </p>
                 <p>
-                    <b style={{ fontSize: 'larger' }}>{`[[fieldname:doctitle]] `}</b>
-                    {`  show value of fieldname from doc specified by it’s title`}
+                    <b style={{ fontSize: 'larger' }}>{`[@fieldname:=expression] `}</b>
+                    {`  assign a computed expression to fieldname to data document and display it (if '=:=' is used instead of ':=' the expression is set on the layout Doc.  if value is wrapped in (()) then it will be sent to ChatGPT and the prompt/response will replace the value)`}
                 </p>
             </div>
         );
-- 
cgit v1.2.3-70-g09d2


From 1d47f6cc8be84ab368ad91f287909ee162d1f2e2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 27 Mar 2024 15:42:12 -0400
Subject: fixed toggling footnotes.  fixed error in bullet hit test.  fixed
 problems with backspace and enter in prosemirror transfer.  fixed display of
 markdown options to start at top.

---
 src/client/util/RTFMarkup.tsx                      |  2 +-
 .../views/nodes/formattedText/FootnoteView.tsx     |  4 ++-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  3 +-
 .../formattedText/ProsemirrorExampleTransfer.ts    | 41 ++++++++++++++--------
 4 files changed, 32 insertions(+), 18 deletions(-)

(limited to 'src/client/util/RTFMarkup.tsx')

diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index 315daad42..57485d893 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -132,7 +132,7 @@ export class RTFMarkup extends React.Component<{}> {
     render() {
         return (
             <MainViewModal
-                dialogueBoxStyle={{ backgroundColor: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, padding: '16px' }}
+                dialogueBoxStyle={{ backgroundColor: SettingsManager.userBackgroundColor, alignContent: 'normal', color: SettingsManager.userColor, padding: '16px' }}
                 contents={this.cheatSheet}
                 isDisplayed={this.isOpen}
                 interactive={true}
diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx
index cf48e1250..b327e5137 100644
--- a/src/client/views/nodes/formattedText/FootnoteView.tsx
+++ b/src/client/views/nodes/formattedText/FootnoteView.tsx
@@ -23,6 +23,7 @@ export class FootnoteView {
         this.dom = document.createElement('footnote');
 
         this.dom.addEventListener('pointerup', this.toggle, true);
+        this.dom.addEventListener('mouseup', (e: MouseEvent) => e.stopPropagation(), true);
         // These are used when the footnote is selected
         this.innerView = null;
     }
@@ -82,9 +83,10 @@ export class FootnoteView {
         document.removeEventListener('pointerup', this.ignore, true);
     };
 
-    toggle = () => {
+    toggle = (e: PointerEvent) => {
         if (this.innerView) this.close();
         else this.open();
+        e.stopPropagation();
     };
 
     close() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 729c4d534..9882a9be9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1685,7 +1685,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     $olistPos = this._editorView?.state.doc.resolve(($olistPos as any).path[($olistPos as any).path.length - 4]);
                 }
             }
-            const listPos = this._editorView?.state.doc.resolve(clickPosVal === olistPos ? clickPosVal + 1 : clickPosVal);
+            const maxSize = this._editorView?.state.doc.content.size ?? 0;
+            const listPos = this._editorView?.state.doc.resolve(Math.min(maxSize, clickPosVal === olistPos ? clickPosVal + 1 : clickPosVal));
             const listNode = listPos?.node();
             if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list && listNode) {
                 if (!highlightOnly) {
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 7b4d8aa03..87eee1b2f 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -255,7 +255,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     });
 
     // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
-    const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => {
+    const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => {
         if (props.onKey?.(event, props)) return true;
         if (!canEdit(state)) return true;
 
@@ -267,7 +267,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
             if (
                 !joinBackward(state, (tx: Transaction) => {
                     dispatch(updateBullets(tx, schema));
-                    if (!view.state.selection.$from.node().content.size) backspace(view.state, view.dispatch, view);
+                    if (once && view.state.selection.$from.depth > 1 && view.state.selection.$from.node(view.state.selection.$from.depth - 1).type === view.state.schema.nodes.list_item) backspace(view.state, view.dispatch, view, false);
                 })
             ) {
                 if (
@@ -286,26 +286,26 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
     //command to break line
 
-    const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => {
+    const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => {
         if (props.onKey?.(event, props)) return true;
         if (!canEdit(state)) return true;
 
         const trange = state.selection.$from.blockRange(state.selection.$to);
-        const path = (state.selection.$from as any).path;
-        const depth = trange ? liftTarget(trange) : undefined;
-        const split = path.length > 5 && !path[path.length - 3].textContent && path[path.length - 6].type !== schema.nodes.list_item;
-        if (split && trange && depth !== undefined && depth !== null) {
+        const depth = trange ? liftTarget(trange) : null;
+        if (
+            depth !== null &&
+            state.selection.$from.node(state.selection.$from.depth - 1)?.type === state.schema.nodes.blockquote && //
+            !state.selection.$from.node().content.size &&
+            trange
+        ) {
             dispatch(state.tr.lift(trange, depth) as any);
             return true;
         }
 
         const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
-        const cr = state.selection.$from.node().textContent.endsWith('\n');
-        if (/*cr ||*/ !newlineInCode(state, dispatch as any)) {
-            if (!view.state.selection.$from.nodeBefore && !view.state.selection.$from.nodeBefore) {
-                do {
-                    liftListItem(schema.nodes.list_item)(view.state, view.dispatch);
-                } while (view.state.selection.$from.depth > 1);
+        if (!newlineInCode(state, dispatch as any)) {
+            if (once && view.state.selection.$from.depth > 1 && !view.state.selection.$from.nodeBefore && !view.state.selection.$from.nodeBefore) {
+                for (let i = 0; i < 10 && view.state.selection.$from.depth > 1 && liftListItem(schema.nodes.list_item)(view.state, view.dispatch); i++);
             } else if (
                 !splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => {
                     const tx3 = updateBullets(tx2, schema);
@@ -321,8 +321,19 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
                         if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
                             const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks);
                             dispatch(tx4);
-                            if (!view.state.selection.$from.node().content.size) liftListItem(schema.nodes.list_item)(view.state, view.dispatch);
-                            else enter(view.state, view.dispatch, view); // view.dispatch(view.state.tr.insertText('\r\n'));
+                            if (
+                                view.state.selection.$from.parentOffset && //
+                                !view.state.selection.$from.node().content.size
+                            )
+                                liftListItem(schema.nodes.list_item)(view.state, view.dispatch);
+                            else if (
+                                once &&
+                                view.state.selection.$from.parentOffset &&
+                                view.state.selection.$from.depth > 1 && //
+                                view.state.selection.$from.node(view.state.selection.$from.depth - 1).type === schema.nodes.list_item
+                            )
+                                enter(view.state, view.dispatch, view, false);
+                            else if (once && depth && !view.state.selection.$from.parentOffset) backspace(view.state, view.dispatch, view, false);
                         } else dispatch(tx3.insertText('\r\n'));
                     })
                 ) {
-- 
cgit v1.2.3-70-g09d2


From aeeebf6d83868d4a5040e9632503511617242c55 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 30 Mar 2024 16:13:31 -0400
Subject: updated and fixed wiki link to use @(wiki:...) and fixed linkManager
 from infinite looping on relatedlinker().  fixed clicking in text exxeptions.

---
 src/client/util/RTFMarkup.tsx                             |  2 +-
 src/client/views/nodes/LinkBox.tsx                        |  4 ++--
 src/client/views/nodes/LinkDocPreview.tsx                 |  5 +++--
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 src/client/views/nodes/formattedText/RichTextRules.ts     | 12 ++++++------
 5 files changed, 13 insertions(+), 12 deletions(-)

(limited to 'src/client/util/RTFMarkup.tsx')

diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index 57485d893..35b1579df 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -30,7 +30,7 @@ export class RTFMarkup extends React.Component<{}> {
         return (
             <div style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, textAlign: 'initial', height: '100%' }}>
                 <p>
-                    <b style={{ fontSize: 'larger' }}>{`wiki:phrase`}</b>
+                    <b style={{ fontSize: 'larger' }}>{`(@wiki:phrase)`}</b>
                     {` display wikipedia page for entered text (terminate with carriage return)`}
                 </p>
                 <p>
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 36bd037ca..3a2509c3d 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -48,9 +48,9 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     componentDidMount() {
         this._props.setContentViewBox?.(this);
         this._disposers.deleting = reaction(
-            () => (!this.anchor1 || !this.anchor2) && this.DocumentView?.() && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView!())),
+            () => !this.anchor1 && !this.anchor2 && this.DocumentView?.() && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView!())),
             empty => empty && ((this._hackToSeeIfDeleted = setTimeout(() => 
-                (!this.anchor1 || !this.anchor2) && this._props.removeDocument?.(this.Document)
+                (!this.anchor1 && !this.anchor2) && this._props.removeDocument?.(this.Document)
             )), 1000) // prettier-ignore
         );
         this._disposers.dragging = reaction(
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 4b6ee7d72..c9c8f9260 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -185,8 +185,9 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
             LinkFollower.FollowLink(this._linkDoc, this._linkSrc, false);
         } else if (this._props.hrefs?.length) {
             const webDoc =
-                Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0], false).keys()).lastElement() ??
-                Docs.Create.WebDocument(this._props.hrefs[0], { title: this._props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true });
+                Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0], false).keys())
+                    .filter(doc => doc.type === DocumentType.WEB)
+                    .lastElement() ?? Docs.Create.WebDocument(this._props.hrefs[0], { title: this._props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true });
             DocumentManager.Instance.showDocument(webDoc, {
                 openLocation: OpenWhere.lightbox,
                 willPan: true,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 5c779734d..66df1eaf2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1607,7 +1607,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 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 {
+            } else if (node) {
                 try {
                     editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos))));
                 } catch (e) {
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 483035e86..5e53a019e 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -295,7 +295,7 @@ 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) => {
+            new InputRule(new RegExp(/(^|\s)@\(([a-zA-Z_@\.\? \-0-9]+)\)/), (state, match, start, end) => {
                 const docTitle = match[2];
                 const prefixLength = '@('.length;
                 if (docTitle) {
@@ -380,17 +380,17 @@ export class RichTextRules {
             }),
 
             // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
-            // wiki:title
-            new InputRule(new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/), (state, match, start, end) => {
-                const title = match[1];
+            // @(wiki:title)
+            new InputRule(new RegExp(/@\(wiki:([a-zA-Z_@:\.\?\-0-9 ]+)\)$/), (state, match, start, end) => {
+                const title = match[1].trim().replace(/ /g, '_');
                 this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))));
 
                 this.TextBox.makeLinkAnchor(undefined, 'add:right', `https://en.wikipedia.org/wiki/${title.trim()}`, 'wikipedia reference');
 
                 const fstate = this.TextBox.EditorView?.state;
                 if (fstate) {
-                    const tr = fstate?.tr.deleteRange(start, start + 5);
-                    return tr.setSelection(new TextSelection(tr.doc.resolve(end - 5))).insertText(' ');
+                    const tr = fstate?.tr.deleteRange(start, start + '@(wiki:'.length);
+                    return tr.setSelection(new TextSelection(tr.doc.resolve(end - '@(wiki:'.length))).insertText(' ');
                 }
                 return state.tr;
             }),
-- 
cgit v1.2.3-70-g09d2

