From 726dfb8fea45352b2eb0729ad6d3b4a7b0824e1a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 6 Mar 2021 16:34:44 -0500
Subject: updated a bunch of _backgroundColors to backgroundColors

---
 src/client/views/collections/CollectionView.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a217de193..18abd3a88 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -154,7 +154,7 @@ export class CollectionView extends Touchable<CollectionViewProps> {
                         if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
                             const pushpin = Docs.Create.FontIconDocument({
                                 title: "pushpin", label: "", annotationOn: Cast(doc.annotationOn, Doc, null), isPushpin: true,
-                                icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7",
+                                icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), backgroundColor: "#0000003d", color: "#ACCEF7",
                                 _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, _timecodeToShow: Cast(doc._timecodeToShow, "number", null)
                             });
                             Doc.SetInPlace(doc, "annotationOn", undefined, true);
-- 
cgit v1.2.3-70-g09d2


From f23e497d5ad05b9af0f90b69f9a383deed0c1e39 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 7 Mar 2021 21:20:53 -0500
Subject: added start of Git functionality within Dash.

---
 src/client/views/DocumentDecorations.tsx        | 13 ++---
 src/client/views/collections/CollectionView.tsx | 71 ++++++++++++++++++++++++-
 src/fields/Doc.ts                               | 25 ++++-----
 3 files changed, 90 insertions(+), 19 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 44d4460fa..aeb2d582b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -10,7 +10,7 @@ import { InkField } from "../../fields/InkField";
 import { ScriptField } from '../../fields/ScriptField';
 import { Cast, NumCast } from "../../fields/Types";
 import { GetEffectiveAcl } from '../../fields/util';
-import { setupMoveUpEvents, emptyFunction } from "../../Utils";
+import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils";
 import { Docs, DocUtils } from "../documents/Documents";
 import { DocumentType } from '../documents/DocumentTypes';
 import { CurrentUserUtils } from '../util/CurrentUserUtils';
@@ -129,7 +129,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
     }
     @undoBatch
     @action
-    onMaximizeClick = (e: React.MouseEvent): void => {
+    onMaximizeClick = (e: any): void => {
         const selectedDocs = SelectionManager.Views();
         if (selectedDocs.length) {
             if (e.ctrlKey) {    // open an alias in a new tab with Ctrl Key
@@ -152,12 +152,12 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
     }
 
     @undoBatch
-    onIconifyClick = (e: React.MouseEvent): void => {
+    onIconifyClick = (): void => {
         SelectionManager.Views().forEach(dv => dv?.iconify());
         SelectionManager.DeselectAll();
     }
 
-    onSelectorClick = (e: React.MouseEvent) => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false);
+    onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false);
 
     onRadiusDown = (e: React.PointerEvent): void => {
         this._resizeUndo = UndoManager.StartBatch("DocDecs set radius");
@@ -413,9 +413,10 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
             return (!docView.rootDoc._stayInCollection || docView.rootDoc.isInkMask) &&
                 (collectionAcl === AclAdmin || collectionAcl === AclEdit || GetEffectiveAcl(docView.rootDoc) === AclAdmin);
         });
-        const topBtn = (key: string, icon: string, click: (e: React.MouseEvent) => void, title: string) => (
+        const topBtn = (key: string, icon: string, click: (e: any) => void, title: string) => (
             <Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
-                <div className={`documentDecorations-${key}Button`} onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} onClick={click}>
+                <div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()}
+                    onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, click, emptyFunction)} >
                     <FontAwesomeIcon icon={icon as any} />
                 </div>
             </Tooltip>);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 18abd3a88..b8fc7786d 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
 import * as React from 'react';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast } from '../../../fields/Doc';
+import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast, DocListCastAsync } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -277,6 +277,65 @@ export class CollectionView extends Touchable<CollectionViewProps> {
         }
     }
 
+    // pulls documents onto a branch from the branch's master
+    // if a document exists on master but not on the branch, it is branched and added
+    // NOTE/TODO: if a document is deleted on master, pulling master should delete the document from the branch
+    //
+    static pullFromMaster = async (branch: Doc, suffix = "") => {
+        const masterMain = Cast(branch.branchOf, Doc, null);
+        // get the set of documents on both the branch and master
+        const masterMainDocs = await DocListCastAsync(masterMain[Doc.LayoutFieldKey(masterMain) + suffix]);
+        const branchMainDocs = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]);
+        // get the master documents that correspond to the branch documents
+        const branchMasterMainDocs = branchMainDocs?.map(bd => Cast(bd.branchOf, Doc, null) || bd).map(doc => Doc.GetProto(doc));
+        // get documents on master that don't have a corresponding master doc (form a branch doc), and ...
+        const newDocsFromMaster = masterMainDocs?.filter(md => !branchMasterMainDocs?.includes(Doc.GetProto(md)));
+        // make branch clones of them, then add them to the branch
+        const newlyBranchedDocs = await Promise.all(newDocsFromMaster?.map(async md => (await Doc.MakeClone(md, false, true)).clone) || []);
+        newlyBranchedDocs.forEach(nd => Doc.AddDocToList(branch, Doc.LayoutFieldKey(branch) + suffix, nd));
+    }
+
+    // merges all branches from the master branch by first merging the top-level collection of documents, 
+    // and then merging all the annotations on those documents.
+    // NOTE: "merging" only means making sure that documents in the branches are on master -- it does not
+    //           currently update the state of those documents to be identical.
+    // TODO: deleting a document on a branch should remove it from master (but doesn't yet).
+    static mergeWithMaster = async (master: Doc, suffix = "") => {
+        const branches = await DocListCastAsync(master.branches);
+        branches?.map(async branch => {
+            const branchChildren = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]);
+            branchChildren?.forEach(async bd => {
+                // see if the branch's child exists on master.  
+                const masterChild = Cast(bd.branchOf, Doc, null) || (await Doc.MakeClone(bd, false, true)).clone;
+                // if the branch's child didn't exist on master, we make a branch clone of the child to add to master.  
+                // however, since master is supposed to have the "main" clone, and branches, the "branch" clones, we have to reverse the fields
+                // on the branch child and master clone.
+                if (masterChild.branchOf) {
+                    const branchDocProto = Doc.GetProto(bd);
+                    const masterChildProto = Doc.GetProto(masterChild);
+                    masterChildProto.branchOf = undefined; // the master child should not be a branch of the branch child, so unset 'branchOf'
+                    masterChildProto.branches = new List<Doc>([bd]); // the master child's branches needs to include the branch child
+                    Doc.RemoveDocFromList(branchDocProto, "branches", masterChildProto);   // the branch child should not have the master child in its branch list.
+                    branchDocProto.branchOf = masterChild;                                 // the branch child is now a branch of the master child
+                }
+                Doc.AddDocToList(master, Doc.LayoutFieldKey(master) + suffix, masterChild); // add the masterChild to master (if it's already there, this is a no-op)
+            });
+        });
+    }
+
+    // performs a "git"-like task: pull or merge
+    //    if pull, then target is a specific branch document that will be updated from its associated master
+    //    if merge, then target is the master doc that will merge in all branches associated with it.
+    // TODO: parameterize 'merge' to specify which branch(es) should be merged.   
+    //       extend 'merge' to allow a specific branch to be merge target (not just master);
+    //       make pull/merge be recursive (ie, this func currently just operates on the main doc and its children)
+    static async GitTask(target: Doc, action: "pull" | "merge") {
+        const func = action === "pull" ? CollectionView.pullFromMaster : CollectionView.mergeWithMaster;
+        await func(target, "");
+        const targetChildren = await DocListCast(target[Doc.LayoutFieldKey(target)]);
+        targetChildren.forEach(async targetChild => await func(targetChild, "-annotations"));
+    }
+
     onContextMenu = (e: React.MouseEvent): void => {
         const cm = ContextMenu.Instance;
         if (cm && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
@@ -298,6 +357,16 @@ export class CollectionView extends Touchable<CollectionViewProps> {
             }
             !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
+            optionItems.push({
+                description: "Create Branch", event: async () => this.props.addDocTab((await Doc.MakeClone(this.props.Document, false, true)).clone, "add:right"), icon: "project-diagram"
+            });
+            optionItems.push({
+                description: "Pull Master", event: () => CollectionView.GitTask(this.props.Document, "pull"), icon: "project-diagram"
+            });
+            optionItems.push({
+                description: "Merge Branches", event: () => CollectionView.GitTask(this.props.Document, "merge"), icon: "project-diagram"
+            });
+
             !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" });
 
             if (!Doc.UserDoc().noviceMode && !this.props.Document.annotationOn) {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index e24821116..c82c05c28 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -503,13 +503,13 @@ export namespace Doc {
         return alias;
     }
 
-    export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[], dontCreate: boolean): Promise<Doc> {
+    export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> {
         if (Doc.IsBaseProto(doc)) return doc;
         if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
-        const copy = dontCreate ? doc : new Doc(undefined, true);
+        const copy = dontCreate ? asBranch ? (Cast(doc.branchMaster, Doc, null) || doc) : doc : new Doc(undefined, true);
         cloneMap.set(doc[Id], copy);
         if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy);
-        const filter = Cast(doc.cloneFieldFilter, listSpec("string"), exclusions);
+        const filter = Cast(doc.cloneFieldFilter, listSpec("string"), ["branches", ...exclusions]);
         await Promise.all(Object.keys(doc).map(async key => {
             if (filter.includes(key)) return;
             const assignKey = (val: any) => !dontCreate && (copy[key] = val);
@@ -519,12 +519,12 @@ export namespace Doc {
                 const list = 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, rtfs, exclusions, dontCreate)));
+                    const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, rtfs, exclusions, dontCreate, asBranch)));
                     !dontCreate && 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, rtfs, exclusions, dontCreate)); // reference documents except copy documents that are expanded teplate fields 
+                    assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded teplate fields 
                 } else {
-                    assignKey(ObjectField.MakeCopy(field));
+                    !dontCreate && assignKey(ObjectField.MakeCopy(field));
                     if (field instanceof RichTextField) {
                         if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) {
                             rtfs.push({ copy, key, field });
@@ -534,7 +534,7 @@ export namespace Doc {
             };
             if (key === "proto") {
                 if (doc[key] instanceof Doc) {
-                    assignKey(await Doc.makeClone(doc[key]!, cloneMap, rtfs, exclusions, dontCreate));
+                    assignKey(await Doc.makeClone(doc[key]!, cloneMap, rtfs, exclusions, dontCreate, asBranch));
                 }
             } else {
                 if (field instanceof RefField) {
@@ -552,16 +552,17 @@ export namespace Doc {
             }
         }));
         if (!dontCreate) {
-            Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true);
-            copy.cloneOf = doc;
+            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);
         }
         return copy;
     }
-    export async function MakeClone(doc: Doc, dontCreate: boolean = false) {
+    export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false) {
         const cloneMap = new Map<string, Doc>();
         const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
-        const copy = await Doc.makeClone(doc, cloneMap, rtfMap, ["context", "annotationOn", "cloneOf"], dontCreate);
+        const copy = await Doc.makeClone(doc, cloneMap, rtfMap, ["context", "annotationOn", "cloneOf", "branchOf"], dontCreate, asBranch);
         rtfMap.map(({ copy, key, field }) => {
             const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
                 const mapped = cloneMap.get(id);
@@ -586,7 +587,7 @@ export namespace Doc {
         // a.click();
         const { clone, map } = await Doc.MakeClone(doc, true);
         function replacer(key: any, value: any) {
-            if (["cloneOf", "context", "cursors"].includes(key)) return undefined;
+            if (["branchOf", "cloneOf", "context", "cursors"].includes(key)) return undefined;
             else if (value instanceof Doc) {
                 if (key !== "field" && Number.isNaN(Number(key))) {
                     const __fields = value[FieldsSym]();
-- 
cgit v1.2.3-70-g09d2


From ca6e8145c5883e32f4cc65edbfff245cdde326ed Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 7 Mar 2021 23:22:39 -0500
Subject: added removal of docs to git-like actions.

---
 src/client/views/DocComponent.tsx               |  1 +
 src/client/views/collections/CollectionView.tsx | 29 +++++++++++++++++++------
 2 files changed, 23 insertions(+), 7 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 128ba858f..6480c6507 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -144,6 +144,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
                     const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc;
                     toRemove.forEach(doc => {
                         Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
+                        doc.context = undefined;
                         recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
                     });
                     this.props.select(false);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index b8fc7786d..c1db8fd0b 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -279,27 +279,34 @@ export class CollectionView extends Touchable<CollectionViewProps> {
 
     // pulls documents onto a branch from the branch's master
     // if a document exists on master but not on the branch, it is branched and added
-    // NOTE/TODO: if a document is deleted on master, pulling master should delete the document from the branch
-    //
+    // NOTE: need to set a timestamp on the branch that is equal to the master's last merge timestamp.
     static pullFromMaster = async (branch: Doc, suffix = "") => {
         const masterMain = Cast(branch.branchOf, Doc, null);
         // get the set of documents on both the branch and master
-        const masterMainDocs = await DocListCastAsync(masterMain[Doc.LayoutFieldKey(masterMain) + suffix]);
+        const masterMainDocs = masterMain && await DocListCastAsync(masterMain[Doc.LayoutFieldKey(masterMain) + suffix]);
         const branchMainDocs = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]);
         // get the master documents that correspond to the branch documents
-        const branchMasterMainDocs = branchMainDocs?.map(bd => Cast(bd.branchOf, Doc, null) || bd).map(doc => Doc.GetProto(doc));
+        const branchMasterMainDocs = branchMainDocs?.map(bd => Cast(bd.branchOf, Doc, null) || bd);
+        const branchMasterMainDocProtos = branchMasterMainDocs?.map(doc => Doc.GetProto(doc));
         // get documents on master that don't have a corresponding master doc (form a branch doc), and ...
-        const newDocsFromMaster = masterMainDocs?.filter(md => !branchMasterMainDocs?.includes(Doc.GetProto(md)));
+        const newDocsFromMaster = masterMainDocs?.filter(md => !branchMasterMainDocProtos?.includes(Doc.GetProto(md)));
         // make branch clones of them, then add them to the branch
         const newlyBranchedDocs = await Promise.all(newDocsFromMaster?.map(async md => (await Doc.MakeClone(md, false, true)).clone) || []);
-        newlyBranchedDocs.forEach(nd => Doc.AddDocToList(branch, Doc.LayoutFieldKey(branch) + suffix, nd));
+        newlyBranchedDocs.forEach(nd => {
+            Doc.AddDocToList(branch, Doc.LayoutFieldKey(branch) + suffix, nd);
+            nd.context = branch;
+        });
+        // if a branch doc's corresponding main branch doc doesn't have a context, then it was deleted.  
+        const remDocsFromMaster = branchMainDocs?.filter(bd => Cast(bd.branchOf, Doc, null) && !Cast(bd.branchOf, Doc, null)?.context);
+        // so then remove all the deleted main docs from this branch.
+        remDocsFromMaster?.forEach(rd => Doc.RemoveDocFromList(branch, Doc.LayoutFieldKey(branch) + suffix, rd));
     }
 
     // merges all branches from the master branch by first merging the top-level collection of documents, 
     // and then merging all the annotations on those documents.
     // NOTE: "merging" only means making sure that documents in the branches are on master -- it does not
     //           currently update the state of those documents to be identical.
-    // TODO: deleting a document on a branch should remove it from master (but doesn't yet).
+    // TODO: need to add an incrementing timestamp whenever anything merges.  don't allow a branch to merge if it's last pull timestamp isn't equal to the last merge timestamp.
     static mergeWithMaster = async (master: Doc, suffix = "") => {
         const branches = await DocListCastAsync(master.branches);
         branches?.map(async branch => {
@@ -319,6 +326,14 @@ export class CollectionView extends Touchable<CollectionViewProps> {
                     branchDocProto.branchOf = masterChild;                                 // the branch child is now a branch of the master child
                 }
                 Doc.AddDocToList(master, Doc.LayoutFieldKey(master) + suffix, masterChild); // add the masterChild to master (if it's already there, this is a no-op)
+                masterChild.context = master;
+            });
+            const masterChildren = await DocListCastAsync(master[Doc.LayoutFieldKey(master) + suffix]);
+            masterChildren?.forEach(async mc => {                      // see if any master children
+                if (!branchChildren?.find(bc => bc.branchOf === mc)) { //    are not in the list of children for this branch. 
+                    Doc.RemoveDocFromList(master, Doc.LayoutFieldKey(master) + suffix, mc); // if so, delete the master child since the branch has deleted it.
+                    mc.context = undefined;      // NOTE if we merge a branch that didn't do a pull, it will look like the branch deleted documents -- need edit timestamps that prevent merging if branch isn't up-to-date with last edit timestamp
+                }
             });
         });
     }
-- 
cgit v1.2.3-70-g09d2


From 94f2e575b95c6edd5410d440f7410d712c90b5db Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 8 Mar 2021 10:00:36 -0500
Subject: added doc data content synching to Git-like operations.

---
 src/client/util/CurrentUserUtils.ts             |  2 +-
 src/client/util/GroupManager.tsx                |  8 ++++----
 src/client/views/collections/CollectionView.tsx | 27 +++++++++++++++++++++----
 3 files changed, 28 insertions(+), 9 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 8b425f929..ccdfeed5f 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -988,7 +988,7 @@ export class CurrentUserUtils {
     static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
         if (!doc.globalGroupDatabase) doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument();
         const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data);
-        reaction(() => DateCast((doc.globalGroupDatabase as Doc).lastModified),
+        reaction(() => DateCast((doc.globalGroupDatabase as Doc)["data-lastModified"]),
             async () => {
                 const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data);
                 const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || [];
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 6458de0ed..68af67a3b 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -146,7 +146,7 @@ export class GroupManager extends React.Component<{}> {
     addGroup(groupDoc: Doc): boolean {
         if (this.GroupManagerDoc) {
             Doc.AddDocToList(this.GroupManagerDoc, "data", groupDoc);
-            this.GroupManagerDoc.lastModified = new DateField;
+            this.GroupManagerDoc["data-lastModified"] = new DateField;
             return true;
         }
         return false;
@@ -167,7 +167,7 @@ export class GroupManager extends React.Component<{}> {
                     const index = DocListCast(this.GroupManagerDoc.data).findIndex(grp => grp === group);
                     index !== -1 && Cast(this.GroupManagerDoc.data, listSpec(Doc), [])?.splice(index, 1);
                 }
-                this.GroupManagerDoc.lastModified = new DateField;
+                this.GroupManagerDoc["data-lastModified"] = new DateField;
                 if (group === this.currentGroup) {
                     this.currentGroup = undefined;
                 }
@@ -188,7 +188,7 @@ export class GroupManager extends React.Component<{}> {
             !memberList.includes(email) && memberList.push(email);
             groupDoc.members = JSON.stringify(memberList);
             SharingManager.Instance.shareWithAddedMember(groupDoc, email);
-            this.GroupManagerDoc && (this.GroupManagerDoc.lastModified = new DateField);
+            this.GroupManagerDoc && (this.GroupManagerDoc["data-lastModified"] = new DateField);
         }
     }
 
@@ -205,7 +205,7 @@ export class GroupManager extends React.Component<{}> {
                 const user = memberList.splice(index, 1)[0];
                 groupDoc.members = JSON.stringify(memberList);
                 SharingManager.Instance.removeMember(groupDoc, email);
-                this.GroupManagerDoc && (this.GroupManagerDoc.lastModified = new DateField);
+                this.GroupManagerDoc && (this.GroupManagerDoc["data-lastModified"] = new DateField);
             }
         }
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index c1db8fd0b..5d1e8b41c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -3,12 +3,12 @@ import { observer } from "mobx-react";
 import * as React from 'react';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast, DocListCastAsync } from '../../../fields/Doc';
+import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast, DocListCastAsync, Field } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { ScriptField } from '../../../fields/ScriptField';
-import { Cast, ScriptCast, StrCast } from '../../../fields/Types';
+import { Cast, ScriptCast, StrCast, DateCast } from '../../../fields/Types';
 import { denormalizeEmail, distributeAcls, GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
 import { returnFalse } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -277,6 +277,21 @@ export class CollectionView extends Touchable<CollectionViewProps> {
         }
     }
 
+    // synchs matching documents on the two branches that are being merged/pulled
+    // currently this just synchs the main 'fieldKey' component of the data since 
+    // we don't have individual timestamps for all fields -- this is a problematic design issue.
+    static synchDocs(bd: Doc, md: Doc) {
+        const fieldKey = Doc.LayoutFieldKey(md);
+        let bdate = DateCast(bd[`${fieldKey}-lastModified`])?.date;
+        let mdate = DateCast(md[`${fieldKey}-lastModified`])?.date;
+        if (bdate === mdate || bdate > mdate) return;
+        const bdproto = bd && Doc.GetProto(bd);
+        if (bdproto && md) {
+            bdproto[fieldKey] = ObjectField.MakeCopy(md[fieldKey] as ObjectField);
+            bdproto[`${fieldKey}-lastModified`] = ObjectField.MakeCopy(md[`${fieldKey}-lastModified`] as ObjectField);
+        }
+    }
+
     // pulls documents onto a branch from the branch's master
     // if a document exists on master but not on the branch, it is branched and added
     // NOTE: need to set a timestamp on the branch that is equal to the master's last merge timestamp.
@@ -290,6 +305,11 @@ export class CollectionView extends Touchable<CollectionViewProps> {
         const branchMasterMainDocProtos = branchMasterMainDocs?.map(doc => Doc.GetProto(doc));
         // get documents on master that don't have a corresponding master doc (form a branch doc), and ...
         const newDocsFromMaster = masterMainDocs?.filter(md => !branchMasterMainDocProtos?.includes(Doc.GetProto(md)));
+        const oldDocsFromMaster = masterMainDocs?.filter(md => branchMasterMainDocProtos?.includes(Doc.GetProto(md)));
+        oldDocsFromMaster?.forEach(md => {
+            const bd = branchMainDocs?.find(bd => (Cast(bd.branchOf, Doc, null) || bd) === md);
+            bd && CollectionView.synchDocs(bd, md);
+        })
         // make branch clones of them, then add them to the branch
         const newlyBranchedDocs = await Promise.all(newDocsFromMaster?.map(async md => (await Doc.MakeClone(md, false, true)).clone) || []);
         newlyBranchedDocs.forEach(nd => {
@@ -304,8 +324,6 @@ export class CollectionView extends Touchable<CollectionViewProps> {
 
     // merges all branches from the master branch by first merging the top-level collection of documents, 
     // and then merging all the annotations on those documents.
-    // NOTE: "merging" only means making sure that documents in the branches are on master -- it does not
-    //           currently update the state of those documents to be identical.
     // TODO: need to add an incrementing timestamp whenever anything merges.  don't allow a branch to merge if it's last pull timestamp isn't equal to the last merge timestamp.
     static mergeWithMaster = async (master: Doc, suffix = "") => {
         const branches = await DocListCastAsync(master.branches);
@@ -327,6 +345,7 @@ export class CollectionView extends Touchable<CollectionViewProps> {
                 }
                 Doc.AddDocToList(master, Doc.LayoutFieldKey(master) + suffix, masterChild); // add the masterChild to master (if it's already there, this is a no-op)
                 masterChild.context = master;
+                CollectionView.synchDocs(Doc.GetProto(masterChild), bd);
             });
             const masterChildren = await DocListCastAsync(master[Doc.LayoutFieldKey(master) + suffix]);
             masterChildren?.forEach(async mc => {                      // see if any master children
-- 
cgit v1.2.3-70-g09d2


From 17da55a9b6225ee656aa2eb4dc167dc2d60cba52 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 8 Mar 2021 10:30:24 -0500
Subject: split gitlike stuff into its own file

---
 src/client/documents/Gitlike.ts                 | 101 ++++++++++++++++++++++++
 src/client/views/collections/CollectionView.tsx | 100 +----------------------
 2 files changed, 105 insertions(+), 96 deletions(-)
 create mode 100644 src/client/documents/Gitlike.ts

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/documents/Gitlike.ts b/src/client/documents/Gitlike.ts
new file mode 100644
index 000000000..0ab2df8cd
--- /dev/null
+++ b/src/client/documents/Gitlike.ts
@@ -0,0 +1,101 @@
+import { Doc, DocListCast, DocListCastAsync } from "../../fields/Doc";
+import { List } from "../../fields/List";
+import { ObjectField } from "../../fields/ObjectField";
+import { Cast, DateCast } from "../../fields/Types";
+
+// synchs matching documents on the two branches that are being merged/pulled
+// currently this just synchs the main 'fieldKey' component of the data since 
+// we don't have individual timestamps for all fields -- this is a problematic design issue.
+function GitlikeSynchDocs(bd: Doc, md: Doc) {
+    const fieldKey = Doc.LayoutFieldKey(md);
+    let bdate = DateCast(bd[`${fieldKey}-lastModified`])?.date;
+    let mdate = DateCast(md[`${fieldKey}-lastModified`])?.date;
+    if (bdate === mdate || bdate > mdate) return;
+    const bdproto = bd && Doc.GetProto(bd);
+    if (bdproto && md) {
+        bdproto[fieldKey] = ObjectField.MakeCopy(md[fieldKey] as ObjectField);
+        bdproto[`${fieldKey}-lastModified`] = ObjectField.MakeCopy(md[`${fieldKey}-lastModified`] as ObjectField);
+    }
+}
+
+// pulls documents onto a branch from the branch's master
+// if a document exists on master but not on the branch, it is branched and added
+// NOTE: need to set a timestamp on the branch that is equal to the master's last merge timestamp.
+async function GitlikePullFromMaster(branch: Doc, suffix = "") {
+    const masterMain = Cast(branch.branchOf, Doc, null);
+    // get the set of documents on both the branch and master
+    const masterMainDocs = masterMain && await DocListCastAsync(masterMain[Doc.LayoutFieldKey(masterMain) + suffix]);
+    const branchMainDocs = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]);
+    // get the master documents that correspond to the branch documents
+    const branchMasterMainDocs = branchMainDocs?.map(bd => Cast(bd.branchOf, Doc, null) || bd);
+    const branchMasterMainDocProtos = branchMasterMainDocs?.map(doc => Doc.GetProto(doc));
+    // get documents on master that don't have a corresponding master doc (form a branch doc), and ...
+    const newDocsFromMaster = masterMainDocs?.filter(md => !branchMasterMainDocProtos?.includes(Doc.GetProto(md)));
+    const oldDocsFromMaster = masterMainDocs?.filter(md => branchMasterMainDocProtos?.includes(Doc.GetProto(md)));
+    oldDocsFromMaster?.forEach(md => {
+        const bd = branchMainDocs?.find(bd => (Cast(bd.branchOf, Doc, null) || bd) === md);
+        bd && GitlikeSynchDocs(bd, md);
+    })
+    // make branch clones of them, then add them to the branch
+    const newlyBranchedDocs = await Promise.all(newDocsFromMaster?.map(async md => (await Doc.MakeClone(md, false, true)).clone) || []);
+    newlyBranchedDocs.forEach(nd => {
+        Doc.AddDocToList(branch, Doc.LayoutFieldKey(branch) + suffix, nd);
+        nd.context = branch;
+    });
+    // if a branch doc's corresponding main branch doc doesn't have a context, then it was deleted.  
+    const remDocsFromMaster = branchMainDocs?.filter(bd => Cast(bd.branchOf, Doc, null) && !Cast(bd.branchOf, Doc, null)?.context);
+    // so then remove all the deleted main docs from this branch.
+    remDocsFromMaster?.forEach(rd => Doc.RemoveDocFromList(branch, Doc.LayoutFieldKey(branch) + suffix, rd));
+}
+
+// merges all branches from the master branch by first merging the top-level collection of documents, 
+// and then merging all the annotations on those documents.
+// TODO: need to add an incrementing timestamp whenever anything merges.  don't allow a branch to merge if it's last pull timestamp isn't equal to the last merge timestamp.
+async function GitlikeMergeWithMaster(master: Doc, suffix = "") {
+    const branches = await DocListCastAsync(master.branches);
+    branches?.map(async branch => {
+        const branchChildren = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]);
+        branchChildren?.forEach(async bd => {
+            // see if the branch's child exists on master.  
+            const masterChild = Cast(bd.branchOf, Doc, null) || (await Doc.MakeClone(bd, false, true)).clone;
+            // if the branch's child didn't exist on master, we make a branch clone of the child to add to master.  
+            // however, since master is supposed to have the "main" clone, and branches, the "branch" clones, we have to reverse the fields
+            // on the branch child and master clone.
+            if (masterChild.branchOf) {
+                const branchDocProto = Doc.GetProto(bd);
+                const masterChildProto = Doc.GetProto(masterChild);
+                masterChildProto.branchOf = undefined; // the master child should not be a branch of the branch child, so unset 'branchOf'
+                masterChildProto.branches = new List<Doc>([bd]); // the master child's branches needs to include the branch child
+                Doc.RemoveDocFromList(branchDocProto, "branches", masterChildProto);   // the branch child should not have the master child in its branch list.
+                branchDocProto.branchOf = masterChild;                                 // the branch child is now a branch of the master child
+            }
+            Doc.AddDocToList(master, Doc.LayoutFieldKey(master) + suffix, masterChild); // add the masterChild to master (if it's already there, this is a no-op)
+            masterChild.context = master;
+            GitlikeSynchDocs(Doc.GetProto(masterChild), bd);
+        });
+        const masterChildren = await DocListCastAsync(master[Doc.LayoutFieldKey(master) + suffix]);
+        masterChildren?.forEach(async mc => {                      // see if any master children
+            if (!branchChildren?.find(bc => bc.branchOf === mc)) { //    are not in the list of children for this branch. 
+                Doc.RemoveDocFromList(master, Doc.LayoutFieldKey(master) + suffix, mc); // if so, delete the master child since the branch has deleted it.
+                mc.context = undefined;      // NOTE if we merge a branch that didn't do a pull, it will look like the branch deleted documents -- need edit timestamps that prevent merging if branch isn't up-to-date with last edit timestamp
+            }
+        });
+    });
+}
+
+// performs a "git"-like task: pull or merge
+//    if pull, then target is a specific branch document that will be updated from its associated master
+//    if merge, then target is the master doc that will merge in all branches associated with it.
+// TODO: parameterize 'merge' to specify which branch(es) should be merged.   
+//       extend 'merge' to allow a specific branch to be merge target (not just master);
+//       make pull/merge be recursive (ie, this func currently just operates on the main doc and its children)
+export async function BranchTask(target: Doc, action: "pull" | "merge") {
+    const func = action === "pull" ? GitlikePullFromMaster : GitlikeMergeWithMaster;
+    await func(target, "");
+    const targetChildren = await DocListCast(target[Doc.LayoutFieldKey(target)]);
+    targetChildren.forEach(async targetChild => await func(targetChild, "-annotations"));
+}
+
+export async function BranchCreate(target: Doc) {
+    return (await Doc.MakeClone(target, false, true)).clone;
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 5d1e8b41c..d652bd2a2 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -12,6 +12,7 @@ import { Cast, ScriptCast, StrCast, DateCast } from '../../../fields/Types';
 import { denormalizeEmail, distributeAcls, GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
 import { returnFalse } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
+import { BranchTask, BranchCreate } from '../../documents/Gitlike';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
@@ -277,99 +278,6 @@ export class CollectionView extends Touchable<CollectionViewProps> {
         }
     }
 
-    // synchs matching documents on the two branches that are being merged/pulled
-    // currently this just synchs the main 'fieldKey' component of the data since 
-    // we don't have individual timestamps for all fields -- this is a problematic design issue.
-    static synchDocs(bd: Doc, md: Doc) {
-        const fieldKey = Doc.LayoutFieldKey(md);
-        let bdate = DateCast(bd[`${fieldKey}-lastModified`])?.date;
-        let mdate = DateCast(md[`${fieldKey}-lastModified`])?.date;
-        if (bdate === mdate || bdate > mdate) return;
-        const bdproto = bd && Doc.GetProto(bd);
-        if (bdproto && md) {
-            bdproto[fieldKey] = ObjectField.MakeCopy(md[fieldKey] as ObjectField);
-            bdproto[`${fieldKey}-lastModified`] = ObjectField.MakeCopy(md[`${fieldKey}-lastModified`] as ObjectField);
-        }
-    }
-
-    // pulls documents onto a branch from the branch's master
-    // if a document exists on master but not on the branch, it is branched and added
-    // NOTE: need to set a timestamp on the branch that is equal to the master's last merge timestamp.
-    static pullFromMaster = async (branch: Doc, suffix = "") => {
-        const masterMain = Cast(branch.branchOf, Doc, null);
-        // get the set of documents on both the branch and master
-        const masterMainDocs = masterMain && await DocListCastAsync(masterMain[Doc.LayoutFieldKey(masterMain) + suffix]);
-        const branchMainDocs = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]);
-        // get the master documents that correspond to the branch documents
-        const branchMasterMainDocs = branchMainDocs?.map(bd => Cast(bd.branchOf, Doc, null) || bd);
-        const branchMasterMainDocProtos = branchMasterMainDocs?.map(doc => Doc.GetProto(doc));
-        // get documents on master that don't have a corresponding master doc (form a branch doc), and ...
-        const newDocsFromMaster = masterMainDocs?.filter(md => !branchMasterMainDocProtos?.includes(Doc.GetProto(md)));
-        const oldDocsFromMaster = masterMainDocs?.filter(md => branchMasterMainDocProtos?.includes(Doc.GetProto(md)));
-        oldDocsFromMaster?.forEach(md => {
-            const bd = branchMainDocs?.find(bd => (Cast(bd.branchOf, Doc, null) || bd) === md);
-            bd && CollectionView.synchDocs(bd, md);
-        })
-        // make branch clones of them, then add them to the branch
-        const newlyBranchedDocs = await Promise.all(newDocsFromMaster?.map(async md => (await Doc.MakeClone(md, false, true)).clone) || []);
-        newlyBranchedDocs.forEach(nd => {
-            Doc.AddDocToList(branch, Doc.LayoutFieldKey(branch) + suffix, nd);
-            nd.context = branch;
-        });
-        // if a branch doc's corresponding main branch doc doesn't have a context, then it was deleted.  
-        const remDocsFromMaster = branchMainDocs?.filter(bd => Cast(bd.branchOf, Doc, null) && !Cast(bd.branchOf, Doc, null)?.context);
-        // so then remove all the deleted main docs from this branch.
-        remDocsFromMaster?.forEach(rd => Doc.RemoveDocFromList(branch, Doc.LayoutFieldKey(branch) + suffix, rd));
-    }
-
-    // merges all branches from the master branch by first merging the top-level collection of documents, 
-    // and then merging all the annotations on those documents.
-    // TODO: need to add an incrementing timestamp whenever anything merges.  don't allow a branch to merge if it's last pull timestamp isn't equal to the last merge timestamp.
-    static mergeWithMaster = async (master: Doc, suffix = "") => {
-        const branches = await DocListCastAsync(master.branches);
-        branches?.map(async branch => {
-            const branchChildren = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]);
-            branchChildren?.forEach(async bd => {
-                // see if the branch's child exists on master.  
-                const masterChild = Cast(bd.branchOf, Doc, null) || (await Doc.MakeClone(bd, false, true)).clone;
-                // if the branch's child didn't exist on master, we make a branch clone of the child to add to master.  
-                // however, since master is supposed to have the "main" clone, and branches, the "branch" clones, we have to reverse the fields
-                // on the branch child and master clone.
-                if (masterChild.branchOf) {
-                    const branchDocProto = Doc.GetProto(bd);
-                    const masterChildProto = Doc.GetProto(masterChild);
-                    masterChildProto.branchOf = undefined; // the master child should not be a branch of the branch child, so unset 'branchOf'
-                    masterChildProto.branches = new List<Doc>([bd]); // the master child's branches needs to include the branch child
-                    Doc.RemoveDocFromList(branchDocProto, "branches", masterChildProto);   // the branch child should not have the master child in its branch list.
-                    branchDocProto.branchOf = masterChild;                                 // the branch child is now a branch of the master child
-                }
-                Doc.AddDocToList(master, Doc.LayoutFieldKey(master) + suffix, masterChild); // add the masterChild to master (if it's already there, this is a no-op)
-                masterChild.context = master;
-                CollectionView.synchDocs(Doc.GetProto(masterChild), bd);
-            });
-            const masterChildren = await DocListCastAsync(master[Doc.LayoutFieldKey(master) + suffix]);
-            masterChildren?.forEach(async mc => {                      // see if any master children
-                if (!branchChildren?.find(bc => bc.branchOf === mc)) { //    are not in the list of children for this branch. 
-                    Doc.RemoveDocFromList(master, Doc.LayoutFieldKey(master) + suffix, mc); // if so, delete the master child since the branch has deleted it.
-                    mc.context = undefined;      // NOTE if we merge a branch that didn't do a pull, it will look like the branch deleted documents -- need edit timestamps that prevent merging if branch isn't up-to-date with last edit timestamp
-                }
-            });
-        });
-    }
-
-    // performs a "git"-like task: pull or merge
-    //    if pull, then target is a specific branch document that will be updated from its associated master
-    //    if merge, then target is the master doc that will merge in all branches associated with it.
-    // TODO: parameterize 'merge' to specify which branch(es) should be merged.   
-    //       extend 'merge' to allow a specific branch to be merge target (not just master);
-    //       make pull/merge be recursive (ie, this func currently just operates on the main doc and its children)
-    static async GitTask(target: Doc, action: "pull" | "merge") {
-        const func = action === "pull" ? CollectionView.pullFromMaster : CollectionView.mergeWithMaster;
-        await func(target, "");
-        const targetChildren = await DocListCast(target[Doc.LayoutFieldKey(target)]);
-        targetChildren.forEach(async targetChild => await func(targetChild, "-annotations"));
-    }
-
     onContextMenu = (e: React.MouseEvent): void => {
         const cm = ContextMenu.Instance;
         if (cm && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
@@ -392,13 +300,13 @@ export class CollectionView extends Touchable<CollectionViewProps> {
             !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
             optionItems.push({
-                description: "Create Branch", event: async () => this.props.addDocTab((await Doc.MakeClone(this.props.Document, false, true)).clone, "add:right"), icon: "project-diagram"
+                description: "Create Branch", event: async () => this.props.addDocTab(BranchCreate(this.props.Document), "add:right"), icon: "project-diagram"
             });
             optionItems.push({
-                description: "Pull Master", event: () => CollectionView.GitTask(this.props.Document, "pull"), icon: "project-diagram"
+                description: "Pull Master", event: () => BranchTask(this.props.Document, "pull"), icon: "project-diagram"
             });
             optionItems.push({
-                description: "Merge Branches", event: () => CollectionView.GitTask(this.props.Document, "merge"), icon: "project-diagram"
+                description: "Merge Branches", event: () => BranchTask(this.props.Document, "merge"), icon: "project-diagram"
             });
 
             !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" });
-- 
cgit v1.2.3-70-g09d2


From 7b3937698e5b22a84c623680e8c84164268bda8f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 8 Mar 2021 11:06:45 -0500
Subject: from last

---
 src/client/views/collections/CollectionView.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index d652bd2a2..857982782 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -300,7 +300,7 @@ export class CollectionView extends Touchable<CollectionViewProps> {
             !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
             optionItems.push({
-                description: "Create Branch", event: async () => this.props.addDocTab(BranchCreate(this.props.Document), "add:right"), icon: "project-diagram"
+                description: "Create Branch", event: async () => this.props.addDocTab(await BranchCreate(this.props.Document), "add:right"), icon: "project-diagram"
             });
             optionItems.push({
                 description: "Pull Master", event: () => BranchTask(this.props.Document, "pull"), icon: "project-diagram"
-- 
cgit v1.2.3-70-g09d2

