From e1329abcd887873b5d9a28ac03960eec63eb34f0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 10 Oct 2020 13:46:21 -0400 Subject: fixed text box cursor --- src/client/views/nodes/formattedText/FormattedTextBox.scss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index dbf98a5e9..e55295100 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -12,7 +12,6 @@ .formattedTextBox-cont { touch-action: none; - cursor: text; background: inherit; padding: 0; border-width: 0px; @@ -108,6 +107,7 @@ } .formattedTextBox-inner-rounded-selected, .formattedTextBox-inner-selected { + cursor: text; .ProseMirror { padding:10px; } @@ -357,7 +357,6 @@ footnote::after { .formattedTextBox-cont { touch-action: none; - cursor: text; background: inherit; padding: 0; border-width: 0px; @@ -399,7 +398,7 @@ footnote::after { height: 35px; background: lightgray; border-radius: 20px; - cursor:grabbing; + cursor: grabbing; } .formattedTextBox-sidebar, -- cgit v1.2.3-70-g09d2 From 8539e5dbeecee2731de46a30ae8a0e7008cc3524 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 10 Oct 2020 13:52:26 -0400 Subject: from last --- src/client/views/nodes/formattedText/FormattedTextBox.scss | 6 +++++- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index e55295100..0d92d7062 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -37,6 +37,7 @@ } } +.formattedTextBox-outer-selected, .formattedTextBox-outer { position: relative; overflow: auto; @@ -44,6 +45,9 @@ width: 100%; height: unset; } +.formattedTextBox-outer-selected { + cursor: text; +} .formattedTextBox-sidebar-handle { position: absolute; @@ -107,7 +111,6 @@ } .formattedTextBox-inner-rounded-selected, .formattedTextBox-inner-selected { - cursor: text; .ProseMirror { padding:10px; } @@ -382,6 +385,7 @@ footnote::after { } } + .formattedTextBox-outer-selected, .formattedTextBox-outer { position: relative; overflow: auto; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 8078a29b7..598657a58 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1654,7 +1654,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp })} onDoubleClick={this.onDoubleClick} > -
Date: Sat, 10 Oct 2020 17:49:41 -0400 Subject: split LinkDatbase into its own field in the user's registry. fixed sharing to set lastModified after modifying groups so that remote participants see the right data. fixed text boxes to show blue icon when there's an annotaitn entry --- src/client/util/CurrentUserUtils.ts | 26 ++++++++++++++-------- src/client/util/GroupManager.tsx | 8 +++---- src/client/util/LinkManager.ts | 12 ++++++---- src/client/util/SharingManager.tsx | 9 +++++--- .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +++++----- src/fields/Doc.ts | 1 + src/server/ApiManagers/UserManager.ts | 12 +++++++--- src/server/GarbageCollector.ts | 2 +- src/server/authentication/AuthenticationManager.ts | 1 + src/server/authentication/DashUserModel.ts | 4 +++- 10 files changed, 57 insertions(+), 30 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 694982fea..7535d7c24 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1,6 +1,6 @@ import { computed, observable, reaction } from "mobx"; import * as rp from 'request-promise'; -import { DataSym, Doc, DocListCast, DocListCastAsync } from "../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, AclReadonly } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; @@ -541,9 +541,9 @@ export class CurrentUserUtils { })) as any as Doc; } } - static async setupMenuPanel(doc: Doc, sharingDocumentId: string) { + static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { if (doc.menuStack === undefined) { - await this.setupSharingSidebar(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing + await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId); // sets up the right sidebar collection for mobile upload documents and sharing const menuBtns = (await CurrentUserUtils.menuBtnDescriptions(doc)).map(({ title, target, icon, click, watchedDocuments }) => Docs.Create.FontIconDocument({ icon, @@ -876,7 +876,16 @@ export class CurrentUserUtils { } // Sharing sidebar is where shared documents are contained - static async setupSharingSidebar(doc: Doc, sharingDocumentId: string) { + static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { + if (doc.myLinkDatabase === undefined) { + let linkDocs = await DocServer.GetRefField(linkDatabaseId); + if (!linkDocs) { + linkDocs = new Doc(linkDatabaseId, true); + (linkDocs as Doc).data = new List([]); + (linkDocs as Doc)["acl-Public"] = SharingPermissions.Add; + } + doc.myLinkDatabase = new PrefetchProxy(linkDocs); + } if (doc.mySharedDocs === undefined) { let sharedDocs = await DocServer.GetRefField(sharingDocumentId + "outer"); if (!sharedDocs) { @@ -956,7 +965,7 @@ export class CurrentUserUtils { return doc.clickFuncs as Doc; } - static async updateUserDocument(doc: Doc, sharingDocumentId: string) { + 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), @@ -996,9 +1005,8 @@ export class CurrentUserUtils { this.setupOverlays(doc); // documents in overlay layer 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); + await this.setupMenuPanel(doc, sharingDocumentId, linkDatabaseId); if (!doc.globalScriptDatabase) doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument(); - if (!doc.myLinkDatabase) doc.myLinkDatabase = new List([]); setTimeout(() => this.setupDefaultPresentation(doc), 0); // presentation that's initially triggered @@ -1036,10 +1044,10 @@ export class CurrentUserUtils { public static async loadUserDocument(id: string) { this.curr_id = id; await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => { - const { userDocumentId, sharingDocumentId } = JSON.parse(ids); + const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids); if (userDocumentId !== "guest") { return DocServer.GetRefField(userDocumentId).then(async field => - this.updateUserDocument(Doc.SetUserDoc(field instanceof Doc ? field : new Doc(userDocumentId, true)), sharingDocumentId)); + this.updateUserDocument(Doc.SetUserDoc(field instanceof Doc ? field : new Doc(userDocumentId, true)), sharingDocumentId, linkDatabaseId)); } else { throw new Error("There should be a user id! Why does Dash think there isn't one?"); } diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index cc1d45a58..6458de0ed 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -145,8 +145,8 @@ export class GroupManager extends React.Component<{}> { */ addGroup(groupDoc: Doc): boolean { if (this.GroupManagerDoc) { - this.GroupManagerDoc.lastModified = new DateField; Doc.AddDocToList(this.GroupManagerDoc, "data", groupDoc); + this.GroupManagerDoc.lastModified = new DateField; return true; } return false; @@ -160,7 +160,6 @@ export class GroupManager extends React.Component<{}> { deleteGroup(group: Doc): boolean { if (group) { if (this.GroupManagerDoc && this.hasEditAccess(group)) { - this.GroupManagerDoc.lastModified = new DateField; Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group); SharingManager.Instance.removeGroup(group); const members = JSON.parse(StrCast(group.members)); @@ -168,6 +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; if (group === this.currentGroup) { this.currentGroup = undefined; } @@ -187,8 +187,8 @@ export class GroupManager extends React.Component<{}> { const memberList = JSON.parse(StrCast(groupDoc.members)); !memberList.includes(email) && memberList.push(email); groupDoc.members = JSON.stringify(memberList); - this.GroupManagerDoc && (this.GroupManagerDoc.lastModified = new DateField); SharingManager.Instance.shareWithAddedMember(groupDoc, email); + this.GroupManagerDoc && (this.GroupManagerDoc.lastModified = new DateField); } } @@ -204,8 +204,8 @@ export class GroupManager extends React.Component<{}> { if (index !== -1) { const user = memberList.splice(index, 1)[0]; groupDoc.members = JSON.stringify(memberList); - this.GroupManagerDoc && (this.GroupManagerDoc.lastModified = new DateField); SharingManager.Instance.removeMember(groupDoc, email); + this.GroupManagerDoc && (this.GroupManagerDoc.lastModified = new DateField); } } } diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 56b6cb8a9..0456b4029 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -36,17 +36,21 @@ export class LinkManager { public getAllLinks(): Doc[] { - const lset = new Set(DocListCast(Doc.UserDoc().myLinkDatabase)); - SharingManager.Instance.users.forEach(user => DocListCast(user.sharingDoc.myLinkDatabase).map(lset.add)); + const lset = new Set(DocListCast(Doc.LinkDBDoc().data)); + SharingManager.Instance.users.forEach(user => { + DocListCast((user.linkDatabase as Doc)?.data).map(doc => { + lset.add(doc); + }); + }); return Array.from(lset); } public addLink(linkDoc: Doc): boolean { - return Doc.AddDocToList(Doc.UserDoc(), "myLinkDatabase", linkDoc); + return Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc); } public deleteLink(linkDoc: Doc): boolean { - return Doc.RemoveDocFromList(Doc.UserDoc(), "myLinkDatabase", linkDoc); + return Doc.RemoveDocFromList(Doc.LinkDBDoc(), "data", linkDoc); } // finds all links that contain the given anchor diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 16bcd46c8..271face98 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -22,11 +22,11 @@ import "./SharingManager.scss"; import { SelectionManager } from "./SelectionManager"; import { intersection } from "lodash"; import { SearchBox } from "../views/search/SearchBox"; -import { listSpec } from "../../fields/Schema"; export interface User { email: string; sharingDocumentId: string; + linkDatabaseId: string; } /** @@ -53,6 +53,7 @@ const storage = "data"; interface ValidatedUser { user: User; // database minimal info to identify / communicate with a user (email, sharing doc id) sharingDoc: Doc; // document to share/message another user + linkDatabase: Doc; userColor: string; // stored on the sharinDoc, extracted for convenience? } @@ -130,8 +131,10 @@ export class SharingManager extends React.Component<{}> { const isCandidate = user.email !== Doc.CurrentUserEmail; if (isCandidate) { const sharingDoc = await DocServer.GetRefField(user.sharingDocumentId); - if (sharingDoc instanceof Doc) { - sharingDocs.push({ user, sharingDoc, userColor: StrCast(sharingDoc.color) }); + const linkDatabase = await DocServer.GetRefField(user.linkDatabaseId); + if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) { + await DocListCastAsync(linkDatabase.data); + sharingDocs.push({ user, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.color) }); } } }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 598657a58..99a009d13 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -289,10 +289,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } } else { - - const json = JSON.parse(Cast(this.dataDoc[this.fieldKey], RichTextField)?.Data!); - json.selection = state.toJSON().selection; - this._editorView.updateState(EditorState.fromJSON(this.config, json)); + const jsonstring = Cast(this.dataDoc[this.fieldKey], RichTextField)?.Data!; + if (jsonstring) { + const json = JSON.parse(jsonstring); + json.selection = state.toJSON().selection; + this._editorView.updateState(EditorState.fromJSON(this.config, json)); + } } } } @@ -1554,7 +1556,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } @computed get sidebarHandle() { - const annotated = DocListCast(this.dataDoc[this.annotationKey]).filter(d => d?.title).length; + const annotated = DocListCast(this.dataDoc[this.annotationKey]).filter(d => d?.author).length; return !this.props.isSelected() && !(annotated && !this.sidebarWidth()) ? (null) :
manager._searchQuery = query); } export function UserDoc(): Doc { return manager._user_doc; } export function SharingDoc(): Doc { return Cast(Doc.UserDoc().mySharedDocs, Doc, null); } + export function LinkDBDoc(): Doc { return Cast(Doc.UserDoc().myLinkDatabase, Doc, null); } export function SetSelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; } export function GetSelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; } diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts index e5c0f3827..f36506b14 100644 --- a/src/server/ApiManagers/UserManager.ts +++ b/src/server/ApiManagers/UserManager.ts @@ -19,9 +19,9 @@ export default class UserManager extends ApiManager { method: Method.GET, subscription: "/getUsers", secureHandler: async ({ res }) => { - const cursor = await Database.Instance.query({}, { email: 1, sharingDocumentId: 1 }, "users"); + const cursor = await Database.Instance.query({}, { email: 1, linkDatabaseId: 1, sharingDocumentId: 1 }, "users"); const results = await cursor.toArray(); - res.send(results.map(user => ({ email: user.email, sharingDocumentId: user.sharingDocumentId }))); + res.send(results.map(user => ({ email: user.email, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId }))); } }); @@ -47,7 +47,7 @@ export default class UserManager extends ApiManager { register({ method: Method.GET, subscription: "/getUserDocumentIds", - secureHandler: ({ res, user }) => res.send({ userDocumentId: user.userDocumentId, sharingDocumentId: user.sharingDocumentId }) + secureHandler: ({ res, user }) => res.send({ userDocumentId: user.userDocumentId, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId }) }); register({ @@ -56,6 +56,12 @@ export default class UserManager extends ApiManager { secureHandler: ({ res, user }) => res.send(user.sharingDocumentId) }); + register({ + method: Method.GET, + subscription: "/getLinkDatabaseId", + secureHandler: ({ res, user }) => res.send(user.linkDatabaseId) + }); + register({ method: Method.GET, subscription: "/getCurrentUser", diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts index 6bd0e5163..7c441e3c0 100644 --- a/src/server/GarbageCollector.ts +++ b/src/server/GarbageCollector.ts @@ -65,7 +65,7 @@ async function GarbageCollect(full: boolean = true) { // await new Promise(res => setTimeout(res, 3000)); const cursor = await Database.Instance.query({}, { userDocumentId: 1 }, 'users'); const users = await cursor.toArray(); - const ids: string[] = [...users.map(user => user.userDocumentId), ...users.map(user => user.sharingDocumentId)]]; + const ids: string[] = [...users.map(user => user.userDocumentId), ...users.map(user => user.sharingDocumentId), ...users.map(user => user.linkDatabaseId)]; const visited = new Set(); const files: { [name: string]: string[] } = {}; diff --git a/src/server/authentication/AuthenticationManager.ts b/src/server/authentication/AuthenticationManager.ts index 84abd41a2..9eb4a328f 100644 --- a/src/server/authentication/AuthenticationManager.ts +++ b/src/server/authentication/AuthenticationManager.ts @@ -49,6 +49,7 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => { password, userDocumentId: Utils.GenerateGuid(), sharingDocumentId: Utils.GenerateGuid(), + linkDatabaseId: Utils.GenerateGuid(), cacheDocumentIds: "" } as Partial; diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts index 4f2856a78..bee28b96d 100644 --- a/src/server/authentication/DashUserModel.ts +++ b/src/server/authentication/DashUserModel.ts @@ -11,6 +11,7 @@ export type DashUserModel = mongoose.Document & { userDocumentId: string; sharingDocumentId: string; + linkDatabaseId: string; cacheDocumentIds: string; profile: { @@ -39,7 +40,8 @@ const userSchema = new mongoose.Schema({ userDocumentId: String, // id that identifies a document which hosts all of a user's account data sharingDocumentId: String, // id that identifies a document that stores documents shared to a user, their user color, and any additional info needed to communicate between users - cacheDocumentIds: String, + linkDatabaseId: String, + cacheDocumentIds: String, // set of document ids to retreive on startup facebook: String, twitter: String, -- cgit v1.2.3-70-g09d2 From 65448167a4db396b2ee3c56d870233f8ac182eb6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 11 Oct 2020 01:33:28 -0400 Subject: prevented documents that are unshared (ie private to soeone else) from showing up as an empty key value in stacking and other views. tried to fix an exception when showing links (that wasn't repeatable, so dion't know if fix works). --- src/client/views/collections/CollectionSubView.tsx | 2 +- .../CollectionFreeFormLinkView.tsx | 28 ++++++++++++---------- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- 3 files changed, 18 insertions(+), 14 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index fa80c8062..493018093 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -134,7 +134,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : []; } - const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc); + const docs = rawdocs.filter(d => !(d instanceof Promise) && Object.keys(d).length).map(d => d as Doc); const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index f051d5f8d..4cf257640 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -100,13 +100,15 @@ export class CollectionFreeFormLinkView extends React.Component width !== undefined && setTimeout(() => this.tryUpdateHeight(), 0) ); this._disposers.height = reaction( - () => NumCast(this.layoutDoc._height), + () => Cast(this.layoutDoc._height, "number", null), action(height => { if (height !== undefined && height <= 20 && height < NumCast(this.layoutDoc._delayAutoHeight, 20)) { this.layoutDoc._delayAutoHeight = height; -- cgit v1.2.3-70-g09d2 From 3256d0f42401b8dd8745e7f32c25033e118b444b Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 11 Oct 2020 17:22:03 -0400 Subject: removed setting fields to undefined when Acl is Private in hopes of not losing data when permissions are changed. prevented server writes on startup by making renderContentBounds setting a reaction, and making linearView not resize if documents are still promises and make text boxes not flip-flop their height all the time. --- src/client/views/collections/CollectionLinearView.tsx | 4 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 15 +++++++++++++-- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 3 +-- src/fields/Doc.ts | 10 +++++----- 4 files changed, 21 insertions(+), 11 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index 859ee9362..0eac5136a 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -38,8 +38,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { componentDidMount() { // is there any reason this needs to exist? -syip. yes, it handles autoHeight for stacking views (masonry isn't yet supported). - this._widthDisposer = reaction(() => this.props.Document[HeightSym]() + this.childDocs.length + (this.props.Document.linearViewIsExpanded ? 1 : 0), - () => this.props.Document._width = 5 + (this.props.Document.linearViewIsExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10), + this._widthDisposer = reaction(() => 5 + (this.props.Document.linearViewIsExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10), + width => this.childDocs.length && (this.props.Document._width = width), { fireImmediately: true } ); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d48961849..4d9906f93 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -7,7 +7,7 @@ import { Id } from "../../../../fields/FieldSymbols"; import { InkData, InkField, InkTool } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { RichTextField } from "../../../../fields/RichTextField"; -import { createSchema, makeInterface } from "../../../../fields/Schema"; +import { createSchema, makeInterface, listSpec } from "../../../../fields/Schema"; import { ScriptField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { TraceMobx } from "../../../../fields/util"; @@ -88,6 +88,7 @@ export class CollectionFreeFormView extends CollectionSubView(); private _layoutSizeData = new ObservableMap(); private _cachedPool: Map = new Map(); @@ -1159,12 +1160,22 @@ export class CollectionFreeFormView extends CollectionSubView this.doLayoutComputation, (elements) => this._layoutElements = elements || [], { fireImmediately: true, name: "doLayout" }); + if (!this.props.annotationsKey) { + this._boundsReaction = reaction(() => this.contentBounds, + bounds => (!this.fitToContent && this._layoutElements?.length) && setTimeout(() => { + const rbounds = Cast(this.Document._renderContentBounds, listSpec("number"), [0, 0, 0, 0]); + if (rbounds[0] !== bounds.x || rbounds[1] !== bounds.y || rbounds[2] !== bounds.r || rbounds[3] !== bounds.b) { + this.Document._renderContentBounds = new List([bounds.x, bounds.y, bounds.r, bounds.b]); + } + })); + } this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any); } componentWillUnmount() { this._layoutComputeReaction?.(); + this._boundsReaction?.(); this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any); } @@ -1438,10 +1449,10 @@ export class CollectionFreeFormView extends CollectionSubView this.Document._renderContentBounds = new List([this.contentBounds.x, this.contentBounds.y, this.contentBounds.r, this.contentBounds.b]), 0); return
limitHeight) { @@ -1545,7 +1544,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.rootDoc[this.fieldKey + "-height"] = finalHeight; } catch (e) { console.log("Error in tryUpdateHeight"); } } - } + } else this.rootDoc[this.fieldKey + "-height"] = 0; } @computed get audioHandle() { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 7dd894ec4..086b7777f 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -240,11 +240,11 @@ export class Doc extends RefField { if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) { DocServer.GetRefField(this[Id], true); } - if (prev !== AclPrivate && GetEffectiveAcl(this) === AclPrivate) { - this[UpdatingFromServer] = true; - this[FieldsSym](true); - this[UpdatingFromServer] = false; - } + // if (prev !== AclPrivate && GetEffectiveAcl(this) === AclPrivate) { + // this[UpdatingFromServer] = true; + // this[FieldsSym](true); + // this[UpdatingFromServer] = false; + // } }; if (sameAuthor || fKey.startsWith("acl") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { delete this[CachedUpdates][fKey]; -- cgit v1.2.3-70-g09d2 From 8ebf3cb0ac7a023aa47a5264d74c3edaebf28b1b Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 12 Oct 2020 17:22:26 -0400 Subject: updated getEffectiveAcl to be a computedFn. got rid of OverrideAcl in favor of just using UpdatingFromServer --- src/client/DocServer.ts | 2 +- src/client/util/SerializationHelper.ts | 2 +- src/client/views/collections/CollectionSubView.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 8 ++--- src/fields/Doc.ts | 20 +++++++----- src/fields/util.ts | 36 +++++++++------------- 6 files changed, 33 insertions(+), 37 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index d936f6e2a..00f9877c3 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -1,6 +1,6 @@ import * as io from 'socket.io-client'; import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message"; -import { Opt, Doc, fetchProto, FieldsSym, UpdatingFromServer } from '../fields/Doc'; +import { Opt, Doc, UpdatingFromServer } from '../fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { SerializationHelper } from './util/SerializationHelper'; import { RefField } from '../fields/RefField'; diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts index 19b217726..00ac6e521 100644 --- a/src/client/util/SerializationHelper.ts +++ b/src/client/util/SerializationHelper.ts @@ -43,7 +43,7 @@ export namespace SerializationHelper { } if (!obj.__type) { - if (ClientUtils.RELEASE) { + if (true || ClientUtils.RELEASE) { console.warn("No property 'type' found in JSON."); return undefined; } else { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index b282d1e27..f3e563422 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -134,7 +134,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : []; } - const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(d) !== AclPrivate).map(d => d as Doc); + const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate).map(d => d as Doc); const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index da797eda0..4f8f46111 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -11,7 +11,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from " import { ReplaceStep } from 'prosemirror-transform'; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../../fields/DateField'; -import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit, AclAdmin } from "../../../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit, AclAdmin, UpdatingFromServer } from "../../../../fields/Doc"; import { documentSchema } from '../../../../fields/documentSchemas'; import applyDevTools = require("prosemirror-dev-tools"); import { removeMarkWithAttrs } from "./prosemirrorPatches"; @@ -22,7 +22,7 @@ import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { makeInterface } from "../../../../fields/Schema"; import { Cast, DateCast, NumCast, StrCast, ScriptCast, BoolCast } from "../../../../fields/Types"; -import { TraceMobx, OVERRIDE_acl, GetEffectiveAcl } from '../../../../fields/util'; +import { TraceMobx, GetEffectiveAcl } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents, OmitKeys } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../../DocServer"; @@ -805,9 +805,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp tr = tr.addMark(pos, pos + node.nodeSize, link); } }); - OVERRIDE_acl(true); + this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter)); - OVERRIDE_acl(false); + this.dataDoc[UpdatingFromServer] = false; } } componentDidMount() { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 2452ab408..cea09b9c5 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -102,22 +102,28 @@ const AclMap = new Map([ [SharingPermissions.Admin, AclAdmin] ]); -export function fetchProto(doc: Doc) { +// caches the document access permissions for the current user. +// this recursively updates all protos as well. +export function updateCachedAcls(doc: Doc) { if (!doc) return; const permissions: { [key: string]: symbol } = {}; + doc[UpdatingFromServer] = true; Object.keys(doc).filter(key => key.startsWith("acl") && (permissions[key] = AclMap.get(StrCast(doc[key]))!)); + doc[UpdatingFromServer] = false; - if (Object.keys(permissions).length) doc[AclSym] = permissions; + if (Object.keys(permissions).length) { + doc[AclSym] = permissions; + } if (doc.proto instanceof Promise) { - doc.proto.then(fetchProto); + doc.proto.then(updateCachedAcls); return doc.proto; } } @scriptingGlobal -@Deserializable("Doc", fetchProto).withFields(["id"]) +@Deserializable("Doc", updateCachedAcls).withFields(["id"]) export class Doc extends RefField { constructor(id?: FieldId, forceSave?: boolean) { super(id); @@ -233,17 +239,15 @@ export class Doc extends RefField { const prev = GetEffectiveAcl(this); this[UpdatingFromServer] = true; this[fKey] = value; + this[UpdatingFromServer] = false; if (fKey.startsWith("acl")) { - fetchProto(this); + updateCachedAcls(this); } - this[UpdatingFromServer] = false; if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) { DocServer.GetRefField(this[Id], true); } // if (prev !== AclPrivate && GetEffectiveAcl(this) === AclPrivate) { - // this[UpdatingFromServer] = true; // this[FieldsSym](true); - // this[UpdatingFromServer] = false; // } }; if (sameAuthor || fKey.startsWith("acl") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { diff --git a/src/fields/util.ts b/src/fields/util.ts index dd0444d61..d48011194 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,14 +1,14 @@ import { UndoManager } from "../client/util/UndoManager"; -import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym, HeightSym, WidthSym, fetchProto, AclUnset, DocListCastAsync } from "./Doc"; +import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync } from "./Doc"; import { SerializationHelper } from "../client/util/SerializationHelper"; import { ProxyField, PrefetchProxy } from "./Proxy"; import { RefField } from "./RefField"; import { ObjectField } from "./ObjectField"; -import { action, trace, observable, reaction, computed } from "mobx"; -import { Parent, OnUpdate, Update, Id, SelfProxy, Self, HandleUpdate, ToString, ToScriptString } from "./FieldSymbols"; +import { action, trace, } from "mobx"; +import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols"; import { DocServer } from "../client/DocServer"; import { ComputedField } from "./ScriptField"; -import { ScriptCast, StrCast, DateCast, Cast, NumCast } from "./Types"; +import { ScriptCast, StrCast } from "./Types"; import { returnZero } from "../Utils"; import CursorField from "./CursorField"; import { List } from "./List"; @@ -114,10 +114,6 @@ export function makeReadOnly() { export function makeEditable() { _setter = _setterImpl; } -var _overrideAcl = false; -export function OVERRIDE_acl(val: boolean) { - _overrideAcl = val; -} export function normalizeEmail(email: string) { return email.replace(/\./g, '__'); @@ -154,18 +150,19 @@ export enum SharingPermissions { None = "Not Shared" } -// return acl from cache or cache the acl and return. bcz: Argh! NOT WORKING ... nothing gets invalidated properly.... +// return acl from cache or cache the acl and return. const getEffectiveAclCache = computedFn(function (target: any, user?: string) { return getEffectiveAcl(target, user); }, true); /** * Calculates the effective access right to a document for the current user. */ export function GetEffectiveAcl(target: any, user?: string): symbol { - return target ? getEffectiveAcl(target, user) : AclPrivate; + return !target ? AclPrivate : + target[UpdatingFromServer] ? AclAdmin : getEffectiveAclCache(target, user);// all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable) } function getPropAcl(target: any, prop: string | symbol | number) { - if (prop === UpdatingFromServer) return AclAdmin; // requesting the UpdatingFromServer prop must always go through to keep the local DB consistent + if (prop === UpdatingFromServer || target[UpdatingFromServer] || prop == AclSym) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent if (prop && DocServer.PlaygroundFields?.includes(prop.toString())) return AclEdit; // playground props are always editable return GetEffectiveAcl(target); } @@ -173,16 +170,12 @@ function getPropAcl(target: any, prop: string | symbol | number) { let HierarchyMapping: Map | undefined; function getEffectiveAcl(target: any, user?: string): symbol { - if (target[UpdatingFromServer]) return AclAdmin; // all changes received from the server must be processed as Admin - // if the current user is the author of the document / the current user is a member of the admin group - const userChecked = user || Doc.CurrentUserEmail; + const targetAcls = target[AclSym]; + const userChecked = user || Doc.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group if (userChecked === (target.__fields?.author || target.author)) return AclAdmin; // target may be a Doc of Proxy, so check __fields.author and .author if (SnappingManager.GetCachedGroupByName("Admin")) return AclAdmin; - const targetAcls = target[AclSym]; if (targetAcls && Object.keys(targetAcls).length) { - if (_overrideAcl) return AclEdit; - HierarchyMapping = HierarchyMapping || new Map([ [AclPrivate, 0], [AclReadonly, 1], @@ -199,7 +192,6 @@ function getEffectiveAcl(target: any, user?: string): symbol { if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) { if (SnappingManager.GetCachedGroupByName(entity) || userChecked === entity) { effectiveAcl = value as symbol; - if (effectiveAcl === AclAdmin) return effectiveAcl; } } } @@ -278,8 +270,8 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc }); } - layoutDocChanged && fetchProto(target); // updates target[AclSym] when changes to acls have been made - dataDocChanged && fetchProto(dataDoc); + layoutDocChanged && updateCachedAcls(target); // updates target[AclSym] when changes to acls have been made + dataDocChanged && updateCachedAcls(dataDoc); } const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox", @@ -312,9 +304,9 @@ export function setter(target: any, in_prop: string | symbol | number, value: an export function getter(target: any, in_prop: string | symbol | number, receiver: any): any { let prop = in_prop; - if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym]; + if (in_prop === AclSym) return target[AclSym]; if (in_prop === "toString" || (in_prop !== HeightSym && in_prop !== WidthSym && in_prop !== LayoutSym && typeof prop === "symbol")) return target.__fields[prop] || target[prop]; - if (GetEffectiveAcl(target) === AclPrivate && !_overrideAcl) return prop === HeightSym || prop === WidthSym ? returnZero : undefined; + if (GetEffectiveAcl(target) === AclPrivate) return prop === HeightSym || prop === WidthSym ? returnZero : undefined; if (prop === LayoutSym) return target.__LAYOUT__; if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { if (!prop.startsWith("_")) { -- cgit v1.2.3-70-g09d2 From c5a64930895ed102bdfab16c65527c1e47fb484a Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 13 Oct 2020 13:19:36 -0400 Subject: fixed following links from hypertext anchors to use FollowLink --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 4f8f46111..c3946dd57 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1608,7 +1608,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const scale = this.props.hideOnLeave ? 1 : this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1); const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; const interactive = (Doc.GetSelectedTool() === InkTool.None || SnappingManager.GetIsDragging()) && !this.layoutDoc._isBackground; - if (!selected && FormattedTextBoxComment.textBox === this) { FormattedTextBoxComment.Hide(); } + if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(() => FormattedTextBoxComment.Hide()); const minimal = this.props.ignoreAutoHeight; const margins = NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0); const selPad = Math.min(margins, 10); diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index f96fda861..0919b2b14 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -6,7 +6,7 @@ import { EditorState, Plugin } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import * as ReactDOM from 'react-dom'; import wiki from "wikijs"; -import { Doc, DocCastAsync, Opt } from "../../../../fields/Doc"; +import { Doc, DocCastAsync, Opt, DocListCast } from "../../../../fields/Doc"; import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, Utils } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; @@ -256,7 +256,7 @@ export class FormattedTextBoxComment { docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => { if (linkDoc instanceof Doc) { (FormattedTextBoxComment.tooltipText as any).href = href; - FormattedTextBoxComment.linkDoc = linkDoc; + FormattedTextBoxComment.linkDoc = DocListCast(textBox.props.Document.links).find(link => link.anchor1 === textBox.props.Document || link.anchor2 === textBox.props.Document ? link : undefined) || linkDoc; const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc); const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; if (anchor !== target && anchor && target) { -- cgit v1.2.3-70-g09d2