aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/util/CurrentUserUtils.ts4
-rw-r--r--src/client/util/DocumentManager.ts15
-rw-r--r--src/client/util/LinkManager.ts85
-rw-r--r--src/client/views/DocComponent.tsx5
-rw-r--r--src/client/views/GlobalKeyHandler.ts5
-rw-r--r--src/client/views/MainView.scss1
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/PropertiesView.tsx4
-rw-r--r--src/client/views/collections/CollectionView.tsx9
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx12
-rw-r--r--src/client/views/linking/LinkEditor.tsx264
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx11
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx20
-rw-r--r--src/client/views/nodes/DocumentView.tsx1
-rw-r--r--src/client/views/nodes/FieldView.tsx2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx10
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.scss3
-rw-r--r--src/client/views/pdf/Annotation.tsx7
-rw-r--r--src/client/views/pdf/PDFViewer.tsx14
-rw-r--r--src/client/views/search/SearchBox.tsx4
-rw-r--r--src/fields/Doc.ts1
-rw-r--r--src/fields/List.ts7
-rw-r--r--src/fields/util.ts54
-rw-r--r--src/server/websocket.ts41
24 files changed, 154 insertions, 427 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index ec550c15a..1096b8e5f 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -884,7 +884,7 @@ export class CurrentUserUtils {
(sharedDocs as Doc)["acl-Public"] = Doc.GetProto(sharedDocs as Doc)["acl-Public"] = SharingPermissions.Add;
}
if (sharedDocs instanceof Doc) {
- sharedDocs.userColor = sharedDocs.userColor || "#12121233";
+ sharedDocs.userColor = sharedDocs.userColor || "rgb(202, 202, 202)";
}
doc.mySharedDocs = new PrefetchProxy(sharedDocs);
}
@@ -986,9 +986,9 @@ export class CurrentUserUtils {
this.setupDockedButtons(doc); // the bottom bar of font icons
await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels
await this.setupMenuPanel(doc, sharingDocumentId);
- doc.globalLinkDatabase = Docs.Prototypes.MainLinkDocument();
doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument();
doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument();
+ if (!doc.myLinkDatabase) doc.myLinkDatabase = new List([]);
setTimeout(() => this.setupDefaultPresentation(doc), 0); // presentation that's initially triggered
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 2ca29cb7e..4becdf4a3 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -151,7 +151,7 @@ export class DocumentManager {
};
const docView = getFirstDocView(targetDoc, originatingDoc);
let annotatedDoc = await Cast(targetDoc.annotationOn, Doc);
- if (annotatedDoc && !targetDoc?.isPushpin) {
+ if (annotatedDoc && annotatedDoc !== originatingDoc?.context && !targetDoc?.isPushpin) {
const first = getFirstDocView(annotatedDoc);
if (first) {
annotatedDoc = first.props.Document;
@@ -161,11 +161,13 @@ export class DocumentManager {
if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight?
if (originatingDoc?.isPushpin) {
docView.props.Document.hidden = !docView.props.Document.hidden;
+ finished?.();
}
else {
docView.select(false);
docView.props.Document.hidden && (docView.props.Document.hidden = undefined);
- docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish);
+ // @ts-ignore
+ docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish, annotatedDoc && annotatedDoc === originatingDoc?.context);
highlight();
}
} else {
@@ -193,11 +195,13 @@ export class DocumentManager {
if (retryDocView) { // we found the target in the context
retryDocView.props.focus(targetDoc, willZoom, undefined, focusAndFinish); // focus on the target in the context
highlight();
- }
- if (delay > 2500) {
+ } else if (delay > 1500) {
// we didn't find the target, so it must have moved out of the context. Go back to just creating it.
if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.props.Document);
- // targetDoc.layout && createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target
+ if (targetDoc.layout) {
+ Doc.SetInPlace(targetDoc, "annotationOn", undefined, false);
+ createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target
+ }
} else {
setTimeout(() => findView(delay + 250), 250);
}
@@ -214,7 +218,6 @@ export class DocumentManager {
public async FollowLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
const linkDocs = link ? [link] : DocListCast(doc.links);
- SelectionManager.DeselectAll();
const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor1
const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor2
const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 269de08a1..a2bb16dfc 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -2,7 +2,7 @@ import { Doc, DocListCast, Opt } from "../../fields/Doc";
import { List } from "../../fields/List";
import { listSpec } from "../../fields/Schema";
import { Cast, StrCast } from "../../fields/Types";
-import { CurrentUserUtils } from "./CurrentUserUtils";
+import { SharingManager } from "./SharingManager";
/*
* link doc:
@@ -33,31 +33,19 @@ export class LinkManager {
private constructor() {
}
- // the linkmanagerdoc stores a list of docs representing all linkdocs in 'allLinks' and a list of strings representing all group types in 'allGroupTypes'
- // lists of strings representing the metadata keys for each group type is stored under a key that is the same as the group type
- public get LinkManagerDoc(): Doc | undefined {
- return Doc.UserDoc().globalLinkDatabase as Doc;
- }
public getAllLinks(): Doc[] {
- const ldoc = LinkManager.Instance.LinkManagerDoc;
- return ldoc ? DocListCast(ldoc.data) : [];
+ const lset = new Set<Doc>(DocListCast(Doc.UserDoc().myLinkDatabase));
+ SharingManager.Instance.users.forEach(user => DocListCast(user.sharingDoc.myLinkDatabase).map(lset.add));
+ return Array.from(lset);
}
public addLink(linkDoc: Doc): boolean {
- if (LinkManager.Instance.LinkManagerDoc) {
- Doc.AddDocToList(LinkManager.Instance.LinkManagerDoc, "data", linkDoc);
- return true;
- }
- return false;
+ return Doc.AddDocToList(Doc.UserDoc(), "myLinkDatabase", linkDoc);
}
public deleteLink(linkDoc: Doc): boolean {
- if (LinkManager.Instance.LinkManagerDoc && linkDoc instanceof Doc) {
- Doc.RemoveDocFromList(LinkManager.Instance.LinkManagerDoc, "data", linkDoc);
- return true;
- }
- return false;
+ return Doc.RemoveDocFromList(Doc.UserDoc(), "myLinkDatabase", linkDoc);
}
// finds all links that contain the given anchor
@@ -85,49 +73,6 @@ export class LinkManager {
related.forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc));
}
- public addGroupType(groupType: string): boolean {
- if (LinkManager.Instance.LinkManagerDoc) {
- LinkManager.Instance.LinkManagerDoc[groupType] = new List<string>([]);
- const groupTypes = LinkManager.Instance.getAllGroupTypes();
- groupTypes.push(groupType);
- LinkManager.Instance.LinkManagerDoc.allGroupTypes = new List<string>(groupTypes);
- return true;
- }
- return false;
- }
-
- // removes all group docs from all links with the given group type
- public deleteGroupType(groupType: string): boolean {
- if (LinkManager.Instance.LinkManagerDoc) {
- if (LinkManager.Instance.LinkManagerDoc[groupType]) {
- const groupTypes = LinkManager.Instance.getAllGroupTypes();
- const index = groupTypes.findIndex(type => type.toUpperCase() === groupType.toUpperCase());
- if (index > -1) groupTypes.splice(index, 1);
- LinkManager.Instance.LinkManagerDoc.allGroupTypes = new List<string>(groupTypes);
- LinkManager.Instance.LinkManagerDoc[groupType] = undefined;
- LinkManager.Instance.getAllLinks().forEach(async linkDoc => {
- const anchor1 = await Cast(linkDoc.anchor1, Doc);
- const anchor2 = await Cast(linkDoc.anchor2, Doc);
- anchor1 && LinkManager.Instance.removeGroupFromAnchor(linkDoc, anchor1, groupType);
- anchor2 && LinkManager.Instance.removeGroupFromAnchor(linkDoc, anchor2, groupType);
- });
- }
- return true;
- } else return false;
- }
-
- public getAllGroupTypes(): string[] {
- if (LinkManager.Instance.LinkManagerDoc) {
- if (LinkManager.Instance.LinkManagerDoc.allGroupTypes) {
- return Cast(LinkManager.Instance.LinkManagerDoc.allGroupTypes, listSpec("string"), []);
- } else {
- LinkManager.Instance.LinkManagerDoc.allGroupTypes = new List<string>([]);
- return [];
- }
- }
- return [];
- }
-
// gets the groups associates with an anchor in a link
public getAnchorGroups(linkDoc: Doc, anchor?: Doc): Array<Doc> {
if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, null))) {
@@ -164,22 +109,6 @@ export class LinkManager {
return anchorGroups;
}
- // gets a list of strings representing the keys of the metadata associated with the given group type
- public getMetadataKeysInGroup(groupType: string): string[] {
- if (LinkManager.Instance.LinkManagerDoc) {
- return LinkManager.Instance.LinkManagerDoc[groupType] ? Cast(LinkManager.Instance.LinkManagerDoc[groupType], listSpec("string"), []) : [];
- }
- return [];
- }
-
- public setMetadataKeysForGroup(groupType: string, keys: string[]): boolean {
- if (LinkManager.Instance.LinkManagerDoc) {
- LinkManager.Instance.LinkManagerDoc[groupType] = new List<string>(keys);
- return true;
- }
- return false;
- }
-
// returns a list of all metadata docs associated with the given group type
public getAllMetadataDocsInGroup(groupType: string): Array<Doc> {
const md: Doc[] = [];
@@ -208,6 +137,8 @@ export class LinkManager {
const a2 = Cast(linkDoc.anchor2, Doc, null);
if (Doc.AreProtosEqual(anchor, a1)) return a2;
if (Doc.AreProtosEqual(anchor, a2)) return a1;
+ if (Doc.AreProtosEqual(anchor, a1.annotationOn as Doc)) return a2;
+ if (Doc.AreProtosEqual(anchor, a2.annotationOn as Doc)) return a1;
if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
}
} \ No newline at end of file
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index b3fbe418b..98e888538 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -123,8 +123,9 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
@action.bound
removeDocument(doc: Doc | Doc[]): boolean {
const effectiveAcl = GetEffectiveAcl(this.dataDoc);
- const docAcl = GetEffectiveAcl(doc);
- if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || docAcl === AclAdmin) {
+ const indocs = doc instanceof Doc ? [doc] : doc;
+ const docs = indocs.filter(doc => effectiveAcl === AclEdit || effectiveAcl === AclAdmin || GetEffectiveAcl(doc) === AclAdmin);
+ if (docs.length) {
const docs = doc instanceof Doc ? [doc] : doc;
docs.map(doc => doc.isPushpin = doc.annotationOn = undefined);
const targetDataDoc = this.dataDoc;
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index c48ba109a..89292a445 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -223,6 +223,11 @@ export class KeyManager {
stopPropagation = false;
break;
case "a":
+ if (e.target !== document.body) {
+ stopPropagation = false;
+ preventDefault = false;
+ }
+ break;
case "v":
stopPropagation = false;
preventDefault = false;
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 5efe9adad..b608eceb1 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -293,7 +293,6 @@
.mainView-libraryFlyout-out,
.mainView-libraryFlyout {
height: 100%;
- width: 100%;
position: relative;
display: flex;
flex-direction: column;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index cb84409cb..69354020b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -86,7 +86,7 @@ export class MainView extends React.Component {
document.getElementById("root")?.addEventListener("scroll", e => ((ele) => ele.scrollLeft = ele.scrollTop = 0)(document.getElementById("root")!));
new InkStrokeProperties();
this._sidebarContent.proto = undefined;
- DocServer.setPlaygroundFields(["dataTransition", "_delayAutoHeight", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollY", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
+ DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_delayAutoHeight", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollY", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg)));
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index f3241e8d9..a64004c5c 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -972,7 +972,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</div>
{this.openContexts ? <div className="propertiesView-contexts-content" >{this.contexts}</div> : null}
</div>
- <div className="propertiesView-layout">
+ {/* <div className="propertiesView-layout">
<div className="propertiesView-layout-title"
onPointerDown={action(() => this.openLayout = !this.openLayout)}
style={{ backgroundColor: this.openLayout ? "black" : "" }}>
@@ -982,7 +982,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</div>
</div>
{this.openLayout ? <div className="propertiesView-layout-content" >{this.layoutPreview}</div> : null}
- </div>
+ </div> */}
</div>;
}
if (this.isPres) {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2bdc8e2f3..80e9b41ad 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -157,7 +157,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
});
}
else {
- added.map(doc => {
+ added.filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))).map(doc => { // only make a pushpin if we have acl's to edit the document
const context = Cast(doc.context, Doc, null);
if (context && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
const pushpin = Docs.Create.FontIconDocument({
@@ -186,9 +186,9 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
@action.bound
removeDocument = (doc: any): boolean => {
const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
- const docAcl = GetEffectiveAcl(doc);
- if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || docAcl === AclAdmin) {
- const docs = doc instanceof Doc ? [doc] : doc as Doc[];
+ const indocs = doc instanceof Doc ? [doc] : doc as Doc[];
+ const docs = indocs.filter(doc => effectiveAcl === AclEdit || effectiveAcl === AclAdmin || GetEffectiveAcl(doc) === AclAdmin);
+ if (docs.length) {
const targetDataDoc = this.props.Document[DataSym];
const value = DocListCast(targetDataDoc[this.props.fieldKey]);
const toRemove = value.filter(v => docs.includes(v));
@@ -196,7 +196,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc;
toRemove.forEach(doc => {
const ind = (targetDataDoc[this.props.fieldKey] as List<Doc>).indexOf(doc);
- (targetDataDoc[this.props.fieldKey] as List<Doc>).splice(ind, 0);
if (ind !== -1) {
Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey, doc);
recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2783011cf..35b4c8e98 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -867,7 +867,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
}
- focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => {
+ focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean, dontCenter?: boolean) => {
const state = HistoryUtil.getState();
// TODO This technically isn't correct if type !== "doc", as
@@ -886,15 +886,19 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
SelectionManager.DeselectAll();
if (this.props.Document.scrollHeight) {
const annotOn = Cast(doc.annotationOn, Doc) as Doc;
+ let delay = 1000;
if (!annotOn) {
this.props.focus(doc);
} else {
const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn._height);
const offset = annotOn && (contextHgt / 2);
- this.props.Document._scrollY = NumCast(doc.y) - offset;
+ const scrollTo = NumCast(doc.y) - ((Number.isNaN(offset) ? 150 : offset));
+ this.props.Document._scrollY = scrollTo;
+ delay = Math.abs(scrollTo - NumCast(this.props.Document._scrollTop)) > 5 ? 1000 : 0;
}
- afterFocus && setTimeout(afterFocus, 1000);
+ !dontCenter && this.props.focus(this.props.Document);
+ afterFocus && setTimeout(afterFocus, delay);
} else {
const layoutdoc = Doc.Layout(doc);
const newPanX = NumCast(doc.x) + NumCast(layoutdoc._width) / 2;
@@ -1159,7 +1163,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
onCursorMove = (e: React.PointerEvent) => {
- super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
+ // super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 3713a1026..435b9d904 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -10,264 +10,6 @@ import { undoBatch } from "../../util/UndoManager";
import './LinkEditor.scss';
import React = require("react");
-interface GroupTypesDropdownProps {
- groupType: string;
- setGroupType: (group: string) => void;
-}
-// this dropdown could be generalized
-@observer
-class GroupTypesDropdown extends React.Component<GroupTypesDropdownProps> {
- @observable private _searchTerm: string = this.props.groupType;
- @observable private _groupType: string = this.props.groupType;
- @observable private _isEditing: boolean = false;
-
- @action
- createGroup = (groupType: string): void => {
- this.props.setGroupType(groupType);
- LinkManager.Instance.addGroupType(groupType);
- }
-
- @action
- onChange = (val: string): void => {
- this._searchTerm = val;
- this._groupType = val;
- this._isEditing = true;
- }
-
- @action
- onKeyDown = (e: React.KeyboardEvent): void => {
- if (e.key === "Enter") {
- const allGroupTypes = Array.from(LinkManager.Instance.getAllGroupTypes());
- const groupOptions = allGroupTypes.filter(groupType => groupType.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
- const exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase());
-
- if (exactFound > -1) {
- const groupType = groupOptions[exactFound];
- this.props.setGroupType(groupType);
- this._groupType = groupType;
- } else {
- this.createGroup(this._searchTerm);
- this._groupType = this._searchTerm;
- }
-
- this._searchTerm = this._groupType;
- this._isEditing = false;
- }
- }
-
- @action
- onOptionClick = (value: string, createNew: boolean): void => {
- if (createNew) {
- this.createGroup(this._searchTerm);
- this._groupType = this._searchTerm;
-
- } else {
- this.props.setGroupType(value);
- this._groupType = value;
-
- }
- this._searchTerm = this._groupType;
- this._isEditing = false;
- }
-
- @action
- onButtonPointerDown = (): void => {
- this._isEditing = true;
- }
-
- renderOptions = (): JSX.Element[] | JSX.Element => {
- if (this._searchTerm === "") return <></>;
-
- const allGroupTypes = Array.from(LinkManager.Instance.getAllGroupTypes());
- const groupOptions = allGroupTypes.filter(groupType => groupType.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
- const exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase()) > -1;
-
- const options = groupOptions.map(groupType => {
- const ref = React.createRef<HTMLDivElement>();
- return <div key={groupType} ref={ref} className="linkEditor-option"
- onClick={() => this.onOptionClick(groupType, false)}>{groupType}</div>;
- });
-
- // if search term does not already exist as a group type, give option to create new group type
- if (!exactFound && this._searchTerm !== "") {
- const ref = React.createRef<HTMLDivElement>();
- options.push(<div key={""} ref={ref} className="linkEditor-option"
- onClick={() => this.onOptionClick(this._searchTerm, true)}>Define new "{this._searchTerm}" relationship</div>);
- }
-
- return options;
- }
-
- render() {
- if (this._isEditing || this._groupType === "") {
- return (
- <div className="linkEditor-dropdown">
- <input type="text" value={this._groupType === "-ungrouped-" ? "" : this._groupType} placeholder="Search for or create a new group"
- onChange={e => this.onChange(e.target.value)} onKeyDown={this.onKeyDown} autoFocus></input>
- <div className="linkEditor-options-wrapper">
- {this.renderOptions()}
- </div>
- </div >
- );
- } else {
- return <button className="linkEditor-typeButton" onClick={() => this.onButtonPointerDown()}>{this._groupType}</button>;
- }
- }
-}
-
-
-interface LinkMetadataEditorProps {
- id: string;
- groupType: string;
- mdDoc: Doc;
- mdKey: string;
- mdValue: string;
- changeMdIdKey: (id: string, newKey: string) => void;
-}
-@observer
-class LinkMetadataEditor extends React.Component<LinkMetadataEditorProps> {
- @observable private _key: string = this.props.mdKey;
- @observable private _value: string = this.props.mdValue;
- @observable private _keyError: boolean = false;
-
- @action
- setMetadataKey = (value: string): void => {
- const groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);
-
- // don't allow user to create existing key
- const newIndex = groupMdKeys.findIndex(key => key.toUpperCase() === value.toUpperCase());
- if (newIndex > -1) {
- this._keyError = true;
- this._key = value;
- return;
- } else {
- this._keyError = false;
- }
-
- // set new value for key
- const currIndex = groupMdKeys.findIndex(key => {
- return StrCast(key).toUpperCase() === this._key.toUpperCase();
- });
- if (currIndex === -1) console.error("LinkMetadataEditor: key was not found");
- groupMdKeys[currIndex] = value;
-
- this.props.changeMdIdKey(this.props.id, value);
- this._key = value;
- LinkManager.Instance.setMetadataKeysForGroup(this.props.groupType, [...groupMdKeys]);
- }
-
- @action
- setMetadataValue = (value: string): void => {
- if (!this._keyError) {
- this._value = value;
- Doc.GetProto(this.props.mdDoc)[this._key] = value;
- }
- }
-
- @action
- removeMetadata = (): void => {
- const groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);
-
- const index = groupMdKeys.findIndex(key => key.toUpperCase() === this._key.toUpperCase());
- if (index === -1) console.error("LinkMetadataEditor: key was not found");
- groupMdKeys.splice(index, 1);
-
- LinkManager.Instance.setMetadataKeysForGroup(this.props.groupType, groupMdKeys);
- this._key = "";
- }
-
- render() {
- return (
- <div className="linkEditor-metadata-row">
- <input className={this._keyError ? "linkEditor-error" : ""} type="text" value={this._key === "new key" ? "" : this._key} placeholder="key" onChange={e => this.setMetadataKey(e.target.value)}></input>:
- <input type="text" value={this._value} placeholder="value" onChange={e => this.setMetadataValue(e.target.value)}></input>
- <button title="remove metadata from relationship" onClick={() => this.removeMetadata()}><FontAwesomeIcon icon="times" size="sm" /></button>
- </div>
- );
- }
-}
-
-interface LinkGroupEditorProps {
- sourceDoc: Doc;
- linkDoc: Doc;
- groupDoc: Doc;
-}
-@observer
-export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
-
- private _metadataIds: Map<string, string> = new Map();
-
- constructor(props: LinkGroupEditorProps) {
- super(props);
-
- const groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(StrCast(props.groupDoc.linkRelationship));
- groupMdKeys.forEach(key => this._metadataIds.set(key, Utils.GenerateGuid()));
- }
-
- @action
- setGroupType = (groupType: string): void => {
- Doc.GetProto(this.props.groupDoc).linkRelationship = groupType;
- }
-
- removeGroupFromLink = (groupType: string): void => {
- LinkManager.Instance.removeGroupFromAnchor(this.props.linkDoc, this.props.sourceDoc, groupType);
- }
-
- deleteGroup = (groupType: string): void => {
- LinkManager.Instance.deleteGroupType(groupType);
- }
-
-
- @action
- addMetadata = (groupType: string): void => {
- this._metadataIds.set("new key", Utils.GenerateGuid());
- const mdKeys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
- // only add "new key" if there is no other key with value "new key"; prevents spamming
- if (mdKeys.indexOf("new key") === -1) mdKeys.push("new key");
- LinkManager.Instance.setMetadataKeysForGroup(groupType, mdKeys);
- }
-
- // for key rendering purposes
- changeMdIdKey = (id: string, newKey: string) => {
- this._metadataIds.set(newKey, id);
- }
-
- renderMetadata = (): JSX.Element[] => {
- const metadata: Array<JSX.Element> = [];
- const groupDoc = this.props.groupDoc;
- const groupType = StrCast(groupDoc.linkRelationship);
- const groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
-
- groupMdKeys.forEach((key) => {
- const val = StrCast(groupDoc[key]);
- metadata.push(
- <LinkMetadataEditor key={"mded-" + this._metadataIds.get(key)} id={this._metadataIds.get(key)!} groupType={groupType} mdDoc={groupDoc} mdKey={key} mdValue={val} changeMdIdKey={this.changeMdIdKey} />
- );
- });
- return metadata;
- }
-
- render() {
- const groupType = StrCast(this.props.groupDoc.linkRelationship);
- // if ((groupType && LinkManager.Instance.getMetadataKeysInGroup(groupType).length > 0) || groupType === "") {
- const buttons = <button className="linkEditor-button" disabled={groupType === ""} onClick={() => this.deleteGroup(groupType)} title="Delete Relationship from all links"><FontAwesomeIcon icon="trash" size="sm" /></button>;
- const addButton = <button className="linkEditor-addbutton" onClick={() => this.addMetadata(groupType)} disabled={groupType === ""} title="Add metadata to relationship"><FontAwesomeIcon icon="plus" size="sm" /></button>;
-
- return (
- <div className="linkEditor-group">
- <div className="linkEditor-group-row ">
- {buttons}
- <GroupTypesDropdown groupType={groupType} setGroupType={this.setGroupType} />
- <button className="linkEditor-button" onClick={() => this.removeGroupFromLink(groupType)} title="Remove relationship from link"><FontAwesomeIcon icon="times" size="sm" /></button>
- </div>
- {this.renderMetadata().length > 0 ? <p className="linkEditor-group-row-label">metadata:</p> : <></>}
- {addButton}
- {this.renderMetadata()}
- </div>
- );
- }
-}
-
interface LinkEditorProps {
sourceDoc: Doc;
@@ -422,10 +164,6 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
render() {
const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
- const groups = [this.props.linkDoc].map(groupDoc => {
- return <LinkGroupEditor key={"gred-" + StrCast(groupDoc.linkRelationship)} linkDoc={this.props.linkDoc}
- sourceDoc={this.props.sourceDoc} groupDoc={groupDoc} />;
- });
return !destination ? (null) : (
<div className="linkEditor">
@@ -450,8 +188,6 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
<div>{this.editDescription}</div>
<div>{this.followingDropdown}</div>
-
- {/* {groups.length > 0 ? groups : <div className="linkEditor-group">There are currently no relationships associated with this link.</div>} */}
</div>
);
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 29e1d921c..e76227ccf 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -54,17 +54,6 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
e.stopPropagation();
}
- viewGroupAsTable = (groupType: string): JSX.Element => {
- const keys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
- const index = keys.indexOf("");
- if (index > -1) keys.splice(index, 1);
- const cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c, "#f1efeb"));
- const docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType);
- const createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { _width: 500, _height: 300, title: groupType + " table", childDropAction: "alias" }));
- const ref = React.createRef<HTMLDivElement>();
- return <div ref={ref}><button className="linkEditor-button linkEditor-tableButton" onPointerDown={SetupDrag(ref, createTable)} title="Drag to view relationship table"><FontAwesomeIcon icon="table" size="sm" /></button></div>;
- }
-
render() {
const set = new Set<Doc>(this.props.group);
const groupItems = Array.from(set.keys()).map(linkDoc => {
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 3a8d41fef..1ba724889 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -90,19 +90,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
return true;
}
- renderMetadata = (): JSX.Element => {
- const index = StrCast(this.props.linkDoc.title).toUpperCase() === this.props.groupType.toUpperCase() ? 0 : -1;
- const mdDoc = index > -1 ? this.props.linkDoc : undefined;
-
- let mdRows: Array<JSX.Element> = [];
- if (mdDoc) {
- const keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
- mdRows = keys.map(key => <div key={key} className="link-metadata-row"><b>{key}</b>: {StrCast(mdDoc[key])}</div>);
- }
-
- return (<div className="link-metadata">{mdRows}</div>);
- }
-
@action
onLinkButtonDown = (e: React.PointerEvent): void => {
this._downX = e.clientX;
@@ -191,8 +178,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
}
render() {
- const keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
- const canExpand = keys ? keys.length > 0 : false;
const eyeIcon = this.props.linkDoc.hidden ? "eye-slash" : "eye";
@@ -230,7 +215,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
return (
<div className="linkMenu-item">
- <div className={canExpand ? "linkMenu-item-content expand-three" : "linkMenu-item-content expand-two"}>
+ <div className={"linkMenu-item-content expand-two"}>
<div ref={this._drag} className="linkMenu-name" //title="drag to view target. click to customize."
onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
@@ -257,8 +242,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
{StrCast(this.props.linkDoc.description)}</p> : null} </div>
<div className="linkMenu-item-buttons" ref={this._buttonRef} >
- {canExpand ? <div title="Show more" className="button" onPointerDown={e => this.toggleShowMore(e)}>
- <FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
<Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.hidden ? "Show link" : "Hide link"}</div></>}>
<div className="button" ref={this._editRef} onPointerDown={this.showLink}>
@@ -277,7 +260,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
<FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div> */}
</div>
</div>
- {this._showMore ? this.renderMetadata() : <></>}
</div>
</div >
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ddcf7f6f4..369b53aa0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -740,6 +740,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), title: StrCast(this.props.Document.title) + ".portal" });
DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to");
}
+ this.Document.followLinkLocation = "inPlace";
this.Document.followLinkZoom = true;
this.Document.isLinkButton = true;
}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index d65b43704..3a5b27b21 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -134,7 +134,7 @@ export class FieldView extends React.Component<FieldViewProps> {
// );
}
else if (field instanceof List) {
- return <div> {field.length ? field.map(f => Field.toString(f)).join(", ") : "[]"} </div>;
+ return <div> {field.length ? field.map(f => Field.toString(f)).join(", ") : ""} </div>;
}
// bcz: this belongs here, but it doesn't render well so taking it out for now
else if (field instanceof WebField) {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a998386d8..ef39e6a90 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1123,6 +1123,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
+ private isActiveTab(el: Element | null | undefined) {
+ while (el && el !== document.body) {
+ if (getComputedStyle(el).display === "none") return false;
+ el = el.parentNode as any;
+ }
+ return true;
+ }
+
private setupEditor(config: any, fieldKey: string) {
const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null);
const rtfField = Cast((!curText?.Text && this.layoutDoc[this.props.fieldKey]) || this.dataDoc[fieldKey], RichTextField);
@@ -1161,7 +1169,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad;
- if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad) {
+ if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
FormattedTextBox.SelectOnLoad = "";
this.props.select(false);
if (FormattedTextBox.SelectOnLoadChar && this._editorView) {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
index 582ada6ce..81afba4d7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
@@ -29,9 +29,6 @@
font-style: italic;
color: rgb(95, 97, 102);
font-size: 10px;
- padding-bottom: 4px;
- margin-bottom: 5px;
-
}
}
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 84b14cd61..e7f901091 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -15,6 +15,7 @@ interface IAnnotationProps {
addDocTab: (document: Doc, where: string) => boolean;
pinToPres: (document: Doc, unpin?: boolean) => void;
focus: (doc: Doc) => void;
+ select: (isCtrlPressed: boolean) => void;
dataDoc: Doc;
fieldKey: string;
showInfo: (anno: Opt<Doc>) => void;
@@ -25,7 +26,7 @@ export
class Annotation extends React.Component<IAnnotationProps> {
render() {
return DocListCast(this.props.anno.annotations).map(a =>
- <RegionAnnotation {...this.props} showInfo={this.props.showInfo} pinToPres={this.props.pinToPres} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />);
+ <RegionAnnotation {...this.props} showInfo={this.props.showInfo} select={this.props.select} pinToPres={this.props.pinToPres} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />);
}
}
@@ -37,6 +38,7 @@ interface IRegionAnnotationProps {
height: number;
addDocTab: (document: Doc, where: string) => boolean;
pinToPres: (document: Doc, unpin: boolean) => void;
+ select: (isCtrlPressed: boolean) => void;
document: Doc;
dataDoc: Doc;
fieldKey: string;
@@ -115,7 +117,8 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
e.persist();
e.stopPropagation();
PromiseValue(this.props.document.group).then(annoGroup => annoGroup instanceof Doc &&
- DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation), false, undefined)
+ DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation), false, undefined,
+ () => this.props.select(false))
);
}
}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 77dd40f2a..3570c565a 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -155,6 +155,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
this._mainCont.current.scrollTop = this.layoutDoc._scrollTop || 0;
const observer = new _global.ResizeObserver(action((entries: any) => this._mainCont.current && (this._mainCont.current.scrollTop = this.layoutDoc._scrollTop || 0)));
observer.observe(this._mainCont.current);
+ this._mainCont.current.addEventListener("scroll", (e) => (e.target as any).scrollLeft = 0);
}
this._disposers.searchMatch = reaction(() => Doc.IsSearchMatch(this.rootDoc),
@@ -179,9 +180,9 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
if (scrollY !== undefined) {
(this._showCover || this._showWaiting) && this.setupPdfJsViewer();
if ((this.props.renderDepth === -1 || (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc)) && this._mainCont.current) {
- smoothScroll(1000, this._mainCont.current, (this.Document._scrollY || 0));
- } else {
- console.log("Waiting for preview");
+ smoothScroll(1000, this._mainCont.current, scrollY || 0);
+ } else if (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) { // wait for mainCont and try again to scroll
+ setTimeout(() => this._mainCont.current && smoothScroll(1000, this._mainCont.current, scrollY || 0), 250);
}
setTimeout(() => this.Document._scrollY = undefined, 1000);
}
@@ -459,7 +460,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@action
onPointerDown = (e: React.PointerEvent): void => {
const hit = document.elementFromPoint(e.clientX, e.clientY);
- if (hit && hit.localName === "span" && this.props.isSelected(true)) { // drag selecting text stops propagation
+ if (hit && hit.localName === "span" && this.annotationsActive(true)) { // drag selecting text stops propagation
e.button === 0 && e.stopPropagation();
}
// if alt+left click, drag and annotate
@@ -556,6 +557,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@action
onSelectEnd = (e: PointerEvent): void => {
clearStyleSheetRules(PDFViewer._annotationStyle);
+ this.props.select(false);
this._savedAnnotations.clear();
if (this._marqueeing) {
if (this._marqueeWidth > 10 || this._marqueeHeight > 10) {
@@ -693,7 +695,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
TraceMobx();
return <div className="pdfViewerDash-annotationLayer" style={{ height: NumCast(this.Document._nativeHeight), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
{this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
- <Annotation {...this.props} showInfo={this.showInfo} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)
+ <Annotation {...this.props} showInfo={this.showInfo} select={this.props.select} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)
}
</div>;
}
@@ -762,7 +764,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
contentZoom = () => this._zoomed;
render() {
TraceMobx();
- return <div className={"pdfViewerDash" + (this.active() ? "-interactive" : "")} ref={this._mainCont}
+ return <div className={"pdfViewerDash" + (this.annotationsActive() ? "-interactive" : "")} ref={this._mainCont}
onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
style={{
overflowX: this._zoomed !== 1 ? "scroll" : undefined,
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index b381bbfa9..1104d8d2a 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -146,7 +146,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
const mod = "_t:";
const newWords: string[] = [];
const oldWords = values[0].split(" ");
- oldWords.forEach((word, i) => i === 0 ? newWords.push(key + mod + "\"" + word + "\"") : newWords.push("AND " + key + mod + "\"" + word + "\""));
+ oldWords.forEach((word, i) => i === 0 ? newWords.push(key + mod + word) : newWords.push("AND " + key + mod + word));
query = `(${query}) AND (${newWords.join(" ")})`;
}
else {
@@ -154,7 +154,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
const mod = "_t:";
const newWords: string[] = [];
const oldWords = values[i].split(" ");
- oldWords.forEach((word, i) => i === 0 ? newWords.push(key + mod + "\"" + word + "\"") : newWords.push("AND " + key + mod + "\"" + word + "\""));
+ oldWords.forEach((word, i) => i === 0 ? newWords.push(key + mod + word) : newWords.push("AND " + key + mod + word));
const v = "(" + newWords.join(" ") + ")";
if (i === 0) {
query = `(${query}) AND (${v}` + (values.length === 1 ? ")" : "");
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index a8a5ba9bd..54d85ba86 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -104,6 +104,7 @@ const AclMap = new Map<string, symbol>([
]);
export function fetchProto(doc: Doc) {
+ if (!doc) return;
const permissions: { [key: string]: symbol } = {};
Object.keys(doc).filter(key => key.startsWith("acl") && (permissions[key] = AclMap.get(StrCast(doc[key]))!));
diff --git a/src/fields/List.ts b/src/fields/List.ts
index c9e4bd3c1..ceb538b2d 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -43,7 +43,7 @@ const listHandlers: any = {
}
}
const res = list.__fields.push(...items);
- this[Update]();
+ this[Update]({ op: "$addToSet", items });
return res;
}),
reverse() {
@@ -66,6 +66,7 @@ const listHandlers: any = {
this[Self].__realFields(); // coerce retrieving entire array
items = items.map(toObjectField);
const list = this[Self];
+ const removed = list.__fields.filter((item: any, i: number) => i >= start && i < start + deleteCount);
for (let i = 0; i < items.length; i++) {
const item = items[i];
//TODO Error checking to make sure parent doesn't already exist
@@ -76,7 +77,7 @@ const listHandlers: any = {
}
}
const res = list.__fields.splice(start, deleteCount, ...items);
- this[Update]();
+ this[Update](items.length === 0 && deleteCount ? { op: "$remFromSet", items: removed } : undefined);
return res.map(toRealField);
}),
unshift(...items: any[]) {
@@ -314,7 +315,7 @@ class ListImpl<T extends Field> extends ObjectField {
// console.log(diff);
const update = this[OnUpdate];
// update && update(diff);
- update?.();
+ update?.(diff);
}
private [Self] = this;
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 4da9fce74..8a694de83 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -11,7 +11,7 @@ import { ComputedField } from "./ScriptField";
import { ScriptCast, StrCast } from "./Types";
import { returnZero } from "../Utils";
import CursorField from "./CursorField";
-
+import { List } from "./List";
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
@@ -93,7 +93,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
} else {
DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue);
}
- UndoManager.AddEvent({
+ !receiver[UpdatingFromServer] && UndoManager.AddEvent({
redo: () => receiver[prop] = value,
undo: () => receiver[prop] = curValue
});
@@ -371,18 +371,44 @@ export function deleteProperty(target: any, prop: string | number | symbol) {
export function updateFunction(target: any, prop: any, value: any, receiver: any) {
let current = ObjectField.MakeCopy(value);
return (diff?: any) => {
- if (true || !diff) {
- diff = { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } };
- const oldValue = current;
- const newValue = ObjectField.MakeCopy(value);
- current = newValue;
- if (!(value instanceof CursorField) && !(value?.some?.((v: any) => v instanceof CursorField))) {
- UndoManager.AddEvent({
- redo() { receiver[prop] = newValue; },
- undo() { receiver[prop] = oldValue; }
- });
- }
+ const op = diff?.op === "$addToSet" ? { '$addToSet': { ["fields." + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } } :
+ diff?.op === "$remFromSet" ? { '$remFromSet': { ["fields." + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } }
+ : { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } };
+ const oldValue = current;
+ const newValue = ObjectField.MakeCopy(value);
+ current = newValue;
+ if (!(value instanceof CursorField) && !(value?.some?.((v: any) => v instanceof CursorField))) {
+ !receiver[UpdatingFromServer] && UndoManager.AddEvent(
+ diff?.op === "$addToSet" ?
+ {
+ redo: () => {
+ receiver[prop].push(...diff.items);
+ },
+ undo: action(() => {
+ let curList = receiver[prop];
+ //while (curList[ForwardUpates]) curList = curList[ForwardUpates];
+ diff.items.forEach((doc: any) => {
+ const ind = curList.indexOf(doc.value());
+ ind !== -1 && curList.splice(ind, 1);
+ });
+ })
+ } :
+ diff?.op === "$remFromSet" ?
+ {
+ redo: action(() => {
+ let curList = receiver[prop];
+ diff.items.forEach((doc: any) => {
+ const ind = curList.indexOf(doc.value());
+ ind !== -1 && curList.splice(ind, 1);
+ });
+ }),
+ undo: () => receiver[prop].push(...diff.items)
+ }
+ : {
+ redo: () => receiver[prop] = newValue,
+ undo: () => receiver[prop] = oldValue
+ });
}
- target[Update](diff);
+ target[Update](op);
};
} \ No newline at end of file
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index b33e76c0b..459fa520b 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -201,7 +201,7 @@ export namespace WebSocket {
function setField(socket: Socket, newValue: Transferable) {
Database.Instance.update(newValue.id, newValue, () =>
- socket.broadcast.emit(MessageStore.SetField.Message, newValue));
+ socket.broadcast.emit(MessageStore.SetField.Message, newValue)); // broadcast set value to all other clients
if (newValue.type === Types.Text) { // if the newValue has sring type, then it's suitable for searching -- pass it to SOLR
Search.updateDocument({ id: newValue.id, data: { set: (newValue as any).data } });
}
@@ -271,7 +271,46 @@ export namespace WebSocket {
return typeof value === "string" ? value : value[0];
}
+ function addToListField(socket: Socket, diff: Diff, curListItems?: Transferable): void {
+ diff.diff.$set = diff.diff.$addToSet; delete diff.diff.$addToSet;// convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones
+ const updatefield = Array.from(Object.keys(diff.diff.$set))[0];
+ const newListItems = diff.diff.$set[updatefield].fields;
+ const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields || [];
+ diff.diff.$set[updatefield].fields = [...curList, ...newListItems.filter((newItem: any) => !curList.some((curItem: any) => curItem.fieldId === newItem.fieldId))];
+ const sendBack = true;//curList.length !== prelen;
+ Database.Instance.update(diff.id, diff.diff,
+ () => {
+ if (sendBack) {
+ const id = socket.id;
+ socket.id = "";
+ socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
+ socket.id = id;
+ } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
+ }, false);
+ }
+
+ function remFromListField(socket: Socket, diff: Diff, curListItems?: Transferable): void {
+ diff.diff.$set = diff.diff.$remFromSet; delete diff.diff.$remFromSet;
+ const updatefield = Array.from(Object.keys(diff.diff.$set))[0];
+ const remListItems = diff.diff.$set[updatefield].fields;
+ const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields || [];
+ diff.diff.$set[updatefield].fields = curList?.filter((curItem: any) => !remListItems.some((remItem: any) => remItem.fieldId === curItem.fieldId));
+ const sendBack = true;//curList.length + remListItems.length !== prelen;
+ Database.Instance.update(diff.id, diff.diff,
+ () => {
+ if (sendBack) {
+ const id = socket.id;
+ socket.id = "";
+ socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
+ socket.id = id;
+ } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
+ }, false);
+ }
+
+
function UpdateField(socket: Socket, diff: Diff) {
+ if (diff.diff.$addToSet) return GetRefField([diff.id, (result?: Transferable) => addToListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
+ if (diff.diff.$remFromSet) return GetRefField([diff.id, (result?: Transferable) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
Database.Instance.update(diff.id, diff.diff,
() => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false);
const docfield = diff.diff.$set || diff.diff.$unset;