From e70360946815cdcde434e25eb592e1b919bb4105 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 6 Sep 2022 14:06:02 -0400
Subject: final cleanup of dragging rotated images.  fixed loading of youtube
 videos and displaying errors when the upload fails

---
 src/client/views/PreviewCursor.tsx | 55 +++++++++++++++++++++++++-------------
 1 file changed, 36 insertions(+), 19 deletions(-)

(limited to 'src/client/views/PreviewCursor.tsx')

diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 68f5f072d..4c17d5a97 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -4,9 +4,9 @@ import 'normalize.css';
 import * as React from 'react';
 import { Doc } from '../../fields/Doc';
 import { Cast, NumCast, StrCast } from '../../fields/Types';
-import { returnFalse } from '../../Utils';
+import { emptyFunction, returnFalse } from '../../Utils';
 import { DocServer } from '../DocServer';
-import { Docs, DocUtils } from '../documents/Documents';
+import { Docs, DocumentOptions, DocUtils } from '../documents/Documents';
 import { Transform } from '../util/Transform';
 import { undoBatch, UndoManager } from '../util/UndoManager';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
@@ -16,15 +16,25 @@ import './PreviewCursor.scss';
 export class PreviewCursor extends React.Component<{}> {
     static _onKeyPress?: (e: KeyboardEvent) => void;
     static _getTransform: () => Transform;
-    static _addDocument: (doc: Doc | Doc[]) => void;
+    static _addDocument: (doc: Doc | Doc[]) => boolean;
     static _addLiveTextDoc: (doc: Doc) => void;
     static _nudge?: undefined | ((x: number, y: number) => boolean);
+    static _slowLoadDocuments?: (
+        files: File[] | string,
+        options: DocumentOptions,
+        generatedDocuments: Doc[],
+        text: string,
+        completed: ((doc: Doc[]) => void) | undefined,
+        clientX: number,
+        clientY: number,
+        addDocument: (doc: Doc | Doc[]) => boolean
+    ) => Promise<void>;
     @observable static _clickPoint = [0, 0];
     @observable public static Visible = false;
     constructor(props: any) {
         super(props);
         document.addEventListener('keydown', this.onKeyPress);
-        document.addEventListener('paste', this.paste);
+        document.addEventListener('paste', this.paste, true);
     }
 
     paste = async (e: ClipboardEvent) => {
@@ -38,20 +48,16 @@ export class PreviewCursor extends React.Component<{}> {
             if (plain) {
                 // tests for youtube and makes video document
                 if (plain.indexOf('www.youtube.com/watch') !== -1) {
-                    const url = plain.replace('youtube.com/watch?v=', 'youtube.com/embed/');
-                    undoBatch(() =>
-                        PreviewCursor._addDocument(
-                            Docs.Create.VideoDocument(url, {
-                                title: url,
-                                _width: 400,
-                                _height: 315,
-                                _nativeWidth: 600,
-                                _nativeHeight: 472.5,
-                                x: newPoint[0],
-                                y: newPoint[1],
-                            })
-                        )
-                    )();
+                    const batch = UndoManager.StartBatch('youtube upload');
+                    const generatedDocuments: Doc[] = [];
+                    const options = {
+                        title: plain,
+                        _width: 400,
+                        _height: 315,
+                        x: newPoint[0],
+                        y: newPoint[1],
+                    };
+                    PreviewCursor._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, newPoint[0], newPoint[1], PreviewCursor._addDocument).then(batch.end);
                 } else if (re.test(plain)) {
                     const url = plain;
                     undoBatch(() =>
@@ -184,7 +190,17 @@ export class PreviewCursor extends React.Component<{}> {
         addLiveText: (doc: Doc) => void,
         getTransform: () => Transform,
         addDocument: undefined | ((doc: Doc | Doc[]) => boolean),
-        nudge: undefined | ((nudgeX: number, nudgeY: number) => boolean)
+        nudge: undefined | ((nudgeX: number, nudgeY: number) => boolean),
+        slowLoadDocuments: (
+            files: File[] | string,
+            options: DocumentOptions,
+            generatedDocuments: Doc[],
+            text: string,
+            completed: ((doc: Doc[]) => void) | undefined,
+            clientX: number,
+            clientY: number,
+            addDocument: (doc: Doc | Doc[]) => boolean
+        ) => Promise<void>
     ) {
         this._clickPoint = [x, y];
         this._onKeyPress = onKeyPress;
@@ -192,6 +208,7 @@ export class PreviewCursor extends React.Component<{}> {
         this._getTransform = getTransform;
         this._addDocument = addDocument || returnFalse;
         this._nudge = nudge;
+        this._slowLoadDocuments = slowLoadDocuments;
         this.Visible = true;
     }
     render() {
-- 
cgit v1.2.3-70-g09d2


From 13e0ac912beeab64a859b3463953774f3f1676f1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 12 Sep 2022 12:09:03 -0400
Subject: fixed h1 style for use in text boxes with #,## etc markdown.  made
 %[tix!] text tags reset user_mark to current time.

---
 src/client/views/DictationOverlay.tsx              | 60 ++++++++++--------
 src/client/views/LightboxView.tsx                  |  1 -
 src/client/views/MainView.scss                     |  5 ++
 src/client/views/MainView.tsx                      |  1 +
 src/client/views/PreviewCursor.scss                |  5 +-
 src/client/views/PreviewCursor.tsx                 |  3 +-
 .../views/nodes/formattedText/RichTextRules.ts     | 10 ++-
 src/debug/Viewer.tsx                               | 72 +++++++++++-----------
 8 files changed, 86 insertions(+), 71 deletions(-)

(limited to 'src/client/views/PreviewCursor.tsx')

diff --git a/src/client/views/DictationOverlay.tsx b/src/client/views/DictationOverlay.tsx
index f4f96da8a..0bdcdc303 100644
--- a/src/client/views/DictationOverlay.tsx
+++ b/src/client/views/DictationOverlay.tsx
@@ -1,9 +1,8 @@
 import { computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import "normalize.css";
 import * as React from 'react';
 import { DictationManager } from '../util/DictationManager';
-import "./Main.scss";
+import './Main.scss';
 import { MainViewModal } from './MainViewModal';
 
 @observer
@@ -29,44 +28,53 @@ export class DictationOverlay extends React.Component {
             this.dictationOverlayVisible = false;
             this.dictationSuccess = undefined;
             DictationOverlay.Instance.hasActiveModal = false;
-            setTimeout(() => this.dictatedPhrase = DictationManager.placeholder, 500);
+            setTimeout(() => (this.dictatedPhrase = DictationManager.placeholder), 500);
         }, duration);
-    }
+    };
     public cancelDictationFade = () => {
         if (this.overlayTimeout) {
             clearTimeout(this.overlayTimeout);
             this.overlayTimeout = undefined;
         }
-    }
+    };
 
-    @computed public get dictatedPhrase() { return this._dictationState; }
-    @computed public get dictationSuccess() { return this._dictationSuccessState; }
-    @computed public get dictationOverlayVisible() { return this._dictationDisplayState; }
-    @computed public get isListening() { return this._dictationListeningState; }
+    @computed public get dictatedPhrase() {
+        return this._dictationState;
+    }
+    @computed public get dictationSuccess() {
+        return this._dictationSuccessState;
+    }
+    @computed public get dictationOverlayVisible() {
+        return this._dictationDisplayState;
+    }
+    @computed public get isListening() {
+        return this._dictationListeningState;
+    }
 
-    public set dictatedPhrase(value: string) { runInAction(() => this._dictationState = value); }
-    public set dictationSuccess(value: boolean | undefined) { runInAction(() => this._dictationSuccessState = value); }
-    public set dictationOverlayVisible(value: boolean) { runInAction(() => this._dictationDisplayState = value); }
-    public set isListening(value: DictationManager.Controls.ListeningUIStatus) { runInAction(() => this._dictationListeningState = value); }
+    public set dictatedPhrase(value: string) {
+        runInAction(() => (this._dictationState = value));
+    }
+    public set dictationSuccess(value: boolean | undefined) {
+        runInAction(() => (this._dictationSuccessState = value));
+    }
+    public set dictationOverlayVisible(value: boolean) {
+        runInAction(() => (this._dictationDisplayState = value));
+    }
+    public set isListening(value: DictationManager.Controls.ListeningUIStatus) {
+        runInAction(() => (this._dictationListeningState = value));
+    }
 
     render() {
         const success = this.dictationSuccess;
         const result = this.isListening && !this.isListening.interim ? DictationManager.placeholder : `"${this.dictatedPhrase}"`;
         const dialogueBoxStyle = {
-            background: success === undefined ? "gainsboro" : success ? "lawngreen" : "red",
-            borderColor: this.isListening ? "red" : "black",
-            fontStyle: "italic"
+            background: success === undefined ? 'gainsboro' : success ? 'lawngreen' : 'red',
+            borderColor: this.isListening ? 'red' : 'black',
+            fontStyle: 'italic',
         };
         const overlayStyle = {
-            backgroundColor: this.isListening ? "red" : "darkslategrey"
+            backgroundColor: this.isListening ? 'red' : 'darkslategrey',
         };
-        return (<MainViewModal
-            contents={result}
-            isDisplayed={this.dictationOverlayVisible}
-            interactive={false}
-            dialogueBoxStyle={dialogueBoxStyle}
-            overlayStyle={overlayStyle}
-            closeOnExternalClick={this.initiateDictationFade}
-        />);
+        return <MainViewModal contents={result} isDisplayed={this.dictationOverlayVisible} interactive={false} dialogueBoxStyle={dialogueBoxStyle} overlayStyle={overlayStyle} closeOnExternalClick={this.initiateDictationFade} />;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 5613e82fb..cb5094f4b 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -1,7 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import 'normalize.css';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { InkTool } from '../../fields/InkField';
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index da79d2992..c5ac1cf52 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -1,5 +1,10 @@
 @import 'global/globalCssVariables';
 @import 'nodeModuleOverrides';
+h1,
+.h1 {
+    // reverts change to h1 made by  normalize.css
+    font-size: 36px;
+}
 
 .dash-tooltip {
     font-size: 11px;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d7b526d22..24dae8816 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -5,6 +5,7 @@ import * as fa from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import 'normalize.css';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
diff --git a/src/client/views/PreviewCursor.scss b/src/client/views/PreviewCursor.scss
index 60b7d14a0..7be765ea9 100644
--- a/src/client/views/PreviewCursor.scss
+++ b/src/client/views/PreviewCursor.scss
@@ -1,11 +1,10 @@
-
 .previewCursor-Dark,
 .previewCursor {
     color: black;
     position: absolute;
     transform-origin: left top;
     top: 0;
-    left:0;
+    left: 0;
     pointer-events: none;
     opacity: 1;
     z-index: 1001;
@@ -13,4 +12,4 @@
 
 .previewCursor-Dark {
     color: white;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 4c17d5a97..d56d2a310 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -1,10 +1,9 @@
 import { action, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import 'normalize.css';
 import * as React from 'react';
 import { Doc } from '../../fields/Doc';
 import { Cast, NumCast, StrCast } from '../../fields/Types';
-import { emptyFunction, returnFalse } from '../../Utils';
+import { returnFalse } from '../../Utils';
 import { DocServer } from '../DocServer';
 import { Docs, DocumentOptions, DocUtils } from '../documents/Documents';
 import { Transform } from '../util/Transform';
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 2097b321f..2eb62c38d 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -343,8 +343,14 @@ export class RichTextRules {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
 
                 if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
-
-                return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+                if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_mark) !== -1) {
+                }
+                return node
+                    ? state.tr
+                          .removeMark(start, end, schema.marks.user_mark)
+                          .addMark(start, end, schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))
+                          .addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) }))
+                    : state.tr;
             }),
 
             new InputRule(new RegExp(/%\(/), (state, match, start, end) => {
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index ee7dd1fc1..02038c426 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -1,5 +1,4 @@
 import { action, configure, observable, ObservableMap, Lambda } from 'mobx';
-import "normalize.css";
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { observer } from 'mobx-react';
@@ -21,7 +20,6 @@ URLField;
 ScriptField;
 CursorField;
 
-
 function applyToDoc(doc: { [index: string]: FieldResult }, key: string, scriptString: string): boolean;
 function applyToDoc(doc: { [index: number]: FieldResult }, key: number, scriptString: string): boolean;
 function applyToDoc(doc: any, key: string | number, scriptString: string): boolean {
@@ -37,11 +35,11 @@ function applyToDoc(doc: any, key: string | number, scriptString: string): boole
 }
 
 configure({
-    enforceActions: "observed"
+    enforceActions: 'observed',
 });
 
 @observer
-class ListViewer extends React.Component<{ field: List<Field> }>{
+class ListViewer extends React.Component<{ field: List<Field> }> {
     @observable
     expanded = false;
 
@@ -49,14 +47,16 @@ class ListViewer extends React.Component<{ field: List<Field> }>{
     onClick = (e: React.MouseEvent) => {
         this.expanded = !this.expanded;
         e.stopPropagation();
-    }
+    };
 
     render() {
         let content;
         if (this.expanded) {
             content = (
                 <div>
-                    {this.props.field.map((field, index) => <DebugViewer field={field} key={index} setValue={value => applyToDoc(this.props.field, index, value)} />)}
+                    {this.props.field.map((field, index) => (
+                        <DebugViewer field={field} key={index} setValue={value => applyToDoc(this.props.field, index, value)} />
+                    ))}
                 </div>
             );
         } else {
@@ -66,7 +66,7 @@ class ListViewer extends React.Component<{ field: List<Field> }>{
             <div>
                 <button onClick={this.onClick}>Toggle</button>
                 {content}
-            </div >
+            </div>
         );
     }
 }
@@ -80,7 +80,7 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
     onClick = (e: React.MouseEvent) => {
         this.expanded = !this.expanded;
         e.stopPropagation();
-    }
+    };
 
     render() {
         let content;
@@ -96,10 +96,7 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
             });
             content = (
                 <div>
-                    Document ({this.props.field[Id]})
-                    <div style={{ paddingLeft: "25px" }}>
-                        {fields}
-                    </div>
+                    Document ({this.props.field[Id]})<div style={{ paddingLeft: '25px' }}>{fields}</div>
                 </div>
             );
         } else {
@@ -109,24 +106,23 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
             <div>
                 <button onClick={this.onClick}>Toggle</button>
                 {content}
-            </div >
+            </div>
         );
     }
 }
 
 @observer
-class DebugViewer extends React.Component<{ field: FieldResult, setValue(value: string): boolean }> {
-
+class DebugViewer extends React.Component<{ field: FieldResult; setValue(value: string): boolean }> {
     render() {
         let content;
         const field = this.props.field;
         if (field instanceof List) {
-            content = (<ListViewer field={field} />);
+            content = <ListViewer field={field} />;
         } else if (field instanceof Doc) {
-            content = (<DocumentViewer field={field} />);
-        } else if (typeof field === "string") {
+            content = <DocumentViewer field={field} />;
+        } else if (typeof field === 'string') {
             content = <p>"{field}"</p>;
-        } else if (typeof field === "number" || typeof field === "boolean") {
+        } else if (typeof field === 'number' || typeof field === 'boolean') {
             content = <p>{field}</p>;
         } else if (field instanceof RichTextField) {
             content = <p>RTF: {field.Data}</p>;
@@ -153,28 +149,30 @@ class Viewer extends React.Component {
     @action
     inputOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         this.idToAdd = e.target.value;
-    }
+    };
 
     @action
     onKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
-        if (e.key === "Enter") {
-            DocServer.GetRefField(this.idToAdd).then(action((field: any) => {
-                if (field !== undefined) {
-                    this.fields.push(field);
-                }
-            }));
-            this.idToAdd = "";
+        if (e.key === 'Enter') {
+            DocServer.GetRefField(this.idToAdd).then(
+                action((field: any) => {
+                    if (field !== undefined) {
+                        this.fields.push(field);
+                    }
+                })
+            );
+            this.idToAdd = '';
         }
-    }
+    };
 
     render() {
         return (
             <>
-                <input value={this.idToAdd}
-                    onChange={this.inputOnChange}
-                    onKeyDown={this.onKeyPress} />
+                <input value={this.idToAdd} onChange={this.inputOnChange} onKeyDown={this.onKeyPress} />
                 <div>
-                    {this.fields.map((field, index) => <DebugViewer field={field} key={index} setValue={() => false}></DebugViewer>)}
+                    {this.fields.map((field, index) => (
+                        <DebugViewer field={field} key={index} setValue={() => false}></DebugViewer>
+                    ))}
                 </div>
             </>
         );
@@ -182,11 +180,11 @@ class Viewer extends React.Component {
 }
 
 (async function () {
-    await DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, "viewer");
-    ReactDOM.render((
-        <div style={{ position: "absolute", width: "100%", height: "100%" }}>
+    await DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, 'viewer');
+    ReactDOM.render(
+        <div style={{ position: 'absolute', width: '100%', height: '100%' }}>
             <Viewer />
-        </div>),
+        </div>,
         document.getElementById('root')
     );
-})();
\ No newline at end of file
+})();
-- 
cgit v1.2.3-70-g09d2


From dc47762c9da3c867c5d76e23e32293fd0abeb5f4 Mon Sep 17 00:00:00 2001
From: mehekj <mehek.jethani@gmail.com>
Date: Wed, 21 Sep 2022 15:49:33 -0400
Subject: fixed image size on paste

---
 src/client/views/PreviewCursor.tsx | 21 +++++++++++----------
 1 file changed, 11 insertions(+), 10 deletions(-)

(limited to 'src/client/views/PreviewCursor.tsx')

diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index d56d2a310..119476210 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -6,6 +6,7 @@ import { Cast, NumCast, StrCast } from '../../fields/Types';
 import { returnFalse } from '../../Utils';
 import { DocServer } from '../DocServer';
 import { Docs, DocumentOptions, DocUtils } from '../documents/Documents';
+import { ImageUtils } from '../util/Import & Export/ImageUtils';
 import { Transform } from '../util/Transform';
 import { undoBatch, UndoManager } from '../util/UndoManager';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
@@ -109,16 +110,16 @@ export class PreviewCursor extends React.Component<{}> {
                 const re: any = /<img src="(.*?)"/g;
                 const arr: any[] = re.exec(e.clipboardData.getData('text/html'));
 
-                undoBatch(() =>
-                    PreviewCursor._addDocument(
-                        Docs.Create.ImageDocument(arr[1], {
-                            _width: 300,
-                            title: arr[1],
-                            x: newPoint[0],
-                            y: newPoint[1],
-                        })
-                    )
-                )();
+                undoBatch(() => {
+                    const doc = Docs.Create.ImageDocument(arr[1], {
+                        _width: 300,
+                        title: arr[1],
+                        x: newPoint[0],
+                        y: newPoint[1],
+                    });
+                    ImageUtils.ExtractExif(doc);
+                    PreviewCursor._addDocument(doc);
+                })();
             } else if (e.clipboardData.items.length) {
                 const batch = UndoManager.StartBatch('collection view drop');
                 const files: File[] = [];
-- 
cgit v1.2.3-70-g09d2


From b8d4b08716791246847d2a647a9df1f37508b87f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 8 Nov 2022 14:43:47 -0500
Subject: making pasting form pdf work again with backlinks. text fixes for
 equationViews and dashField views -- trying to get docref linkAnchor to work.

---
 src/client/views/LightboxView.tsx                  |  2 +-
 src/client/views/PreviewCursor.tsx                 |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 26 +++----
 src/client/views/nodes/DocumentContentsView.tsx    |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 .../views/nodes/formattedText/DashFieldView.scss   |  1 +
 .../views/nodes/formattedText/DashFieldView.tsx    |  6 +-
 .../views/nodes/formattedText/EquationView.tsx     | 12 ++-
 .../nodes/formattedText/FormattedTextBox.scss      |  4 +
 .../views/nodes/formattedText/FormattedTextBox.tsx | 87 ++++++++--------------
 src/client/views/nodes/formattedText/marks_rts.ts  | 18 +----
 src/client/views/pdf/PDFViewer.tsx                 |  4 +
 12 files changed, 75 insertions(+), 93 deletions(-)

(limited to 'src/client/views/PreviewCursor.tsx')

diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 22b0380a2..a9fba3688 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -292,7 +292,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                             PanelWidth={this.lightboxWidth}
                             PanelHeight={this.lightboxHeight}
                             LayoutTemplate={LightboxView.LightboxDocTemplate}
-                            isDocumentActive={returnFalse}
+                            isDocumentActive={returnTrue}
                             isContentActive={returnTrue}
                             styleProvider={DefaultStyleProvider}
                             ScreenToLocalTransform={this.lightboxScreenToLocal}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 119476210..3712fff58 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -100,9 +100,9 @@ export class PreviewCursor extends React.Component<{}> {
                     batch.end();
                     e.stopPropagation();
                 } else {
-                    // creates text document
                     FormattedTextBox.PasteOnLoad = e;
-                    UndoManager.RunInBatch(() => PreviewCursor._addLiveTextDoc(DocUtils.GetNewTextDoc('-pasted text-', newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), 'paste');
+                    if (e.clipboardData.getData('dash/pdfAnchor')) e.preventDefault();
+                    UndoManager.RunInBatch(() => PreviewCursor._addLiveTextDoc(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), 'paste');
                 }
             }
             //pasting in images
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ac3777aa6..b83d911c7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,5 +1,5 @@
 import { Bezier } from 'bezier-js';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import { DateField } from '../../../../fields/DateField';
@@ -1987,19 +1987,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 {this._firstRender ? this.placeholder : this.marqueeView}
                 {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
 
-                {
-                    // uncomment to show snap lines
-                    <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
-                        <svg style={{ width: '100%', height: '100%' }}>
-                            {this._hLines?.map(l => (
-                                <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />
-                            ))}
-                            {this._vLines?.map(l => (
-                                <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />
-                            ))}
-                        </svg>
-                    </div>
-                }
+                {/* // uncomment to show snap lines */}
+                <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
+                    <svg style={{ width: '100%', height: '100%' }}>
+                        {this._hLines?.map(l => (
+                            <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />
+                        ))}
+                        {this._vLines?.map(l => (
+                            <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />
+                        ))}
+                    </svg>
+                </div>
 
                 {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? (
                     <div
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 6d6609317..3ab7cc0bc 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,4 +1,4 @@
-import { computed } from 'mobx';
+import { computed, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { AclPrivate, Doc, Opt } from '../../../fields/Doc';
 import { ScriptField } from '../../../fields/ScriptField';
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a35400e72..9dc4b36cf 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,7 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
 import { Document } from '../../../fields/documentSchemas';
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index f17579853..ad315acc8 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -34,6 +34,7 @@
         display: inline-block;
         background-color: rgba(155, 155, 155, 0.24);
         span {
+            user-select: all;
             min-width: 100%;
             display: inline-block;
         }
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index afae59733..39b38add2 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -124,7 +124,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
                 return (
                     <span
                         className="dashFieldView-fieldSpan"
-                        contentEditable={this.props.editable}
+                        contentEditable={true}
                         style={{ display: strVal.length < 2 ? 'inline-block' : undefined }}
                         suppressContentEditableWarning={true}
                         defaultValue={strVal}
@@ -152,6 +152,10 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
     // we need to handle all key events on the input span or else they will propagate to prosemirror.
     @action
     fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => {
+        if (e.key === 'c' && (e.ctrlKey || e.metaKey)) {
+            navigator.clipboard.writeText(window.getSelection()?.toString() || '');
+            return;
+        }
         if (e.key === 'Enter') {
             // handle the enter key by "submitting" the current text to Dash's database.
             this.updateText(span.textContent!, true);
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index 0fd2a7808..89f2a7c81 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -1,5 +1,5 @@
 import EquationEditor from 'equation-editor-react';
-import { IReactionDisposer } from 'mobx';
+import { IReactionDisposer, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { TextSelection } from 'prosemirror-state';
 import * as ReactDOM from 'react-dom/client';
@@ -12,7 +12,11 @@ import React = require('react');
 export class EquationView {
     dom: HTMLDivElement; // container for label and value
     root: any;
+    tbox: FormattedTextBox;
+    view: any;
     constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        this.tbox = tbox;
+        this.view = view;
         this.dom = document.createElement('div');
         this.dom.style.width = node.attrs.width;
         this.dom.style.height = node.attrs.height;
@@ -34,7 +38,11 @@ export class EquationView {
         this._editor?.mathField.focus();
     }
     selectNode() {
-        this._editor?.mathField.focus();
+        this.tbox._applyingChange = this.tbox.fieldKey; // setting focus will make prosemirror lose focus, which will cause it to change its selection to a text selection, which causes this view to get rebuilt but it's no longer node selected, so the equationview won't have focus
+        setTimeout(() => {
+            this._editor?.mathField.focus();
+            setTimeout(() => (this.tbox._applyingChange = ''));
+        });
     }
     deselectNode() {}
 }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index d3d8c47c0..93dc0b2a1 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -225,6 +225,8 @@ footnote::after {
 
 .prosemirror-attribution {
     font-size: 8px;
+    float: right;
+    display: inline;
 }
 
 .footnote-tooltip::before {
@@ -740,6 +742,8 @@ footnote::after {
 
     .prosemirror-attribution {
         font-size: 8px;
+        float: right;
+        display: inline;
     }
 
     .footnote-tooltip::before {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 63435eea8..fc8d2b7dd 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,7 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { isEqual } from 'lodash';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { baseKeymap, selectAll } from 'prosemirror-commands';
 import { history } from 'prosemirror-history';
@@ -87,7 +87,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
     private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
     private _editorView: Opt<EditorView>;
-    private _applyingChange: string = '';
+    public _applyingChange: string = '';
     private _searchIndex = 0;
     private _lastTimedMark: Mark | undefined = undefined;
     private _cachedLinks: Doc[] = [];
@@ -499,12 +499,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             const end = this._editorView.state.doc.nodeSize - 2;
             this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
         }
-        if (FormattedTextBox.PasteOnLoad) {
-            const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfOrigin');
-            const pdfRegionId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfRegion');
-            FormattedTextBox.PasteOnLoad = undefined;
-            setTimeout(() => pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, undefined), 10);
-        }
     };
     adoptAnnotation = (start: number, end: number, mark: Mark) => {
         const view = this._editorView!;
@@ -1235,61 +1229,38 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
 
     handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
-        const cbe = event as ClipboardEvent;
-        const pdfDocId = cbe.clipboardData?.getData('dash/pdfOrigin');
-        const pdfRegionId = cbe.clipboardData?.getData('dash/pdfRegion');
-        return pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, slice) ? true : false;
+        const pdfAnchorId = (event as ClipboardEvent).clipboardData?.getData('dash/pdfAnchor');
+        return pdfAnchorId && this.addPdfReference(pdfAnchorId) ? true : false;
     };
 
-    addPdfReference = (pdfDocId: string, pdfRegionId: string, slice?: Slice) => {
+    addPdfReference = (pdfAnchorId: string) => {
         const view = this._editorView!;
-        if (pdfDocId && pdfRegionId) {
-            DocServer.GetRefField(pdfDocId).then(pdfDoc => {
-                DocServer.GetRefField(pdfRegionId).then(pdfRegion => {
-                    if (pdfDoc instanceof Doc && pdfRegion instanceof Doc) {
-                        setTimeout(async () => {
-                            const targetField = Doc.LayoutFieldKey(pdfDoc);
-                            const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + '-annotations']); // bcz: better to have the PDF's view handle updating its own annotations
-                            if (targetAnnotations) targetAnnotations.push(pdfRegion);
-                            else Doc.AddDocToList(pdfDoc[DataSym], targetField + '-annotations', pdfRegion);
-                        });
-
-                        const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, 'PDF pasted');
-                        if (link) {
-                            const linkId = link[Id];
-                            const quote = view.state.schema.nodes.blockquote.create({ content: addMarkToFrag(slice?.content || view.state.doc.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)) });
-                            const newSlice = new Slice(Fragment.from(quote), slice?.openStart || 0, slice?.openEnd || 0);
-                            if (slice) {
-                                view.dispatch(view.state.tr.replaceSelection(newSlice).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste'));
-                            } else {
-                                selectAll(view.state, (tx: Transaction) => view.dispatch(tx.replaceSelection(newSlice).scrollIntoView()));
-                            }
-                        }
+        if (pdfAnchorId) {
+            DocServer.GetRefField(pdfAnchorId).then(pdfAnchor => {
+                if (pdfAnchor instanceof Doc) {
+                    const dashField = view.state.schema.nodes.paragraph.create({}, [
+                        view.state.schema.nodes.dashField.create({ fieldKey: 'text', docid: pdfAnchor[Id], hideKey: true, editable: false }, undefined, [
+                            view.state.schema.marks.linkAnchor.create({
+                                allAnchors: [{ href: `/doc/${this.rootDoc[Id]}`, title: this.rootDoc.title, anchorId: `${this.rootDoc[Id]}` }],
+                                location: 'add:right',
+                                title: `from: ${DocCast(pdfAnchor.context).title}`,
+                                noPreview: true,
+                                docref: false,
+                            }),
+                            view.state.schema.marks.pFontSize.create({ fontSize: '8px' }),
+                            view.state.schema.marks.em.create({}),
+                        ]),
+                    ]);
+
+                    const link = DocUtils.MakeLink({ doc: pdfAnchor }, { doc: this.rootDoc }, 'PDF pasted');
+                    if (link) {
+                        view.dispatch(view.state.tr.replaceSelectionWith(dashField, false).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste'));
                     }
-                });
+                }
             });
             return true;
         }
         return false;
-
-        function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) {
-            const nodes: Node[] = [];
-            frag.forEach(node => nodes.push(marker(node)));
-            return Fragment.fromArray(nodes);
-        }
-
-        function addLinkMark(node: Node, title: string, linkId: string) {
-            if (!node.isText) {
-                const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));
-                return node.copy(content);
-            }
-            const marks = [...node.marks];
-            const linkIndex = marks.findIndex(mark => mark.type.name === 'link');
-            const allLinks = [{ href: Doc.globalServerPath(linkId), title, linkId }];
-            const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: 'add:right', title, docref: true });
-            marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
-            return node.mark(marks);
-        }
     };
 
     isActiveTab(el: Element | null | undefined) {
@@ -1417,6 +1388,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []),
                 ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []),
             ];
+            if (FormattedTextBox.PasteOnLoad) {
+                const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
+                FormattedTextBox.PasteOnLoad = undefined;
+                pdfAnchorId && this.addPdfReference(pdfAnchorId);
+            }
         }
         FormattedTextBox.DontSelectInitialText = false;
     }
@@ -1530,6 +1506,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         //applyDevTools.applyDevTools(this._editorView);
         FormattedTextBox.Focused = this;
         this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
+        this.startUndoTypingBatch();
     };
     @observable public static Focused: FormattedTextBox | undefined;
 
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 97549830c..8d43c33f0 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -97,12 +97,9 @@ export const marks: { [index: string]: MarkSpec } = {
             return node.attrs.docref && node.attrs.title
                 ? [
                       'div',
-                      ['span', `"`],
                       ['span', 0],
-                      ['span', `"`],
-                      ['br'],
                       [
-                          'a',
+                          'span',
                           {
                               ...node.attrs,
                               class: 'prosemirror-attribution',
@@ -110,19 +107,8 @@ export const marks: { [index: string]: MarkSpec } = {
                           },
                           node.attrs.title,
                       ],
-                      ['br'],
                   ]
-                : //node.attrs.allLinks.length === 1 ?
-                  ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, location: node.attrs.location, style: `text-decoration: underline` }, 0];
-            // ["div", { class: "prosemirror-anchor" },
-            //     ["span", { class: "prosemirror-linkBtn" },
-            //         ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0],
-            //         ["input", { class: "prosemirror-hrefoptions" }],
-            //     ],
-            //     ["div", { class: "prosemirror-links" }, ...node.attrs.allLinks.map((item: { href: string, title: string }) =>
-            //         ["a", { class: "prosemirror-dropdownlink", href: item.href }, item.title]
-            //     )]
-            // ];
+                : ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, location: node.attrs.location, style: `text-decoration: underline` }, 0];
         },
     },
 
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index abc7336bd..7f99c30e3 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -131,6 +131,10 @@ export class PDFViewer extends React.Component<IViewerProps> {
     copy = (e: ClipboardEvent) => {
         if (this.props.isContentActive() && e.clipboardData) {
             e.clipboardData.setData('text/plain', this._selectionText);
+            const anchor = this._getAnchor();
+            if (anchor) {
+                e.clipboardData.setData('dash/pdfAnchor', anchor[Id]);
+            }
             e.preventDefault();
         }
     };
-- 
cgit v1.2.3-70-g09d2


From 4ecac6f070759e79c254f4a356d02a871ac6abb5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 17 Mar 2023 09:24:34 -0400
Subject: fixed copy paste to clone links if both anchors are copied.  adjusted
 API for clone to have a cloneLinks field.

---
 src/client/views/GlobalKeyHandler.ts |  2 +-
 src/client/views/PreviewCursor.tsx   | 10 ++-----
 src/fields/Doc.ts                    | 51 ++++++++++++++++++++----------------
 3 files changed, 32 insertions(+), 31 deletions(-)

(limited to 'src/client/views/PreviewCursor.tsx')

diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 3fbf6e445..f849b21e3 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -372,7 +372,7 @@ export class KeyManager {
                                 list.push(doc);
                             }
                             if (count === docids.length) {
-                                const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => (clone ? (await Doc.MakeClone(d)).clone : d)));
+                                const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => (clone ? (await Doc.MakeClone(d, ['links'])).clone : d)));
                                 if (added.length) {
                                     added.map(doc => (doc.context = targetDataDoc));
                                     undoBatch(() => {
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 3712fff58..95ae65d7a 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -81,14 +81,8 @@ export class PreviewCursor extends React.Component<{}> {
 
                     const batch = UndoManager.StartBatch('cloning');
                     {
-                        const docs = await Promise.all(
-                            docids
-                                .filter((did, i) => i)
-                                .map(async did => {
-                                    const doc = Cast(await DocServer.GetRefField(did), Doc, null);
-                                    return clone ? (await Doc.MakeClone(doc)).clone : doc;
-                                })
-                        );
+                        const toCopy = await Promise.all(docids.slice(1).map(async did => Cast(await DocServer.GetRefField(did), Doc, null)));
+                        const docs = clone ? (await Promise.all(Doc.MakeClones(toCopy, false))).map(res => res.clone) : toCopy;
                         const firstx = docs.length ? NumCast(docs[0].x) + ptx - newPoint[0] : 0;
                         const firsty = docs.length ? NumCast(docs[0].y) + pty - newPoint[1] : 0;
                         docs.map(doc => {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index deda8aa1f..3169031b4 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -702,28 +702,28 @@ export namespace Doc {
         return bestAlias ?? Doc.MakeAlias(doc);
     }
 
-    export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<string, Doc>, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> {
+    export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<string, Doc>, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], cloneLinks: boolean, asBranch: boolean): Promise<Doc> {
         if (Doc.IsBaseProto(doc)) return doc;
         if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
-        const copy = dontCreate ? (asBranch ? Cast(doc.branchMaster, Doc, null) ?? doc : doc) : new Doc(undefined, true);
+        const copy = new Doc(undefined, true);
         cloneMap.set(doc[Id], copy);
         const filter = [...exclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])];
         await Promise.all(
             Object.keys(doc).map(async key => {
                 if (filter.includes(key)) return;
-                const assignKey = (val: any) => !dontCreate && (copy[key] = val);
+                const assignKey = (val: any) => (copy[key] = val);
                 const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
                 const field = ProxyField.WithoutProxy(() => doc[key]);
                 const copyObjectField = async (field: ObjectField) => {
                     const list = await Cast(doc[key], listSpec(Doc));
                     const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
                     if (docs !== undefined && docs.length) {
-                        const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)));
-                        !dontCreate && assignKey(new List<Doc>(clones));
+                        const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch)));
+                        assignKey(new List<Doc>(clones));
                     } else if (doc[key] instanceof Doc) {
-                        assignKey(key.includes('layout[') ? undefined : key.startsWith('layout') ? (doc[key] as Doc) : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields
+                        assignKey(key.includes('layout[') ? undefined : key.startsWith('layout') ? (doc[key] as Doc) : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch)); // reference documents except copy documents that are expanded template fields
                     } else {
-                        !dontCreate && assignKey(ObjectField.MakeCopy(field));
+                        assignKey(ObjectField.MakeCopy(field));
                         if (field instanceof RichTextField) {
                             if (field.Data.includes('"audioId":') || field.Data.includes('"textId":') || field.Data.includes('"anchorId":')) {
                                 rtfs.push({ copy, key, field });
@@ -734,7 +734,7 @@ export namespace Doc {
                 const docAtKey = doc[key];
                 if (docAtKey instanceof Doc) {
                     if (!Doc.IsSystem(docAtKey) && (key === 'annotationOn' || key === 'proto' || ((key === 'anchor1' || key === 'anchor2') && doc.author === Doc.CurrentUserEmail))) {
-                        assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch));
+                        assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch));
                     } else {
                         assignKey(docAtKey);
                     }
@@ -742,7 +742,7 @@ export namespace Doc {
                     if (field instanceof RefField) {
                         assignKey(field);
                     } else if (cfield instanceof ComputedField) {
-                        !dontCreate && assignKey(cfield[Copy]());
+                        assignKey(cfield[Copy]());
                         //   ComputedField.MakeFunction(cfield.script.originalScript));
                     } else if (field instanceof ObjectField) {
                         await copyObjectField(field);
@@ -754,18 +754,21 @@ export namespace Doc {
                 }
             })
         );
-        for (const link of Array.from(doc[DirectLinksSym])) {
-            const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch);
-            linkMap.set(link[Id], linkClone);
-        }
-        Doc.SetInPlace(copy, 'title', (asBranch ? 'BRANCH: ' : 'CLONE: ') + doc.title, true);
-        if (!dontCreate) {
-            asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc);
-            if (!Doc.IsPrototype(copy)) {
-                Doc.AddDocToList(doc, 'branches', Doc.GetProto(copy));
+        Array.from(doc[DirectLinksSym]).forEach(async link => {
+            if (
+                cloneLinks ||
+                ((cloneMap.has(DocCast(link.anchor1)?.[Id]) || cloneMap.has(DocCast(DocCast(link.anchor1)?.annotationOn)?.[Id])) && (cloneMap.has(DocCast(link.anchor2)?.[Id]) || cloneMap.has(DocCast(DocCast(link.anchor2)?.annotationOn)?.[Id])))
+            ) {
+                linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch));
             }
-            cloneMap.set(doc[Id], copy);
+        });
+        Doc.SetInPlace(copy, 'title', (asBranch ? 'BRANCH: ' : 'CLONE: ') + doc.title, true);
+        asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc);
+        if (!Doc.IsPrototype(copy)) {
+            Doc.AddDocToList(doc, 'branches', Doc.GetProto(copy));
         }
+        cloneMap.set(doc[Id], copy);
+
         Doc.AddFileOrphan(copy);
         return copy;
     }
@@ -787,10 +790,14 @@ export namespace Doc {
                 }
             });
     }
-    export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map<string, Doc> = new Map()) {
+    export function MakeClones(docs: Doc[], cloneLinks: boolean, asBranch = false, cloneMap: Map<string, Doc> = new Map()) {
+        return docs.map(doc => Doc.MakeClone(doc, cloneLinks, asBranch, cloneMap));
+    }
+
+    export async function MakeClone(doc: Doc, cloneLinks = true, asBranch = false, cloneMap: Map<string, Doc> = new Map()) {
         const linkMap = new Map<string, Doc>();
         const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = [];
-        const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], dontCreate, asBranch);
+        const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], cloneLinks, asBranch);
         const repaired = new Set<Doc>();
         const linkedDocs = Array.from(linkMap.values());
         const clonedDocs = [...Array.from(cloneMap.values()), ...linkedDocs];
@@ -818,7 +825,7 @@ export namespace Doc {
         // a.href = url;
         // a.download = `DocExport-${this.props.Document[Id]}.zip`;
         // a.click();
-        const { clone, map, linkMap } = await Doc.MakeClone(doc, false);
+        const { clone, map, linkMap } = await Doc.MakeClone(doc);
         clone.LINKS = new List<Doc>(Array.from(linkMap.values()));
         const proms = [] as string[];
         function replacer(key: any, value: any) {
-- 
cgit v1.2.3-70-g09d2

