From 442c22e55bf4e53e57378980e7831cfea035d52a Mon Sep 17 00:00:00 2001 From: srichman333 Date: Wed, 21 Jun 2023 16:55:45 -0400 Subject: layout acls initially unset + bug fix with public acl when creating tabs and dashbaords --- src/client/views/collections/CollectionDockingView.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index a5d7e7864..215e9c343 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -2,13 +2,13 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from 'mo import { observer } from 'mobx-react'; import * as ReactDOM from 'react-dom/client'; import * as GoldenLayout from '../../../client/goldenLayout'; -import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { Doc, DocListCast, HierarchyMapping, Opt, ReverseHierarchyMap } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; -import { inheritParentAcls } from '../../../fields/util'; +import { distributeAcls, inheritParentAcls } from '../../../fields/util'; import { emptyFunction, incrementTitleCopy } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { Docs } from '../../documents/Documents'; @@ -29,6 +29,7 @@ import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { TabDocView } from './TabDocView'; import React = require('react'); +import { DocAcl } from '../../../fields/DocSymbols'; const _global = (window /* browser */ || global) /* node */ as any; @observer @@ -485,6 +486,15 @@ export class CollectionDockingView extends CollectionSubView() { } }; tabCreated = (tab: any) => { + const aclKeys = Object.keys(Doc.GetProto(this.props.Document)[DocAcl] ?? {}); + aclKeys.forEach(key => { + if (key != 'acl-Me'){ + const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]) + const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl + const permission = HierarchyMapping.get(permissionSymbol)!.name + distributeAcls(key, permission, Doc.GetProto(tab)) + } + }); this.tabMap.add(tab); tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content) }; @@ -545,7 +555,9 @@ export class CollectionDockingView extends CollectionSubView() { _freeform_backgroundGrid: true, title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); - this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); + this.props.Document.isShared && inheritParentAcls(Doc.GetProto(this.props.Document), Doc.GetProto(docToAdd)); + console.log(Doc.GetProto(this.props.Document)) + console.log(Doc.GetProto(docToAdd)) CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }) -- cgit v1.2.3-70-g09d2 From a807da6b6fed067888f817e2273b29b8b6219e52 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Fri, 23 Jun 2023 12:05:24 -0400 Subject: fixed collection acls --- src/client/documents/Documents.ts | 6 +++--- src/client/views/PropertiesView.tsx | 2 -- src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 8 ++++++-- 3 files changed, 9 insertions(+), 7 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 05994ebbd..0ab0a2ca5 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1011,7 +1011,7 @@ export namespace Docs { I.rotation = 0; I.defaultDoubleClick = 'click'; I.author_date = new DateField(); - // I['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + I['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; //I['acl-Override'] = SharingPermissions.Unset; I[Initializing] = false; return I; @@ -1348,8 +1348,8 @@ export namespace DocUtils { source, target, { - // 'acl-Public': SharingPermissions.Augment, - // '_acl-Public': SharingPermissions.Augment, + 'acl-Public': SharingPermissions.Augment, + '_acl-Public': SharingPermissions.Augment, title: ComputedField.MakeFunction('generateLinkTitle(self)') as any, link_anchor_1_useSmallAnchor: source.useSmallAnchor ? true : undefined, link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined, diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 6bca08d23..d5d09389f 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -425,8 +425,6 @@ export class PropertiesView extends React.Component { * @returns the sharing and permissions panel. */ @computed get sharingTable() { - console.log('doc', this.selectedDoc) - console.log('proto', Doc.GetProto(this.selectedDoc!)) // all selected docs const docs = SelectionManager.Views().length < 2 && this.selectedDoc ? [this.layoutDocAcls ? this.selectedDoc : this.dataDoc!] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document[DocData])); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 77cb7f88c..430e8ecd5 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -9,7 +9,7 @@ import { RichTextField } from '../../../../fields/RichTextField'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; import { Cast, DocCast, FieldValue, NumCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; -import { GetEffectiveAcl } from '../../../../fields/util'; +import { GetEffectiveAcl, SharingPermissions } from '../../../../fields/util'; import { intersectRect, returnFalse, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents'; @@ -390,7 +390,11 @@ export class MarqueeView extends React.Component (d.embedContainer = newCollection)); + newCollection['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + selected.forEach(d => { + d.embedContainer = newCollection; + d['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + }); this.hideMarquee(); return newCollection; }); -- cgit v1.2.3-70-g09d2 From 45e36e459f34a16d8c8f7ae11685d50018e379ab Mon Sep 17 00:00:00 2001 From: srichman333 Date: Fri, 30 Jun 2023 12:09:04 -0400 Subject: fixed inherit parent acls to update shared docs list --- src/client/views/DocComponent.tsx | 35 +++++++++++++++++----- src/client/views/DocumentDecorations.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 2 -- src/fields/util.ts | 7 ++--- 4 files changed, 31 insertions(+), 15 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 6f350e0fc..aa8b145be 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -3,8 +3,8 @@ import { DateField } from '../../fields/DateField'; import { DocListCast, Opt, Doc, ReverseHierarchyMap, HierarchyMapping } from '../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocAcl, DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; -import { Cast, ScriptCast, StrCast } from '../../fields/Types'; -import { denormalizeEmail, distributeAcls, GetEffectiveAcl, inheritParentAcls, SharingPermissions } from '../../fields/util'; +import { Cast, DocCast, ScriptCast, StrCast } from '../../fields/Types'; +import { denormalizeEmail, distributeAcls, GetEffectiveAcl, inheritParentAcls, normalizeEmail, SharingPermissions } from '../../fields/util'; import { returnFalse } from '../../Utils'; import { DocUtils } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; @@ -12,6 +12,7 @@ import { InteractionUtils } from '../util/InteractionUtils'; import { UndoManager } from '../util/UndoManager'; import { DocumentView } from './nodes/DocumentView'; import { Touchable } from './Touchable'; +import { SharingManager } from '../util/SharingManager'; /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) export interface DocComponentProps { @@ -206,13 +207,22 @@ export function ViewBoxAnnotatableComponent

() if (effectiveAcl === AclAugment) { added.map(doc => { - // if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, doc); doc.embedContainer = this.props.Document; if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc); + const parent = DocCast(doc.embedContainer); + doc.embedContainer && inheritParentAcls(parent, doc); + for (const key of Object.keys(parent)) { + const symbol = ReverseHierarchyMap.get(StrCast(parent[key])) + if (symbol && key.startsWith('acl')){ + const sharePermission = HierarchyMapping.get(symbol.acl!)!.name; + const user = SharingManager.Instance?.users.filter(({ user: { email } }) => normalizeEmail(email) == key.slice(4))[0]; + if (user && sharePermission !== SharingPermissions.None) return Doc.AddDocToList(user.sharingDoc, 'data', doc); + } + } }); - } else { - added + } else { + added .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) .map(doc => { // only make a pushpin if we have acl's to edit the document @@ -220,9 +230,18 @@ export function ViewBoxAnnotatableComponent

() doc._stayInCollection = undefined; doc.embedContainer = this.props.Document; if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc; - - // Doc.ActiveDashboard && inheritParentAcls(Doc.ActiveDashboard, doc); - }); + const parent = DocCast(doc.embedContainer); + doc.embedContainer && inheritParentAcls(parent, doc); + for (const key of Object.keys(Doc.GetProto(parent))) { + const symbol = ReverseHierarchyMap.get(StrCast(parent[key])) + if (symbol && key.startsWith('acl')){ + const sharePermission = HierarchyMapping.get(symbol.acl!)!.name; + const user = SharingManager.Instance?.users.filter(({ user: { email } }) => normalizeEmail(email) == key.slice(4))[0]; + if (user && sharePermission !== SharingPermissions.None) return Doc.AddDocToList(user.sharingDoc, 'data', doc); + } + } + }); + const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List; if (annoDocs instanceof List) annoDocs.push(...added); else targetDataDoc[annotationKey ?? this.annotationKey] = new List(added); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index d306d3e45..964aada53 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -772,7 +772,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P // hide the decorations if the parent chooses to hide it or if the document itself hides it const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations; - const hideResizers = hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating; + const hideResizers = ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveLayoutAcl(seldocview.rootDoc)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating; const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.layout_hideDecorationTitle || this._isRounding || this._isRotating; const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.layout_hideDocumentButtonBar || this._isRounding || this._isRotating; // if multiple documents have been opened at the same time, then don't show open button diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 215e9c343..14f03044e 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -556,8 +556,6 @@ export class CollectionDockingView extends CollectionSubView() { title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); this.props.Document.isShared && inheritParentAcls(Doc.GetProto(this.props.Document), Doc.GetProto(docToAdd)); - console.log(Doc.GetProto(this.props.Document)) - console.log(Doc.GetProto(docToAdd)) CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }) diff --git a/src/fields/util.ts b/src/fields/util.ts index f9090e977..d9ccb528b 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -129,11 +129,10 @@ export function denormalizeEmail(email: string) { * Copies parent's acl fields to the child */ export function inheritParentAcls(parent: Doc, child: Doc) { - const dataDoc = parent[DocData]; - for (const key of Object.keys(dataDoc)) { + for (const key of Object.keys(parent)) { // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private. - // const permission: string = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : dataDoc[key]; - const symbol = ReverseHierarchyMap.get(StrCast(dataDoc[key])) + // const permission: string = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : parent[key]; + const symbol = ReverseHierarchyMap.get(StrCast(parent[key])) if (symbol){ const sharePermission = HierarchyMapping.get(symbol.acl!)!.name; key.startsWith('acl') && distributeAcls(key, sharePermission, child) -- cgit v1.2.3-70-g09d2 From 5a9af979cc293d0e3843270ee053c24bf0eb6ef5 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 5 Jul 2023 16:15:27 -0400 Subject: changed acl inheritance for docking views. --- src/client/documents/Documents.ts | 10 ++-- src/client/util/LinkFollower.ts | 2 +- src/client/util/SharingManager.tsx | 70 ++++++++++------------ src/client/views/DocComponent.tsx | 30 +++++----- src/client/views/DocumentDecorations.tsx | 40 ++++++++----- .../views/collections/CollectionDockingView.tsx | 12 ++-- .../views/collections/CollectionTreeView.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 8 +-- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/ComparisonBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- .../formattedText/ProsemirrorExampleTransfer.ts | 10 ++-- src/fields/Doc.ts | 11 +++- src/fields/util.ts | 33 +++++----- 15 files changed, 123 insertions(+), 113 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 06ff95df0..18e8b5940 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -859,7 +859,7 @@ export namespace Docs { // users placeholderDoc as proto if it exists const dataDoc = Doc.assign(placeholderDoc ? Doc.GetProto(placeholderDoc) : Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); - + if (placeholderDoc) { dataDoc.proto = proto; } @@ -1053,7 +1053,7 @@ export namespace Docs { export function FreeformDocument(documents: Array, options: DocumentOptions, id?: string) { const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, ...options, _type_collection: CollectionViewType.Freeform }, id); - documents.forEach(d => (d.embedContainer = inst)); + documents.forEach(d => Doc.SetContainer(d, inst)); return inst; } @@ -1158,7 +1158,9 @@ export namespace Docs { } export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { treeViewFreezeChildren: 'remove|add', ...options, type_collection: CollectionViewType.Docking, dockingConfig: config }, id); + const ret = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { treeViewFreezeChildren: 'remove|add', ...options, type_collection: CollectionViewType.Docking, dockingConfig: config }, id); + documents.map(c => Doc.SetContainer(c, ret)); + return ret; } export function DirectoryImportDocument(options: DocumentOptions = {}) { @@ -1186,7 +1188,7 @@ export namespace Docs { options, id ); - configs.map(c => (c.doc.embedContainer = doc)); + configs.map(c => Doc.SetContainer(c.doc, doc)); return doc; } diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index f74409e42..3e526c4c0 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -103,7 +103,7 @@ export class LinkFollower { Doc.AddDocToList(sourceDocParent, Doc.LayoutFieldKey(sourceDocParent), target); movedTarget = true; } - target.embedContainer = sourceDocParent; + Doc.SetContainer(target, sourceDocParent); const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)]; if (sourceDoc.followLinkXoffset !== undefined && moveTo[0] !== target.x) { target.x = moveTo[0]; diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 534a9cfde..64b2bb5b8 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -1,12 +1,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { IconButton, Size } from 'browndash-components'; import { concat, intersection } from 'lodash'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import Select from 'react-select'; import * as RequestPromise from 'request-promise'; -import { Doc, DocCastAsync, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc'; +import { Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc'; import { AclAdmin, AclPrivate, AclUnset, DocAcl, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; @@ -24,7 +23,6 @@ import { GroupManager, UserOptions } from './GroupManager'; import { GroupMemberView } from './GroupMemberView'; import { SelectionManager } from './SelectionManager'; import './SharingManager.scss'; -import { Docs } from '../documents/Documents'; export interface User { email: string; @@ -166,7 +164,7 @@ export class SharingManager extends React.Component<{}> { // or if the 'Override Nested' checkbox is selected var childDocs = DocListCast(target.data); childDocs.map(doc => { - if (this.overrideNested || doc[acl]==undefined){ + if (this.overrideNested || doc[acl] == undefined) { this.setInternalSharing(recipient, permission, doc); } }); @@ -207,7 +205,7 @@ export class SharingManager extends React.Component<{}> { // or if the 'Override Private' checkbox is selected var childDocs = DocListCast(target.data); childDocs.map(doc => { - if (this.overrideNested || doc[acl]==undefined){ + if (this.overrideNested || doc[acl] == undefined) { this.setInternalGroupSharing(group, permission, doc); } }); @@ -288,7 +286,7 @@ export class SharingManager extends React.Component<{}> { this.shareFromPropertiesSidebar(shareWith, permission, DocListCast(doc.data), layout); } if (GetEffectiveAcl(doc) === AclAdmin) { - if ( shareWith == 'Public' && layout) shareWith = 'Public-layout'; + if (shareWith == 'Public' && layout) shareWith = 'Public-layout'; distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined, isDashboard); } this.setDashboardBackground(doc, permission as SharingPermissions); @@ -538,13 +536,12 @@ export class SharingManager extends React.Component<{}> { const uniform = docs.map(doc => (this.layoutDocAcls ? doc : doc[DocData])).every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]); // const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; let permissions = this.layoutDocAcls ? (targetDoc[DocAcl][userKey] ? HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name : StrCast(Doc.GetProto(targetDoc)[userKey])) : StrCast(targetDoc[userKey]); - if (this.layoutDocAcls){ + if (this.layoutDocAcls) { if (targetDoc[DocAcl][userKey]) permissions = HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name; - else if (targetDoc['embedContainer']) permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]); + else if (targetDoc['embedContainer']) permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]); else permissions = uniform ? StrCast(Doc.GetProto(targetDoc)?.[userKey]) : '-multiple-'; - } - else permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; - + } else permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; + return !permissions ? null : (

{user.email} @@ -570,12 +567,11 @@ export class SharingManager extends React.Component<{}> { // the owner of the doc and the current user are placed at the top of the user list. const userKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`; var curUserPermission; - if (this.layoutDocAcls){ - if (targetDoc[DocAcl][userKey]) curUserPermission = HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name; - else if (targetDoc['embedContainer']) curUserPermission = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]); - else curUserPermission = StrCast(Doc.GetProto(targetDoc)?.[userKey]); - } - else curUserPermission = StrCast(targetDoc[userKey]); + if (this.layoutDocAcls) { + if (targetDoc[DocAcl][userKey]) curUserPermission = HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name; + else if (targetDoc['embedContainer']) curUserPermission = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]); + else curUserPermission = StrCast(Doc.GetProto(targetDoc)?.[userKey]); + } else curUserPermission = StrCast(targetDoc[userKey]); // const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name userListContents.unshift( sameAuthor ? ( @@ -609,16 +605,15 @@ export class SharingManager extends React.Component<{}> { .every(doc => (this.layoutDocAcls ? doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey] : doc?.[DocData]?.[DocAcl]?.[groupKey] === docs[0]?.[DocData]?.[DocAcl]?.[groupKey])); // const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-'; let permissions = this.layoutDocAcls ? (targetDoc[DocAcl][groupKey] ? HierarchyMapping.get(targetDoc[DocAcl][groupKey])?.name : StrCast(Doc.GetProto(targetDoc)[groupKey])) : StrCast(targetDoc[groupKey]); - if (this.layoutDocAcls){ - if (groupKey == 'acl-Public') groupKey = 'acl-Public-layout'; - if (targetDoc[DocAcl][groupKey]) permissions = HierarchyMapping.get(targetDoc[DocAcl][groupKey])?.name; - else{ - if (groupKey == 'acl-Public-layout') groupKey = 'acl-Public'; - if (targetDoc['embedContainer']) permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[groupKey]); - else permissions = uniform ? StrCast(Doc.GetProto(targetDoc)?.[groupKey]) : '-multiple-'; - } + if (this.layoutDocAcls) { + if (groupKey == 'acl-Public') groupKey = 'acl-Public-layout'; + if (targetDoc[DocAcl][groupKey]) permissions = HierarchyMapping.get(targetDoc[DocAcl][groupKey])?.name; + else { + if (groupKey == 'acl-Public-layout') groupKey = 'acl-Public'; + if (targetDoc['embedContainer']) permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[groupKey]); + else permissions = uniform ? StrCast(Doc.GetProto(targetDoc)?.[groupKey]) : '-multiple-'; } - else permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-'; + } else permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-'; return !permissions ? null : (
@@ -629,7 +624,7 @@ export class SharingManager extends React.Component<{}> {
) : null} -
+
{admin || this.myDocAcls ? ( {this.sharingOptions(true)} @@ -706,9 +700,9 @@ export class SharingManager extends React.Component<{}> {
) : ( -
-
-
+
+
+
(this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} />
@@ -717,7 +711,7 @@ export class SharingManager extends React.Component<{}> {
(this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}> -
+
Individuals   {this.individualSort === 'ascending' ? ( @@ -732,7 +726,7 @@ export class SharingManager extends React.Component<{}> {
(this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}> -
+
Groups  
GroupManager.Instance?.open())}> diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index f3aa8451a..1d0feec74 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -196,52 +196,52 @@ export function ViewBoxAnnotatableComponent

() aclKeys.forEach(key => added.forEach(d => { - if (key != 'acl-Me'){ - const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]) - const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl - const permission = HierarchyMapping.get(permissionSymbol)!.name - distributeAcls(key, permission, Doc.GetProto(d)) + if (key != 'acl-Me') { + const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]); + const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl; + const permission = HierarchyMapping.get(permissionSymbol)!.name; + distributeAcls(key, permission, Doc.GetProto(d)); } }) ); if (effectiveAcl === AclAugment) { added.map(doc => { - doc.embedContainer = this.props.Document; + Doc.SetContainer(doc, this.props.Document); if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc); const parent = DocCast(doc.embedContainer); doc.embedContainer && inheritParentAcls(parent, doc); for (const key of Object.keys(parent)) { - const symbol = ReverseHierarchyMap.get(StrCast(parent[key])) - if (symbol && key.startsWith('acl')){ + const symbol = ReverseHierarchyMap.get(StrCast(parent[key])); + if (symbol && key.startsWith('acl')) { const sharePermission = HierarchyMapping.get(symbol.acl!)!.name; const user = SharingManager.Instance?.users.filter(({ user: { email } }) => normalizeEmail(email) == key.slice(4))[0]; if (user && sharePermission !== SharingPermissions.None) return Doc.AddDocToList(user.sharingDoc, 'data', doc); } } }); - } else { - added + } else { + added .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) .map(doc => { // only make a pushpin if we have acl's to edit the document //DocUtils.LeavePushpin(doc); doc._dragOnlyWithinContainer = undefined; - doc.embedContainer = this.props.Document; + Doc.SetContainer(doc, this.props.Document); if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc; const parent = DocCast(doc.embedContainer); doc.embedContainer && inheritParentAcls(parent, doc); for (const key of Object.keys(Doc.GetProto(parent))) { - const symbol = ReverseHierarchyMap.get(StrCast(parent[key])) - if (symbol && key.startsWith('acl')){ + const symbol = ReverseHierarchyMap.get(StrCast(parent[key])); + if (symbol && key.startsWith('acl')) { const sharePermission = HierarchyMapping.get(symbol.acl!)!.name; const user = SharingManager.Instance?.users.filter(({ user: { email } }) => normalizeEmail(email) == key.slice(4))[0]; if (user && sharePermission !== SharingPermissions.None) return Doc.AddDocToList(user.sharingDoc, 'data', doc); } } - }); - + }); + const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List; if (annoDocs instanceof List) annoDocs.push(...added.filter(add => !annoDocs.includes(add))); else targetDataDoc[annotationKey ?? this.annotationKey] = new List(added); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 4454a3ec1..cdd9e62d8 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -201,9 +201,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => { const first = SelectionManager.Views()[0]; const effectiveLayoutAcl = GetEffectiveLayoutAcl(first.rootDoc); - if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment){ + if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment) { return false; - } + } const dragDocView = SelectionManager.Views()[0]; const containers = new Set(); SelectionManager.Views().forEach(v => containers.add(DocCast(v.rootDoc.embedContainer))); @@ -766,13 +766,14 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P // sharing const acl = this.showLayoutAcl ? GetEffectiveLayoutAcl(seldocview.rootDoc) : GetEffectiveAcl(seldocview.rootDoc); - const docShareMode = HierarchyMapping.get(acl)!.name + const docShareMode = HierarchyMapping.get(acl)!.name; const shareMode = StrCast(docShareMode); var shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image; // hide the decorations if the parent chooses to hide it or if the document itself hides it const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations; - const hideResizers = ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveLayoutAcl(seldocview.rootDoc)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating; + const hideResizers = + ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveLayoutAcl(seldocview.rootDoc)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating; const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.layout_hideDecorationTitle || this._isRounding || this._isRotating; const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.layout_hideDocumentButtonBar || this._isRounding || this._isRotating; // if multiple documents have been opened at the same time, then don't show open button @@ -826,26 +827,26 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P const radiusHandleLocation = Math.min(radiusHandle, maxDist); const sharingMenu = docShareMode ? ( -

+
  {shareSymbolIcon + ' ' + shareMode}   - {!Doc.noviceMode ? -
-
- (this.showLayoutAcl = !this.showLayoutAcl))} /> + {!Doc.noviceMode ? ( +
+
+ (this.showLayoutAcl = !this.showLayoutAcl))} />
-
Layout
+
Layout
- : null } + ) : null}  
) : (
); - + const titleArea = this._editingTitle ? ( e.stopPropagation()} /> ) : ( -
{e.stopPropagation}}> - { hideTitle ? null : {this.selectionTitle}} +
{ + e.stopPropagation; + }}> + {hideTitle ? null : ( + + {this.selectionTitle} + + )} {sharingMenu} {!useLock ? null : ( toggle ability to interact with document
} placement="top"> @@ -872,7 +882,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P )}
); - + return (
Doc.MakeEmbedding(origtabdoc)); if (newtabdocs.length) { Doc.GetProto(newtab).data = new List(newtabdocs); - newtabdocs.forEach(ntab => (ntab.embedContainer = newtab)); + newtabdocs.forEach(ntab => Doc.SetContainer(ntab, newtab)); } json = json.replace(origtab[Id], newtab[Id]); return newtab; @@ -497,11 +497,11 @@ export class CollectionDockingView extends CollectionSubView() { tabCreated = (tab: any) => { const aclKeys = Object.keys(Doc.GetProto(this.props.Document)[DocAcl] ?? {}); aclKeys.forEach(key => { - if (key != 'acl-Me'){ - const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]) - const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl - const permission = HierarchyMapping.get(permissionSymbol)!.name - distributeAcls(key, permission, Doc.GetProto(tab)) + if (key != 'acl-Me') { + const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]); + const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl; + const permission = HierarchyMapping.get(permissionSymbol)!.name; + distributeAcls(key, permission, Doc.GetProto(tab)); } }); this.tabMap.add(tab); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 4cd3885f5..d78a0e781 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -180,7 +180,7 @@ export class CollectionTreeView extends CollectionSubView (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => { const res = flg && Doc.AddDocToList(this.doc[DocData], this.props.fieldKey, doc, relativeTo, before); - res && (doc.embedContainer = this.props.Document); + res && Doc.SetContainer(doc, this.props.Document); return res; }, true); if (this.doc.resolvedDataDoc instanceof Promise) return false; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 4d780f46b..3473eee18 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -262,7 +262,7 @@ export class TabDocView extends React.Component { pinDoc.presMovement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom; pinDoc.presDuration = pinDoc.presDuration ?? 1000; pinDoc.groupWithUp = false; - pinDoc.embedContainer = curPres; + Doc.SetContainer(pinDoc, curPres); // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area pinDoc.treeViewHeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling. diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 8d8d895c6..a2269075d 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -392,7 +392,7 @@ export class TreeView extends React.Component { const innerAdd = (doc: Doc) => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); - dataIsComputed && (doc.embedContainer = this.doc.embedContainer); + dataIsComputed && Doc.SetContainer(doc, this.doc.embedContainer); return added; }; return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); @@ -455,7 +455,7 @@ export class TreeView extends React.Component { const innerAdd = (doc: Doc) => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); - dataIsComputed && (doc.embedContainer = this.doc.embedContainer); + dataIsComputed && Doc.SetContainer(doc, this.doc.embedContainer); return added; }; return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); @@ -563,7 +563,7 @@ export class TreeView extends React.Component { } const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); - !dataIsComputed && added && (doc.embedContainer = this.doc.embedContainer); + !dataIsComputed && added && Doc.SetContainer(doc, this.doc.embedContainer); return added; }; @@ -1192,7 +1192,7 @@ export class TreeView extends React.Component { TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined; Doc.AddDocToList(newParent, fieldKey, child, addAfter, false); newParent.treeViewOpen = true; - child.embedContainer = treeView.Document; + Doc.SetContainer(child, treeView.Document); } }; const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1])); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 9b0abc48b..764b1e08a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -392,7 +392,7 @@ export class MarqueeView extends React.Component { - d.embedContainer = newCollection; + Doc.SetContainer(d, newCollection); d['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; }); this.hideMarquee(); diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 2290e0711..ca5ec9389 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -46,7 +46,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey)); - droppedDocs.lastElement().embedContainer = this.dataDoc; + Doc.SetContainer(droppedDocs.lastElement(), this.dataDoc); !added && e.preventDefault(); e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place return added; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 202a9f851..e5ea8e0c1 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -564,7 +564,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent>(schema: S, props: any, mapKey switch (GetEffectiveAcl(props.DataDoc)) { case AclAugment: const prevNode = state.selection.$cursor.nodeBefore; - const prevUser = prevNode.marks[prevNode.marks.length-1].attrs.userid - if (prevUser != Doc.CurrentUserEmail){ + const prevUser = !prevNode ? Doc.CurrentUserEmail : prevNode.marks[prevNode.marks.length - 1].attrs.userid; + if (prevUser != Doc.CurrentUserEmail) { return false; } } @@ -263,7 +263,7 @@ export function buildKeymap>(schema: S, props: any, mapKey bind('Backspace', (state: EditorState, dispatch: (tx: Transaction) => void) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; - + if ( !deleteSelection(state, (tx: Transaction) => { dispatch(updateBullets(tx, schema)); @@ -334,7 +334,7 @@ export function buildKeymap>(schema: S, props: any, mapKey //Command to create a blank space bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (GetEffectiveAcl(props.DataDoc)!=AclEdit && GetEffectiveAcl(props.DataDoc)!=AclAugment && GetEffectiveAcl(props.DataDoc)!=AclAdmin) return true; + if (GetEffectiveAcl(props.DataDoc) != AclEdit && GetEffectiveAcl(props.DataDoc) != AclAugment && GetEffectiveAcl(props.DataDoc) != AclAdmin) return true; const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); dispatch(splitMetadata(marks, state.tr)); return false; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 99712fb04..9c138d348 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -20,7 +20,6 @@ import { AclEdit, AclPrivate, AclReadonly, - AclSelfEdit, AclUnset, Animation, CachedUpdates, @@ -138,7 +137,7 @@ export const HierarchyMapping: Map = new Map(Array.from(HierarchyMapping.entries()).map(value => [value[1].name, { level: value[1].level, acl: value[0], image: value[1].image }])); +export const ReverseHierarchyMap: Map = new Map(Array.from(HierarchyMapping.entries()).map(value => [value[1].name, { level: value[1].level, acl: value[0], image: value[1].image }])); // caches the document access permissions for the current user. // this recursively updates all protos as well. @@ -478,6 +477,14 @@ export namespace Doc { // }); // } + export function SetContainer(doc: Doc, container: Doc) { + doc.embedContainer = container; + if (Doc.GetProto(container).author === doc.author) { + Object.keys(Doc.GetProto(container)) + .filter(key => key.startsWith('acl')) + .forEach(key => (doc[key] = Doc.GetProto(container)[key])); + } + } export function RunCachedUpdate(doc: Doc, field: string) { const update = doc[CachedUpdates][field]; if (update) { diff --git a/src/fields/util.ts b/src/fields/util.ts index 0f613d926..48de36efe 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -131,10 +131,10 @@ export function inheritParentAcls(parent: Doc, child: Doc) { for (const key of Object.keys(parent)) { // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private. // const permission: string = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : parent[key]; - const symbol = ReverseHierarchyMap.get(StrCast(parent[key])) - if (symbol){ + const symbol = ReverseHierarchyMap.get(StrCast(parent[key])); + if (symbol) { const sharePermission = HierarchyMapping.get(symbol.acl!)!.name; - key.startsWith('acl') && distributeAcls(key, sharePermission, child) + key.startsWith('acl') && distributeAcls(key, sharePermission, child); } } } @@ -171,7 +171,7 @@ const getEffectiveAclCache = computedFn(function (target: any, user?: string) { // return layout acl from cache or chache the acl and return. const getEffectiveLayoutAclCache = computedFn(function (target: any, user?: string) { return getEffectiveLayoutAcl(target, user); - }, true); +}, true); /** * Calculates the effective access right to a document for the current user. @@ -183,13 +183,13 @@ export function GetEffectiveAcl(target: any, user?: string): symbol { } /** -* Calculates the effective access layout right to a document for the current user. By getting the container's effective acl if the layout acl isn't set. -*/ + * Calculates the effective access layout right to a document for the current user. By getting the container's effective acl if the layout acl isn't set. + */ export function GetEffectiveLayoutAcl(target: any, user?: string): symbol { if (!target) return AclPrivate; if (target[UpdatingFromServer]) return AclAdmin; return getEffectiveLayoutAclCache(target, user); - } +} function getPropAcl(target: any, prop: string | symbol | number) { if (typeof prop === 'symbol' || target[UpdatingFromServer]) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent @@ -234,9 +234,9 @@ function getEffectiveAcl(target: any, user?: string): symbol { } /** -* Returns the layout acl that is effective on the document passed through as the target. If no layout acls -* have been set, it returns the regular acls for the document target is contained in. -*/ + * Returns the layout acl that is effective on the document passed through as the target. If no layout acls + * have been set, it returns the regular acls for the document target is contained in. + */ function getEffectiveLayoutAcl(target: any, user?: string): symbol { const targetAcls = target[DocAcl]; @@ -248,27 +248,24 @@ function getEffectiveLayoutAcl(target: any, user?: string): symbol { if ((GetCachedGroupByName(entity) || userChecked === entity || entity === 'Me') && entity != 'Public') { if (effectiveAcl && HierarchyMapping.get(value as symbol)!.level > HierarchyMapping.get(effectiveAcl)!.level) { effectiveAcl = value as symbol; - } - else{ + } else { effectiveAcl = value as symbol; } } } - if (effectiveAcl){ + if (effectiveAcl) { return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)!.level < aclLevel.editable ? AclEdit : effectiveAcl; - } - else{ + } else { return GetEffectiveAcl(Doc.GetProto(target['embedContainer']), user); } } // authored documents are private until an ACL is set. const targetAuthor = target.__fieldTuples?.author || target.author; // target may be a Doc of Proxy, so check __fieldTuples.author and .author if (targetAuthor && targetAuthor !== userChecked) return AclPrivate; - return AclAdmin; + return AclAdmin; } - /** * Recursively distributes the access right for a user across the children of a document and its annotations. * @param key the key storing the access right (e.g. acl-groupname) @@ -286,7 +283,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc //apparently we can't call updateCachedAcls twice (once for the main dashboard, and again for the nested dashboard...???) updateCachedAcls(target); } - return; + //return; } visited.push(target); -- cgit v1.2.3-70-g09d2 From fa38dbe06d6ddb5f4499b759459a24d2b3c111e8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 7 Jul 2023 13:36:21 -0400 Subject: a bunch of fixes to simplify collaboration and make it work better. --- src/client/DocServer.ts | 12 +- src/client/util/SharingManager.tsx | 203 +++++---------------- src/client/views/DashboardView.scss | 139 +++++++------- src/client/views/DashboardView.tsx | 18 +- src/client/views/DocComponent.tsx | 61 ++----- src/client/views/DocumentDecorations.tsx | 29 ++- src/client/views/GlobalKeyHandler.ts | 12 +- src/client/views/MainView.tsx | 4 +- src/client/views/PropertiesView.tsx | 47 ++--- .../views/collections/CollectionDockingView.tsx | 17 +- src/client/views/collections/TreeView.tsx | 6 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/fields/Doc.ts | 4 +- src/fields/util.ts | 77 ++------ 14 files changed, 231 insertions(+), 400 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 2a7f5a09b..876f2400d 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -76,10 +76,14 @@ export namespace DocServer { const fieldWriteModes: { [field: string]: WriteMode } = {}; const docsWithUpdates: { [field: string]: Set } = {}; - export var PlaygroundFields: string[]; - export function setPlaygroundFields(livePlaygroundFields: string[]) { - DocServer.PlaygroundFields = livePlaygroundFields; - livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.Playground)); + export var PlaygroundFields: string[] = []; + export function setLivePlaygroundFields(livePlaygroundFields: string[]) { + DocServer.PlaygroundFields.push(...livePlaygroundFields); + livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground)); + } + export function setPlaygroundFields(playgroundFields: string[]) { + DocServer.PlaygroundFields.push(...playgroundFields); + playgroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.Playground)); } export function IsPlaygroundField(field: string) { return DocServer.PlaygroundFields?.includes(field.replace(/^_/, '')); diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 64b2bb5b8..d3241009a 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -6,10 +6,9 @@ import * as React from 'react'; import Select from 'react-select'; import * as RequestPromise from 'request-promise'; import { Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc'; -import { AclAdmin, AclPrivate, AclUnset, DocAcl, DocData } from '../../fields/DocSymbols'; +import { AclAdmin, AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; -import { List } from '../../fields/List'; -import { DocCast, NumCast, StrCast } from '../../fields/Types'; +import { StrCast } from '../../fields/Types'; import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util'; import { Utils } from '../../Utils'; import { DocServer } from '../DocServer'; @@ -73,13 +72,11 @@ export class SharingManager extends React.Component<{}> { @observable private individualSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of individuals @observable private groupSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of groups private shareDocumentButtonRef: React.RefObject = React.createRef(); // ref for the share button, used for the position of the popup - private distributeAclsButtonRef: React.RefObject = React.createRef(); // ref for the distribute button, used for the position of the popup // if both showUserOptions and showGroupOptions are false then both are displayed @observable private showUserOptions: boolean = false; // whether to show individuals as options when sharing (in the react-select component) @observable private showGroupOptions: boolean = false; // // whether to show groups as options when sharing (in the react-select component) private populating: boolean = false; // whether the list of users is populating or not @observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used - @observable private overrideNested: boolean = false; // whether child docs in a collection/dashboard should be changed to be less private @observable private myDocAcls: boolean = false; // whether the My Docs checkbox is selected or not // private get linkVisible() { @@ -110,7 +107,6 @@ export class SharingManager extends React.Component<{}> { 500 ); this.layoutDocAcls = false; - this.overrideNested = false; }); constructor(props: {}) { @@ -153,41 +149,19 @@ export class SharingManager extends React.Component<{}> { /** * Shares the document with a user. */ - setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc?: Doc) => { + setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc: Doc | undefined) => { const { user, sharingDoc } = recipient; const target = targetDoc || this.targetDoc!; const acl = `acl-${normalizeEmail(user.email)}`; const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`; - const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1; - - // setting the same acl for a docs within the doc being shared if they haven't been set yet - // or if the 'Override Nested' checkbox is selected - var childDocs = DocListCast(target.data); - childDocs.map(doc => { - if (this.overrideNested || doc[acl] == undefined) { - this.setInternalSharing(recipient, permission, doc); - } - }); - - const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); - - // ! ensures it returns true if document has been shared successfully, false otherwise - return !docs - .map(doc => (this.layoutDocAcls ? doc : Doc.GetProto(doc))) - .map(doc => { - doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined, isDashboard); + const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc); + docs.map(doc => (this.layoutDocAcls ? doc : Doc.GetProto(doc))).forEach(doc => { + doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined); - if (permission === SharingPermissions.None) { - if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 1) - 1; - } else { - if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1; - } - distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard); - this.setDashboardBackground(doc, permission as SharingPermissions); - if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); - return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc); - }) - .some(success => !success); + distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined); + if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); + else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc); + }); }; /** @@ -199,51 +173,24 @@ export class SharingManager extends React.Component<{}> { const target = targetDoc || this.targetDoc!; const key = normalizeEmail(StrCast(group.title)); let acl = `acl-${key}`; - const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1; - - // setting the same acl for a docs within the doc being shared if they haven't been set yet - // or if the 'Override Private' checkbox is selected - var childDocs = DocListCast(target.data); - childDocs.map(doc => { - if (this.overrideNested || doc[acl] == undefined) { - this.setInternalGroupSharing(group, permission, doc); - } - }); - - const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); - - if (acl == 'acl-Public' && this.layoutDocAcls) acl = 'acl-Public-layout'; - // ! ensures it returns true if document has been shared successfully, false otherwise - return !docs - .map(doc => (this.layoutDocAcls ? doc : doc[DocData])) - .map(doc => { - doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc, undefined, undefined, isDashboard); - - if (permission === SharingPermissions.None) { - if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 1) - 1; - } else { - if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 0) + 1; - } - distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard); - this.setDashboardBackground(doc, permission as SharingPermissions); + const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc); + docs.map(doc => (this.layoutDocAcls ? doc : Doc.GetProto(doc))).forEach(doc => { + doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc, undefined, undefined); - if (group instanceof Doc) { - const members: string[] = JSON.parse(StrCast(group.members)); - const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); + distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined); - // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc - group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List).push(doc) : (group.docsShared = new List([doc])); + if (group instanceof Doc) { + Doc.AddDocToList(group, 'docsShared', doc); - return users - .map(({ user, sharingDoc }) => { - if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added - else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists - }) - .some(success => !success); - } - }) - .some(success => success); + this.users + .filter(({ user: { email } }) => JSON.parse(StrCast(group.members)).includes(email)) + .forEach(({ user, sharingDoc }) => { + if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added + else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists + }); + } + }); }; /** @@ -279,44 +226,15 @@ export class SharingManager extends React.Component<{}> { else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc); }); } else { - const dashboards = DocListCast(Doc.MyDashboards.data); docs.forEach(doc => { - const isDashboard = dashboards.indexOf(doc) !== -1; - if (this.overrideNested) { - this.shareFromPropertiesSidebar(shareWith, permission, DocListCast(doc.data), layout); - } if (GetEffectiveAcl(doc) === AclAdmin) { - if (shareWith == 'Public' && layout) shareWith = 'Public-layout'; - distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined, isDashboard); + distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined); } - this.setDashboardBackground(doc, permission as SharingPermissions); }); } this.layoutDocAcls = false; }; - /** - * Sets the background of the Dashboard if it has been shared as a visual indicator - */ - setDashboardBackground = (doc: Doc, permission: SharingPermissions) => { - if (Doc.IndexOf(doc, DocListCast(Doc.MyDashboards.data)) !== -1) { - if (permission !== SharingPermissions.None) { - doc.isShared = true; - doc.backgroundColor = 'green'; - } else { - const acls = doc[DocData][DocAcl]; - if ( - Object.keys(acls) - .filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && key !== 'acl-Me') - .every(key => [AclUnset, AclPrivate].includes(acls[key])) - ) { - doc.isShared = undefined; - doc.backgroundColor = undefined; - } - } - } - }; - /** * Removes the documents shared with a user through a group when the user is removed from the group. * @param group @@ -341,12 +259,9 @@ export class SharingManager extends React.Component<{}> { */ removeGroup = (group: Doc) => { if (group.docsShared) { - const dashboards = DocListCast(Doc.MyDashboards.data); DocListCast(group.docsShared).forEach(doc => { const acl = `acl-${StrCast(group.title)}`; - const isDashboard = dashboards.indexOf(doc) !== -1; - distributeAcls(acl, SharingPermissions.None, doc, undefined, undefined, isDashboard); - distributeAcls(acl, SharingPermissions.None, Doc.GetProto(doc), undefined, undefined, isDashboard); + distributeAcls(acl, SharingPermissions.None, doc, undefined, undefined); const members: string[] = JSON.parse(StrCast(group.members)); const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); @@ -440,7 +355,7 @@ export class SharingManager extends React.Component<{}> { if (this.selectedUsers) { this.selectedUsers.forEach(user => { if (user.value.includes(indType)) { - this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions); + this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions, undefined); } else { this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions); } @@ -457,7 +372,6 @@ export class SharingManager extends React.Component<{}> { ); this.layoutDocAcls = false; - this.overrideNested = false; this.selectedUsers = null; } }; @@ -509,8 +423,7 @@ export class SharingManager extends React.Component<{}> { const users = this.individualSort === 'ascending' ? this.users.slice().sort(this.sortUsers) : this.individualSort === 'descending' ? this.users.slice().sort(this.sortUsers).reverse() : this.users; const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList; - // handles the case where multiple documents are selected - let docs = SelectionManager.Views().length < 2 ? [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DocData]] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DocData])); + let docs = SelectionManager.Views().length < 2 ? [this.targetDoc] : SelectionManager.Views().map(docView => docView.rootDoc); if (this.myDocAcls) { const newDocs: Doc[] = []; @@ -518,36 +431,32 @@ export class SharingManager extends React.Component<{}> { docs = newDocs.filter(doc => GetEffectiveAcl(doc) === AclAdmin); } - const targetDoc: Doc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; + const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; // tslint:disable-next-line: no-unnecessary-callback-wrapper const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc)); const admin = this.myDocAcls ? Boolean(docs.length) : effectiveAcls.every(acl => acl === AclAdmin); // users in common between all docs - const commonKeys = intersection(...docs.map(doc => (this.layoutDocAcls ? doc : doc[DocData])).map(doc => doc?.[DocAcl] && Object.keys(doc[DocAcl]))); + const commonKeys = intersection(docs).reduce((list, doc) => (doc?.[DocAcl] ? [...list, ...Object.keys(doc[DocAcl])] : list), [] as string[]); // the list of users shared with - const userListContents: (JSX.Element | null)[] = users + const userListContents = users // .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email)) .filter(({ user }) => docs[0]?.author !== user.email) .map(({ user, linkDatabase, sharingDoc, userColor }) => { const userKey = `acl-${normalizeEmail(user.email)}`; - const uniform = docs.map(doc => (this.layoutDocAcls ? doc : doc[DocData])).every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]); + const uniform = docs.every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]); // const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; - let permissions = this.layoutDocAcls ? (targetDoc[DocAcl][userKey] ? HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name : StrCast(Doc.GetProto(targetDoc)[userKey])) : StrCast(targetDoc[userKey]); - if (this.layoutDocAcls) { - if (targetDoc[DocAcl][userKey]) permissions = HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name; - else if (targetDoc['embedContainer']) permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]); - else permissions = uniform ? StrCast(Doc.GetProto(targetDoc)?.[userKey]) : '-multiple-'; - } else permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; + let permissions = targetDoc[DocAcl][userKey] ? HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name : StrCast(targetDoc[userKey]); + permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; return !permissions ? null : (
{user.email}
{admin || this.myDocAcls ? ( - this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value, undefined)}> {this.sharingOptions(uniform)} ) : ( @@ -566,12 +475,7 @@ export class SharingManager extends React.Component<{}> { // the owner of the doc and the current user are placed at the top of the user list. const userKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`; - var curUserPermission; - if (this.layoutDocAcls) { - if (targetDoc[DocAcl][userKey]) curUserPermission = HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name; - else if (targetDoc['embedContainer']) curUserPermission = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[userKey]); - else curUserPermission = StrCast(Doc.GetProto(targetDoc)?.[userKey]); - } else curUserPermission = StrCast(targetDoc[userKey]); + const curUserPermission = StrCast(targetDoc[userKey]); // const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name userListContents.unshift( sameAuthor ? ( @@ -600,20 +504,8 @@ export class SharingManager extends React.Component<{}> { groupListMap.unshift({ title: 'Public' }); //, { title: "ALL" }); const groupListContents = groupListMap.map(group => { let groupKey = `acl-${StrCast(group.title)}`; - const uniform = docs - .map(doc => (this.layoutDocAcls ? doc : doc[DocData])) - .every(doc => (this.layoutDocAcls ? doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey] : doc?.[DocData]?.[DocAcl]?.[groupKey] === docs[0]?.[DocData]?.[DocAcl]?.[groupKey])); - // const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-'; - let permissions = this.layoutDocAcls ? (targetDoc[DocAcl][groupKey] ? HierarchyMapping.get(targetDoc[DocAcl][groupKey])?.name : StrCast(Doc.GetProto(targetDoc)[groupKey])) : StrCast(targetDoc[groupKey]); - if (this.layoutDocAcls) { - if (groupKey == 'acl-Public') groupKey = 'acl-Public-layout'; - if (targetDoc[DocAcl][groupKey]) permissions = HierarchyMapping.get(targetDoc[DocAcl][groupKey])?.name; - else { - if (groupKey == 'acl-Public-layout') groupKey = 'acl-Public'; - if (targetDoc['embedContainer']) permissions = StrCast(Doc.GetProto(DocCast(targetDoc['embedContainer']))[groupKey]); - else permissions = uniform ? StrCast(Doc.GetProto(targetDoc)?.[groupKey]) : '-multiple-'; - } - } else permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-'; + const uniform = docs.every(doc => doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey]); + const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-'; return !permissions ? null : (
@@ -693,7 +585,6 @@ export class SharingManager extends React.Component<{}> {
{Doc.noviceMode ? null : (
- (this.overrideNested = !this.overrideNested))} checked={this.overrideNested} /> (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} />
)} @@ -713,16 +604,10 @@ export class SharingManager extends React.Component<{}> {
(this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}>
Individuals   - {this.individualSort === 'ascending' ? ( - - ) : this.individualSort === 'descending' ? ( - - ) : ( - - )} +
-
{userListContents}
+
{userListContents}
(this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}> @@ -732,13 +617,7 @@ export class SharingManager extends React.Component<{}> {
  - {this.groupSort === 'ascending' ? ( - - ) : this.groupSort === 'descending' ? ( - - ) : ( - - )} +
{groupListContents}
diff --git a/src/client/views/DashboardView.scss b/src/client/views/DashboardView.scss index b8a6f6c05..a37df106b 100644 --- a/src/client/views/DashboardView.scss +++ b/src/client/views/DashboardView.scss @@ -1,39 +1,38 @@ -@import "./global/globalCssVariables"; - +@import './global/globalCssVariables'; .dashboard-view { - padding: 50px; - display: flex; - flex-direction: row; - width: 100%; - position: absolute; - - .left-menu { - display: flex; - justify-content: flex-start; - flex-direction: column; - width: 250px; - min-width: 250px; - } - - .all-dashboards { - display: flex; - flex-direction: row; - flex-wrap: wrap; - overflow-y: scroll; - } + padding: 50px; + display: flex; + flex-direction: row; + width: 100%; + position: absolute; + + .left-menu { + display: flex; + justify-content: flex-start; + flex-direction: column; + width: 250px; + min-width: 250px; + } + + .all-dashboards { + display: flex; + flex-direction: row; + flex-wrap: wrap; + overflow-y: scroll; + } } .text-button { cursor: pointer; - padding: 3px 0; - &:hover { - font-weight: 500; - } - - &.selected { - font-weight: 700; - } + padding: 3px 0; + &:hover { + font-weight: 500; + } + + &.selected { + font-weight: 700; + } } .new-dashboard-button { @@ -64,41 +63,51 @@ } .dashboard-container { - border-radius: 10px; - cursor: pointer; - width: 250px; - height: 200px; - outline: solid 2px $light-gray; - display: flex; - flex-direction: column; - margin: 0 0px 30px 30px; - overflow: hidden; - - &:hover{ + border-radius: 10px; + cursor: pointer; + width: 250px; + height: 200px; + outline: solid 2px $light-gray; + display: flex; + flex-direction: column; + margin: 0 0px 30px 30px; + overflow: hidden; + + &:hover { outline: solid 2px $light-blue; - } - - .title { - margin: 10px; - font-weight: 500; - } - - img { - width: auto; - height: 80%; - } - - .info { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - padding: 0px 10px; - } - - .more { - z-index: 100; - } + } + + .title { + margin: 10px; + font-weight: 500; + } + + img { + width: auto; + height: 80%; + } + + .info { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 0px 10px; + } + .dashboard-status, + .dashboard-status-shared { + font-size: 9; + left: 10%; + position: relative; + top: -5; + } + .dashboard-status-shared { + background: 'lightgreen'; + } + + .more { + z-index: 100; + } } .new-dashboard { @@ -136,4 +145,4 @@ flex-direction: row; justify-content: flex-end; } -} \ No newline at end of file +} diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index b60b84015..6feeb8846 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -4,7 +4,7 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, DocListCastAsync } from '../../fields/Doc'; -import { DocData } from '../../fields/DocSymbols'; +import { AclPrivate, AclUnset, DocAcl, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { PrefetchProxy } from '../../fields/Proxy'; @@ -67,7 +67,7 @@ export class DashboardView extends React.Component { if (this.selectedDashboardGroup === DashboardGroup.MyDashboards) { return allDashboards.filter(dashboard => Doc.GetProto(dashboard).author === Doc.CurrentUserEmail); } else { - const sharedDashboards = DocListCast(Doc.MySharedDocs.data).filter(doc => doc.dockingConfig) + const sharedDashboards = DocListCast(Doc.MySharedDocs.data).filter(doc => doc.dockingConfig); // const sharedDashboards = DocListCast(Doc.MySharedDocs.data).filter(doc => doc._type_collection === CollectionViewType.Docking); return sharedDashboards; } @@ -168,15 +168,24 @@ export class DashboardView extends React.Component {
{this.getDashboards().map(dashboard => { const href = ImageCast(dashboard.thumb)?.url.href; + const shared = Object.keys(dashboard[DocAcl]) + .filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && !['acl-Me', 'acl-Public'].includes(key)) + .some(key => ![AclUnset, AclPrivate].includes(dashboard[DocAcl][key])); return ( -
this.onContextMenu(dashboard, e)} onClick={e => this.clickDashboard(e, dashboard)}> +
this.onContextMenu(dashboard, e)} onClick={e => this.clickDashboard(e, dashboard)}>
- e.stopPropagation()} defaultValue={StrCast(dashboard.title)} onChange={e => (Doc.GetProto(dashboard).title = (e.target as any).value)} /> + e.stopPropagation()} + defaultValue={StrCast(dashboard.title)} + onChange={e => (Doc.GetProto(dashboard).title = (e.target as any).value)} + /> {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ?
unviewed
:
}
} />
+
{shared ? 'shared' : ''}
); })} diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 1d0feec74..44af51341 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -1,18 +1,16 @@ import { action, computed, observable } from 'mobx'; import { DateField } from '../../fields/DateField'; -import { DocListCast, Opt, Doc, ReverseHierarchyMap, HierarchyMapping } from '../../fields/Doc'; +import { Doc, DocListCast, HierarchyMapping, Opt, ReverseHierarchyMap } from '../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocAcl, DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; -import { Cast, DocCast, ScriptCast, StrCast } from '../../fields/Types'; -import { denormalizeEmail, distributeAcls, GetEffectiveAcl, inheritParentAcls, normalizeEmail, SharingPermissions } from '../../fields/util'; +import { Cast, StrCast } from '../../fields/Types'; +import { distributeAcls, GetEffectiveAcl, inheritParentAcls, SharingPermissions } from '../../fields/util'; import { returnFalse } from '../../Utils'; import { DocUtils } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { InteractionUtils } from '../util/InteractionUtils'; -import { UndoManager } from '../util/UndoManager'; import { DocumentView } from './nodes/DocumentView'; import { Touchable } from './Touchable'; -import { SharingManager } from '../util/SharingManager'; /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) export interface DocComponentProps { @@ -184,7 +182,7 @@ export function ViewBoxAnnotatableComponent

() if (this.props.filterAddDocument?.(docs) === false || docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.props.Document))) { return false; } - const targetDataDoc = this.props.Document[DocData]; + const targetDataDoc = this.rootDoc[DocData]; const effectiveAcl = GetEffectiveAcl(targetDataDoc); if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) { @@ -194,52 +192,33 @@ export function ViewBoxAnnotatableComponent

() if (added.length) { const aclKeys = Object.keys(Doc.GetProto(this.props.Document)[DocAcl] ?? {}); - aclKeys.forEach(key => - added.forEach(d => { - if (key != 'acl-Me') { - const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]); - const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl; - const permission = HierarchyMapping.get(permissionSymbol)!.name; - distributeAcls(key, permission, Doc.GetProto(d)); - } - }) - ); + GetEffectiveAcl(this.rootDoc) === AclAdmin && + aclKeys.forEach(key => + added.forEach(d => { + if (key != 'acl-Me') { + const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]); + const permissionSymbol = ReverseHierarchyMap.get(permissionString)?.acl; + const permission = permissionSymbol && HierarchyMapping.get(permissionSymbol)?.name; + distributeAcls(key, permission ?? SharingPermissions.Augment, Doc.GetProto(d)); + } + }) + ); if (effectiveAcl === AclAugment) { added.map(doc => { - Doc.SetContainer(doc, this.props.Document); - if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; + if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc; Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc); - const parent = DocCast(doc.embedContainer); - doc.embedContainer && inheritParentAcls(parent, doc); - for (const key of Object.keys(parent)) { - const symbol = ReverseHierarchyMap.get(StrCast(parent[key])); - if (symbol && key.startsWith('acl')) { - const sharePermission = HierarchyMapping.get(symbol.acl!)!.name; - const user = SharingManager.Instance?.users.filter(({ user: { email } }) => normalizeEmail(email) == key.slice(4))[0]; - if (user && sharePermission !== SharingPermissions.None) return Doc.AddDocToList(user.sharingDoc, 'data', doc); - } - } + Doc.SetContainer(doc, targetDataDoc); + inheritParentAcls(targetDataDoc, doc); }); } else { added .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) .map(doc => { - // only make a pushpin if we have acl's to edit the document - //DocUtils.LeavePushpin(doc); doc._dragOnlyWithinContainer = undefined; - Doc.SetContainer(doc, this.props.Document); if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc; - const parent = DocCast(doc.embedContainer); - doc.embedContainer && inheritParentAcls(parent, doc); - for (const key of Object.keys(Doc.GetProto(parent))) { - const symbol = ReverseHierarchyMap.get(StrCast(parent[key])); - if (symbol && key.startsWith('acl')) { - const sharePermission = HierarchyMapping.get(symbol.acl!)!.name; - const user = SharingManager.Instance?.users.filter(({ user: { email } }) => normalizeEmail(email) == key.slice(4))[0]; - if (user && sharePermission !== SharingPermissions.None) return Doc.AddDocToList(user.sharingDoc, 'data', doc); - } - } + Doc.SetContainer(doc, this.rootDoc); + inheritParentAcls(targetDataDoc, doc); }); const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index cdd9e62d8..3522830e5 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -5,7 +5,6 @@ import { IconButton } from 'browndash-components'; import { action, computed, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { FaUndo } from 'react-icons/fa'; -import { Utils, aggregateBounds, emptyFunction, numberValue, returnFalse, setupMoveUpEvents } from '../../Utils'; import { DateField } from '../../fields/DateField'; import { Doc, DocListCast, Field, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, DocData, Height, Width } from '../../fields/DocSymbols'; @@ -13,27 +12,27 @@ import { InkField } from '../../fields/InkField'; import { RichTextField } from '../../fields/RichTextField'; import { ScriptField } from '../../fields/ScriptField'; import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; -import { GetEffectiveAcl, GetEffectiveLayoutAcl, normalizeEmail, SharingPermissions } from '../../fields/util'; -import { DocumentType } from '../documents/DocumentTypes'; +import { GetEffectiveAcl } from '../../fields/util'; +import { aggregateBounds, emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils'; import { Docs } from '../documents/Documents'; +import { DocumentType } from '../documents/DocumentTypes'; import { DocumentManager } from '../util/DocumentManager'; import { DragManager } from '../util/DragManager'; import { LinkFollower } from '../util/LinkFollower'; import { SelectionManager } from '../util/SelectionManager'; -import { SettingsManager } from '../util/SettingsManager'; import { SnappingManager } from '../util/SnappingManager'; import { UndoManager } from '../util/UndoManager'; +import { CollectionDockingView } from './collections/CollectionDockingView'; +import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { DocumentButtonBar } from './DocumentButtonBar'; import './DocumentDecorations.scss'; -import { InkStrokeProperties } from './InkStrokeProperties'; +import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; +import { InkStrokeProperties } from './InkStrokeProperties'; import { LightboxView } from './LightboxView'; -import { CollectionDockingView } from './collections/CollectionDockingView'; -import { CollectionFreeFormView } from './collections/collectionFreeForm'; -import { Colors } from './global/globalEnums'; import { DocumentView, OpenWhereMod } from './nodes/DocumentView'; -import { ImageBox } from './nodes/ImageBox'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; +import { ImageBox } from './nodes/ImageBox'; import React = require('react'); import _ = require('lodash'); @@ -165,7 +164,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P @action onContainerDown = (e: React.PointerEvent): void => { const first = SelectionManager.Views()[0]; - const effectiveLayoutAcl = GetEffectiveLayoutAcl(first.rootDoc); + const effectiveLayoutAcl = GetEffectiveAcl(first.rootDoc); if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) { setupMoveUpEvents( this, @@ -179,7 +178,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P @action onTitleDown = (e: React.PointerEvent): void => { const first = SelectionManager.Views()[0]; - const effectiveLayoutAcl = GetEffectiveLayoutAcl(first.rootDoc); + const effectiveLayoutAcl = GetEffectiveAcl(first.rootDoc); if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) { setupMoveUpEvents( this, @@ -200,7 +199,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P @action onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => { const first = SelectionManager.Views()[0]; - const effectiveLayoutAcl = GetEffectiveLayoutAcl(first.rootDoc); + const effectiveLayoutAcl = GetEffectiveAcl(first.rootDoc); if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment) { return false; } @@ -496,7 +495,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => { const first = SelectionManager.Views()[0]; - const effectiveAcl = GetEffectiveLayoutAcl(first.rootDoc); + const effectiveAcl = GetEffectiveAcl(first.rootDoc); if (!(effectiveAcl == AclAdmin || effectiveAcl == AclEdit || effectiveAcl == AclAugment)) return false; if (!first) return false; let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY }; @@ -765,7 +764,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } // sharing - const acl = this.showLayoutAcl ? GetEffectiveLayoutAcl(seldocview.rootDoc) : GetEffectiveAcl(seldocview.rootDoc); + const acl = GetEffectiveAcl(!this.showLayoutAcl ? Doc.GetProto(seldocview.rootDoc) : seldocview.rootDoc); const docShareMode = HierarchyMapping.get(acl)!.name; const shareMode = StrCast(docShareMode); var shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image; @@ -773,7 +772,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P // hide the decorations if the parent chooses to hide it or if the document itself hides it const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations; const hideResizers = - ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveLayoutAcl(seldocview.rootDoc)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating; + ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(seldocview.rootDoc)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating; const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.layout_hideDecorationTitle || this._isRounding || this._isRotating; const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.layout_hideDocumentButtonBar || this._isRounding || this._isRotating; // if multiple documents have been opened at the same time, then don't show open button diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 47dcdd2e4..347c40c18 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -285,13 +285,17 @@ export class KeyManager { preventDefault = false; break; case 'y': - SelectionManager.DeselectAll(); - UndoManager.Redo(); + if (Doc.ActivePage !== 'home') { + SelectionManager.DeselectAll(); + UndoManager.Redo(); + } stopPropagation = false; break; case 'z': - SelectionManager.DeselectAll(); - UndoManager.Undo(); + if (Doc.ActivePage !== 'home') { + SelectionManager.DeselectAll(); + UndoManager.Undo(); + } stopPropagation = false; break; case 'a': diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 258674d53..b708e2587 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -153,10 +153,12 @@ export class MainView extends React.Component { } this._sidebarContent.proto = undefined; if (!MainView.Live) { - DocServer.setPlaygroundFields([ + DocServer.setPlaygroundFields(['dockingConfig']); + DocServer.setLivePlaygroundFields([ 'dataTransition', 'viewTransition', 'treeViewOpen', + 'treeViewExpandedView', 'carousel_index', 'itemIndex', // for changing slides in presentations 'layout_sidebarWidthPercent', diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 14291b537..633401d58 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -395,9 +395,7 @@ export class PropertiesView extends React.Component {

-
- {admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission) : concat(shareImage, ' ', permission)} -
+
{admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission) : concat(shareImage, ' ', permission)}
@@ -425,11 +423,10 @@ export class PropertiesView extends React.Component { */ @computed get sharingTable() { // all selected docs - const docs = - SelectionManager.Views().length < 2 && this.selectedDoc ? [this.layoutDocAcls ? this.selectedDoc : this.dataDoc!] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document[DocData])); + const docs = SelectionManager.Views().length < 2 && this.selectedDoc ? [this.selectedDoc] : SelectionManager.Views().map(docView => docView.rootDoc); const target = docs[0]; - const showAdmin = GetEffectiveAcl(target) == AclAdmin + const showAdmin = GetEffectiveAcl(target) == AclAdmin; const individualTableEntries = []; const usersAdded: string[] = []; // all shared users being added - organized by denormalized email @@ -450,13 +447,7 @@ export class PropertiesView extends React.Component { usersAdded.sort(this.sortUsers); usersAdded.map(userEmail => { const userKey = `acl-${normalizeEmail(userEmail)}`; - var permission; - if (this.layoutDocAcls){ - if (target[DocAcl][userKey]) permission = HierarchyMapping.get(target[DocAcl][userKey])?.name; - else if (target['embedContainer']) permission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))[userKey]); - else permission = StrCast(Doc.GetProto(target)?.[userKey]); - } - else permission = StrCast(target[userKey]); + var permission = StrCast(target[userKey]); individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user }); @@ -466,12 +457,11 @@ export class PropertiesView extends React.Component { if (userEmail == 'guest') userEmail = 'Public'; if (!usersAdded.includes(userEmail) && userEmail != 'Public' && userEmail != target.author) { var permission; - if (this.layoutDocAcls){ + if (this.layoutDocAcls) { if (target[DocAcl][userKey]) permission = HierarchyMapping.get(target[DocAcl][userKey])?.name; else if (target['embedContainer']) permission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))[userKey]); else permission = StrCast(Doc.GetProto(target)?.[userKey]); - } - else permission = StrCast(target[userKey]); + } else permission = StrCast(target[userKey]); individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user } @@ -481,36 +471,29 @@ export class PropertiesView extends React.Component { // adds groups const groupTableEntries: JSX.Element[] = []; const groupList = GroupManager.Instance?.allGroups || []; - groupList.sort(this.sortGroups) + groupList.sort(this.sortGroups); groupList.map(group => { if (group.title != 'Public' && this.selectedDoc) { const groupKey = 'acl-' + normalizeEmail(StrCast(group.title)); if (this.selectedDoc[groupKey] != '' && this.selectedDoc[groupKey] != undefined) { var permission; - if (this.layoutDocAcls){ - if (target[DocAcl][groupKey]){ + if (this.layoutDocAcls) { + if (target[DocAcl][groupKey]) { permission = HierarchyMapping.get(target[DocAcl][groupKey])?.name; - } - else if (target['embedContainer']) permission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))[groupKey]); + } else if (target['embedContainer']) permission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))[groupKey]); else permission = StrCast(Doc.GetProto(target)?.[groupKey]); - } - else permission = StrCast(target[groupKey]); + } else permission = StrCast(target[groupKey]); groupTableEntries.unshift(this.sharingItem(StrCast(group.title), showAdmin, permission!, false)); } } }); // public permission - let publicPermission = StrCast(target['acl-Public']); - if (this.layoutDocAcls){ - if (target['acl-Public-layout']) publicPermission = StrCast(target['acl-Public-layout']); - else if (target['embedContainer']) publicPermission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))['acl-Public']); - else StrCast(Doc.GetProto(target)['acl-Public']); - } + const publicPermission = StrCast((this.layoutDocAcls ? target : Doc.GetProto(target))['acl-Public']); return (
-
+
Public / Guest Users
{this.colorACLDropDown('Public', showAdmin, publicPermission!, false)}
@@ -518,7 +501,7 @@ export class PropertiesView extends React.Component {

Individual Users with Access to this Document{' '}
{
{individualTableEntries}
}
- {groupTableEntries.length>0 ? + {groupTableEntries.length > 0 ? (
{' '} @@ -526,7 +509,7 @@ export class PropertiesView extends React.Component {
{
{groupTableEntries}
}
- : null} + ) : null}
); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index ce9eb9f17..0daa3dd92 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -8,7 +8,7 @@ import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; -import { distributeAcls, inheritParentAcls } from '../../../fields/util'; +import { distributeAcls, GetEffectiveAcl, GetPropAcl, inheritParentAcls } from '../../../fields/util'; import { emptyFunction, incrementTitleCopy } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { Docs } from '../../documents/Documents'; @@ -30,7 +30,7 @@ import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { TabDocView } from './TabDocView'; import React = require('react'); import { DocumentManager } from '../../util/DocumentManager'; -import { DocAcl } from '../../../fields/DocSymbols'; +import { AclAdmin, AclEdit, DocAcl } from '../../../fields/DocSymbols'; const _global = (window /* browser */ || global) /* node */ as any; @observer @@ -386,8 +386,13 @@ export class CollectionDockingView extends CollectionSubView() { .map(f => f as Doc); const changesMade = this.props.Document.dockingConfig !== json; if (changesMade) { - this.props.Document.dockingConfig = json; - this.props.Document.data = new List(docs); + if (![AclAdmin, AclEdit].includes(GetEffectiveAcl(this.dataDoc))) { + this.layoutDoc.dockingConfig = json; + this.layoutDoc.data = new List(docs); + } else { + Doc.SetInPlace(this.rootDoc, 'dockingConfig', json, true); + Doc.SetInPlace(this.rootDoc, 'data', new List(docs), true); + } } this._flush?.end(); this._flush = undefined; @@ -521,7 +526,7 @@ export class CollectionDockingView extends CollectionSubView() { _layout_fitWidth: true, title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); - this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); + inheritParentAcls(this.rootDoc, docToAdd); CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }); @@ -564,7 +569,7 @@ export class CollectionDockingView extends CollectionSubView() { _freeform_backgroundGrid: true, title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); - this.props.Document.isShared && inheritParentAcls(Doc.GetProto(this.props.Document), Doc.GetProto(docToAdd)); + inheritParentAcls(this.dataDoc, Doc.GetProto(docToAdd)); CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }) diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index a2269075d..7767c5b79 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -392,7 +392,7 @@ export class TreeView extends React.Component { const innerAdd = (doc: Doc) => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); - dataIsComputed && Doc.SetContainer(doc, this.doc.embedContainer); + dataIsComputed && Doc.SetContainer(doc, DocCast(this.doc.embedContainer)); return added; }; return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); @@ -455,7 +455,7 @@ export class TreeView extends React.Component { const innerAdd = (doc: Doc) => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); - dataIsComputed && Doc.SetContainer(doc, this.doc.embedContainer); + dataIsComputed && Doc.SetContainer(doc, DocCast(this.doc.embedContainer)); return added; }; return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); @@ -563,7 +563,7 @@ export class TreeView extends React.Component { } const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); - !dataIsComputed && added && Doc.SetContainer(doc, this.doc.embedContainer); + !dataIsComputed && added && Doc.SetContainer(doc, DocCast(this.doc.embedContainer)); return added; }; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index dab269474..a4f5eb62b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -535,7 +535,7 @@ export class DocumentViewInternal extends DocComponent key.startsWith('acl')) + .filter(key => key.startsWith('acl') && !key.includes(Doc.CurrentUserEmailNormalized)) .forEach(key => (doc[key] = Doc.GetProto(container)[key])); } } @@ -696,7 +696,7 @@ export namespace Doc { Doc.SetLayout(embedding, Doc.MakeEmbedding(layout)); } embedding.createdFrom = doc; - embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = NumCast(Doc.GetProto(doc).proto_embeddingId) + 1; + embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = DocListCast(Doc.GetProto(doc).proto_embeddings).length + 1; embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`); embedding.author = Doc.CurrentUserEmail; diff --git a/src/fields/util.ts b/src/fields/util.ts index fc0057827..9230d9df0 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -14,6 +14,7 @@ import { List } from './List'; import { ObjectField } from './ObjectField'; import { PrefetchProxy, ProxyField } from './Proxy'; import { RefField } from './RefField'; +import { RichTextField } from './RichTextField'; import { SchemaHeaderField } from './SchemaHeaderField'; import { ComputedField } from './ScriptField'; import { ScriptCast, StrCast } from './Types'; @@ -62,8 +63,9 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number const writeMode = DocServer.getFieldWriteMode(prop as string); const fromServer = target[UpdatingFromServer]; const sameAuthor = fromServer || receiver.author === Doc.CurrentUserEmail; - const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAugment || effectiveAcl === AclAdmin || writeMode !== DocServer.WriteMode.LiveReadonly; - const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAugment || effectiveAcl === AclAdmin) && !DocServer.Control.isReadOnly(); + const writeToDoc = + sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Playground || writeMode === DocServer.WriteMode.LivePlayground || (effectiveAcl === AclAugment && value instanceof RichTextField); + const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclAugment && value instanceof RichTextField)) && !DocServer.Control.isReadOnly(); if (writeToDoc) { if (value === undefined) { @@ -104,7 +106,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number ); return true; } - return false; + return true; }); let _setter: (target: any, prop: string | symbol | number, value: any, receiver: any) => boolean = _setterImpl; @@ -128,6 +130,7 @@ export function denormalizeEmail(email: string) { * Copies parent's acl fields to the child */ export function inheritParentAcls(parent: Doc, child: Doc) { + if (GetEffectiveAcl(parent) !== AclAdmin) return; for (const key of Object.keys(parent)) { // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private. // const permission: string = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : parent[key]; @@ -168,30 +171,16 @@ const getEffectiveAclCache = computedFn(function (target: any, user?: string) { return getEffectiveAcl(target, user); }, true); -// return layout acl from cache or chache the acl and return. -const getEffectiveLayoutAclCache = computedFn(function (target: any, user?: string) { - return getEffectiveLayoutAcl(target, user); -}, true); - /** * Calculates the effective access right to a document for the current user. */ export function GetEffectiveAcl(target: any, user?: string): symbol { if (!target) return AclPrivate; if (target[UpdatingFromServer]) return AclAdmin; - return getEffectiveAclCache(Doc.GetProto(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) -} - -/** - * Calculates the effective access layout right to a document for the current user. By getting the container's effective acl if the layout acl isn't set. - */ -export function GetEffectiveLayoutAcl(target: any, user?: string): symbol { - if (!target) return AclPrivate; - if (target[UpdatingFromServer]) return AclAdmin; - return getEffectiveLayoutAclCache(target, user); + return 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) { +export function GetPropAcl(target: any, prop: string | symbol | number) { if (typeof prop === 'symbol' || target[UpdatingFromServer]) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent if (prop && DocServer.IsPlaygroundField(prop.toString())) return AclEdit; // playground props are always editable return GetEffectiveAcl(target); @@ -233,37 +222,6 @@ function getEffectiveAcl(target: any, user?: string): symbol { return AclAdmin; } -/** - * Returns the layout acl that is effective on the document passed through as the target. If no layout acls - * have been set, it returns the regular acls for the document target is contained in. - */ -function getEffectiveLayoutAcl(target: any, user?: string): symbol { - const targetAcls = target[DocAcl]; - - 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 (targetAcls && Object.keys(targetAcls).length) { - var effectiveAcl; - for (const [key, value] of Object.entries(targetAcls)) { - const entity = denormalizeEmail(key.substring(4)); // an individual or a group - if ((GetCachedGroupByName(entity) || userChecked === entity || entity === 'Me') && entity != 'Public') { - if (effectiveAcl && HierarchyMapping.get(value as symbol)!.level > HierarchyMapping.get(effectiveAcl)!.level) { - effectiveAcl = value as symbol; - } else { - effectiveAcl = value as symbol; - } - } - } - - if (effectiveAcl) { - return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)!.level < aclLevel.editable ? AclEdit : effectiveAcl; - } - } - // authored documents are private until an ACL is set. - const targetAuthor = target.__fieldTuples?.author || target.author; // target may be a Doc or Proxy, so check __fieldTuples.author and .author - if (targetAuthor && targetAuthor !== userChecked) return AclPrivate; - return AclAdmin; -} - /** * Recursively distributes the access right for a user across the children of a document and its annotations. * @param key the key storing the access right (e.g. acl-groupname) @@ -272,11 +230,13 @@ function getEffectiveLayoutAcl(target: any, user?: string): symbol { * @param inheritingFromCollection whether the target is being assigned rights after being dragged into a collection (and so is inheriting the acls from the collection) * inheritingFromCollection is not currently being used but could be used if acl assignment defaults change */ -export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean, visited?: Doc[], isDashboard?: boolean) { +export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean, visited?: Doc[]) { if (!visited) visited = [] as Doc[]; if (!target || visited.includes(target)) return; if ((target._type_collection === CollectionViewType.Docking && visited.length > 1) || Doc.GetProto(visited[0]) !== Doc.GetProto(target)) { - target[key] = acl; + if (target.author !== Doc.CurrentUserEmail || key !== `acl-${Doc.CurrentUserEmailNormalized}`) { + target[key] = acl; + } if (target !== Doc.GetProto(target)) { //apparently we can't call updateCachedAcls twice (once for the main dashboard, and again for the nested dashboard...???) updateCachedAcls(target); @@ -288,19 +248,16 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym]) // if it is inheriting from a collection, it only inherits if A) the key doesn't already exist or B) the right being inherited is more restrictive if (GetEffectiveAcl(target) === AclAdmin && (!inheritingFromCollection || !target[key] || ReverseHierarchyMap.get(StrCast(target[key]))!.level > ReverseHierarchyMap.get(acl)!.level)) { - target[key] = acl; - layoutDocChanged = true; - if (isDashboard) { - DocListCastAsync(target[Doc.LayoutFieldKey(target)]).then(docs => { - docs?.forEach(d => distributeAcls(key, acl, d, inheritingFromCollection, visited)); - }); + if (target.author !== Doc.CurrentUserEmail || key !== `acl-${Doc.CurrentUserEmailNormalized}`) { + target[key] = acl; + layoutDocChanged = true; } } let dataDocChanged = false; const dataDoc = target[DocData]; if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || ReverseHierarchyMap.get(StrCast(dataDoc[key]))! > ReverseHierarchyMap.get(acl)!)) { - if (GetEffectiveAcl(dataDoc) === AclAdmin) { + if (GetEffectiveAcl(dataDoc) === AclAdmin && (target.author !== Doc.CurrentUserEmail || key !== `acl-${Doc.CurrentUserEmailNormalized}`)) { dataDoc[key] = acl; dataDocChanged = true; } @@ -327,7 +284,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { let prop = in_prop; - const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : getPropAcl(target, prop); + const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : GetPropAcl(target, prop); if (effectiveAcl !== AclEdit && effectiveAcl !== AclAugment && effectiveAcl !== AclAdmin) return true; // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't if (typeof prop === 'string' && prop.startsWith('acl') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true; -- cgit v1.2.3-70-g09d2 From cf88809ea2299395db70ce608c193df7f24f0fb2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 7 Jul 2023 15:57:51 -0400 Subject: fixed self-ownership of copied Docs created by someone else. prevent dash from being pasted into itself. fixed inheritance of acls --- src/client/DocServer.ts | 6 +- src/client/util/SharingManager.tsx | 109 +++++++++++---------- src/client/views/DocComponent.tsx | 32 ++---- src/client/views/PreviewCursor.tsx | 26 ++--- .../views/collections/CollectionDockingView.tsx | 12 +-- src/fields/Doc.ts | 4 +- src/fields/util.ts | 60 +++++------- 7 files changed, 120 insertions(+), 129 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 8b8a9a618..67be96d13 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -198,7 +198,7 @@ export namespace DocServer { export namespace Control { let _isReadOnly = false; export function makeReadOnly() { - if (!_isReadOnly) { + if (!Control.isReadOnly()) { _isReadOnly = true; _CreateField = field => (_cache[field[Id]] = field); _UpdateField = emptyFunction; @@ -207,7 +207,7 @@ export namespace DocServer { } export function makeEditable() { - if (_isReadOnly) { + if (Control.isReadOnly() && Doc.CurrentUserEmail !== 'guest') { location.reload(); // _isReadOnly = false; // _CreateField = _CreateFieldImpl; @@ -218,7 +218,7 @@ export namespace DocServer { } export function isReadOnly() { - return _isReadOnly; + return _isReadOnly || Doc.CurrentUserEmail === 'guest'; } } diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index d3241009a..828271270 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -1,4 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Colors } from 'browndash-components'; import { concat, intersection } from 'lodash'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -22,6 +23,7 @@ import { GroupManager, UserOptions } from './GroupManager'; import { GroupMemberView } from './GroupMemberView'; import { SelectionManager } from './SelectionManager'; import './SharingManager.scss'; +import { undoable } from './UndoManager'; export interface User { email: string; @@ -76,8 +78,10 @@ export class SharingManager extends React.Component<{}> { @observable private showUserOptions: boolean = false; // whether to show individuals as options when sharing (in the react-select component) @observable private showGroupOptions: boolean = false; // // whether to show groups as options when sharing (in the react-select component) private populating: boolean = false; // whether the list of users is populating or not + @observable private overrideNested: boolean = false; // whether child docs in a collection/dashboard should be changed to be less private - initially selected so default is override @observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used @observable private myDocAcls: boolean = false; // whether the My Docs checkbox is selected or not + @observable private _buttonDown = false; // private get linkVisible() { // return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false; @@ -91,6 +95,7 @@ export class SharingManager extends React.Component<{}> { DictationOverlay.Instance.hasActiveModal = true; this.isOpen = this.targetDoc !== undefined; this.permissions = SharingPermissions.Augment; + this.overrideNested = true; }); }; @@ -149,36 +154,30 @@ export class SharingManager extends React.Component<{}> { /** * Shares the document with a user. */ - setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc: Doc | undefined) => { + setInternalSharing = undoable((recipient: ValidatedUser, permission: string, targetDoc: Doc | undefined) => { const { user, sharingDoc } = recipient; const target = targetDoc || this.targetDoc!; const acl = `acl-${normalizeEmail(user.email)}`; - const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`; const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc); docs.map(doc => (this.layoutDocAcls ? doc : Doc.GetProto(doc))).forEach(doc => { - doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined); - - distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined); + distributeAcls(acl, permission as SharingPermissions, doc); if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc); }); - }; + }, 'set Doc permissions'); /** * Sets the permission on the target for the group. * @param group * @param permission */ - setInternalGroupSharing = (group: Doc | { title: string }, permission: string, targetDoc?: Doc) => { + setInternalGroupSharing = undoable((group: Doc | { title: string }, permission: string, targetDoc?: Doc) => { const target = targetDoc || this.targetDoc!; - const key = normalizeEmail(StrCast(group.title)); - let acl = `acl-${key}`; + const acl = `acl-${normalizeEmail(StrCast(group.title))}`; const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc); docs.map(doc => (this.layoutDocAcls ? doc : Doc.GetProto(doc))).forEach(doc => { - doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc, undefined, undefined); - - distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined); + distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.overrideNested); if (group instanceof Doc) { Doc.AddDocToList(group, 'docsShared', doc); @@ -191,7 +190,7 @@ export class SharingManager extends React.Component<{}> { }); } }); - }; + }, 'set group permissions'); /** * Shares the documents shared with a group with a new user who has been added to that group. @@ -217,23 +216,23 @@ export class SharingManager extends React.Component<{}> { /** * Called from the properties sidebar to change permissions of a user. */ - shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => { + shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => { if (layout) this.layoutDocAcls = true; if (shareWith !== 'Public' && shareWith !== 'Override') { const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? Doc.CurrentUserEmail : shareWith)); docs.forEach(doc => { if (user) this.setInternalSharing(user, permission, doc); - else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc); + else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc, undefined, true); }); } else { docs.forEach(doc => { if (GetEffectiveAcl(doc) === AclAdmin) { - distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined); + distributeAcls(`acl-${shareWith}`, permission, doc, undefined, true); } }); } this.layoutDocAcls = false; - }; + }, 'sidebar set permissions'); /** * Removes the documents shared with a user through a group when the user is removed from the group. @@ -261,7 +260,7 @@ export class SharingManager extends React.Component<{}> { if (group.docsShared) { DocListCast(group.docsShared).forEach(doc => { const acl = `acl-${StrCast(group.title)}`; - distributeAcls(acl, SharingPermissions.None, doc, undefined, undefined); + distributeAcls(acl, SharingPermissions.None, doc); const members: string[] = JSON.parse(StrCast(group.members)); const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); @@ -342,39 +341,43 @@ export class SharingManager extends React.Component<{}> { /** * Handles changes in the permission chosen to share with someone with */ - @action - handlePermissionsChange = (event: React.ChangeEvent) => { - this.permissions = event.currentTarget.value as SharingPermissions; - }; + handlePermissionsChange = undoable( + action((event: React.ChangeEvent) => { + this.permissions = event.currentTarget.value as SharingPermissions; + }), + 'permission change' + ); /** * Calls the relevant method for sharing, displays the popup, and resets the relevant variables. */ - @action - share = () => { - if (this.selectedUsers) { - this.selectedUsers.forEach(user => { - if (user.value.includes(indType)) { - this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions, undefined); - } else { - this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions); - } - }); - - const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect(); - TaskCompletionBox.popupX = left - 1.5 * width; - TaskCompletionBox.popupY = top - 1.5 * height; - TaskCompletionBox.textDisplayed = 'Document shared!'; - TaskCompletionBox.taskCompleted = true; - setTimeout( - action(() => (TaskCompletionBox.taskCompleted = false)), - 2000 - ); + share = undoable( + action(() => { + if (this.selectedUsers) { + this.selectedUsers.forEach(user => { + if (user.value.includes(indType)) { + this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions, undefined); + } else { + this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions); + } + }); + + const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect(); + TaskCompletionBox.popupX = left - 1.5 * width; + TaskCompletionBox.popupY = top - 1.5 * height; + TaskCompletionBox.textDisplayed = 'Document shared!'; + TaskCompletionBox.taskCompleted = true; + setTimeout( + action(() => (TaskCompletionBox.taskCompleted = false)), + 2000 + ); - this.layoutDocAcls = false; - this.selectedUsers = null; - } - }; + this.layoutDocAcls = false; + this.selectedUsers = null; + } + }), + 'share Doc' + ); /** * Sorting algorithm to sort users. @@ -542,12 +545,17 @@ export class SharingManager extends React.Component<{}> { Share {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}

- -
- +
+
{admin ? (
@@ -585,6 +593,7 @@ export class SharingManager extends React.Component<{}> {
{Doc.noviceMode ? null : (
+ (this.overrideNested = !this.overrideNested))} checked={this.overrideNested} /> (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} />
)} diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 44af51341..422d2d6d7 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -190,36 +190,24 @@ export function ViewBoxAnnotatableComponent

() } const added = docs; if (added.length) { - const aclKeys = Object.keys(Doc.GetProto(this.props.Document)[DocAcl] ?? {}); - - GetEffectiveAcl(this.rootDoc) === AclAdmin && - aclKeys.forEach(key => + Object.keys(Doc.GetProto(this.rootDoc)[DocAcl]) // apply all collection acls (except pseudo-acl 'Me') to each added doc + .filter(key => key !== 'acl-Me') + .forEach(key => added.forEach(d => { - if (key != 'acl-Me') { - const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]); - const permissionSymbol = ReverseHierarchyMap.get(permissionString)?.acl; - const permission = permissionSymbol && HierarchyMapping.get(permissionSymbol)?.name; - distributeAcls(key, permission ?? SharingPermissions.Augment, Doc.GetProto(d)); - } + const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]); + const permissionSymbol = ReverseHierarchyMap.get(permissionString)?.acl; + const permission = permissionSymbol && HierarchyMapping.get(permissionSymbol)?.name; + distributeAcls(key, permission ?? SharingPermissions.Augment, d); }) ); - if (effectiveAcl === AclAugment) { + if ([AclAugment, AclEdit, AclAdmin].includes(effectiveAcl)) { added.map(doc => { + doc._dragOnlyWithinContainer = undefined; if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc; - Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc); - Doc.SetContainer(doc, targetDataDoc); + Doc.SetContainer(doc, this.rootDoc); inheritParentAcls(targetDataDoc, doc); }); - } else { - added - .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) - .map(doc => { - doc._dragOnlyWithinContainer = undefined; - if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc; - Doc.SetContainer(doc, this.rootDoc); - inheritParentAcls(targetDataDoc, doc); - }); const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List; if (annoDocs instanceof List) annoDocs.push(...added.filter(add => !annoDocs.includes(add))); diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index b513fe245..82d2bff56 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -50,18 +50,20 @@ export class PreviewCursor extends React.Component<{}> { PreviewCursor._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, PreviewCursor._addDocument).then(batch.end); } else if (re.test(plain)) { const url = plain; - undoBatch(() => - PreviewCursor._addDocument( - Docs.Create.WebDocument(url, { - title: url, - _width: 500, - _height: 300, - data_useCors: true, - x: newPoint[0], - y: newPoint[1], - }) - ) - )(); + if (url.startsWith(window.location.href)) { + undoBatch(() => + PreviewCursor._addDocument( + Docs.Create.WebDocument(url, { + title: url, + _width: 500, + _height: 300, + data_useCors: true, + x: newPoint[0], + y: newPoint[1], + }) + ) + )(); + } else alert('cannot paste dash into itself'); } else if (plain.startsWith('__DashDocId(') || plain.startsWith('__DashCloneId(')) { const clone = plain.startsWith('__DashCloneId('); const docids = plain.split(':'); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 0daa3dd92..30bc8cbec 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -500,15 +500,15 @@ export class CollectionDockingView extends CollectionSubView() { } }; tabCreated = (tab: any) => { - const aclKeys = Object.keys(Doc.GetProto(this.props.Document)[DocAcl] ?? {}); - aclKeys.forEach(key => { - if (key != 'acl-Me') { + const aclKeys = Object.keys(Doc.GetProto(this.rootDoc)[DocAcl] ?? {}); + aclKeys + .filter(key => key !== 'acl-Me') + .forEach(key => { const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]); const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl; const permission = HierarchyMapping.get(permissionSymbol)!.name; - distributeAcls(key, permission, Doc.GetProto(tab)); - } - }); + distributeAcls(key, permission, tab); + }); this.tabMap.add(tab); tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content) }; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 28fbdc192..8be295810 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -751,7 +751,9 @@ export namespace Doc { } }; const docAtKey = doc[key]; - if (docAtKey instanceof Doc) { + if (key === 'author') { + assignKey(Doc.CurrentUserEmail); + } else if (docAtKey instanceof Doc) { if (pruneDocs.includes(docAtKey)) { // prune doc and do nothing } else if (!Doc.IsSystem(docAtKey) && (key.startsWith('layout') || ['embedContainer', 'annotationOn', 'proto'].includes(key) || ((key === 'link_anchor_1' || key === 'link_anchor_2') && doc.author === Doc.CurrentUserEmail))) { diff --git a/src/fields/util.ts b/src/fields/util.ts index 0e9940ced..815f3b186 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -65,7 +65,9 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number const sameAuthor = fromServer || receiver.author === Doc.CurrentUserEmail; const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Playground || writeMode === DocServer.WriteMode.LivePlayground || (effectiveAcl === AclAugment && value instanceof RichTextField); - const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclAugment && Doc.CurrentUserEmail !== 'guest' && value instanceof RichTextField)) && !DocServer.Control.isReadOnly(); + const writeToServer = + !DocServer.Control.isReadOnly() && // + (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclAugment && value instanceof RichTextField)); if (writeToDoc) { if (value === undefined) { @@ -230,52 +232,40 @@ function getEffectiveAcl(target: any, user?: string): symbol { * @param inheritingFromCollection whether the target is being assigned rights after being dragged into a collection (and so is inheriting the acls from the collection) * inheritingFromCollection is not currently being used but could be used if acl assignment defaults change */ -export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean, visited?: Doc[]) { +export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean) { + const selfKey = `acl-${Doc.CurrentUserEmailNormalized}`; if (!visited) visited = [] as Doc[]; - if (!target || visited.includes(target)) return; - if ((target._type_collection === CollectionViewType.Docking && visited.length > 1) || Doc.GetProto(visited[0]) !== Doc.GetProto(target)) { - if (target.author !== Doc.CurrentUserEmail || key !== `acl-${Doc.CurrentUserEmailNormalized}`) { - target[key] = acl; - } - if (target !== Doc.GetProto(target)) { - //apparently we can't call updateCachedAcls twice (once for the main dashboard, and again for the nested dashboard...???) - updateCachedAcls(target); - } - //return; - } + if (!target || visited.includes(target) || key === selfKey) return; visited.push(target); - let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym]) - // if it is inheriting from a collection, it only inherits if A) the key doesn't already exist or B) the right being inherited is more restrictive - if (GetEffectiveAcl(target) === AclAdmin && (!inheritingFromCollection || !target[key] || ReverseHierarchyMap.get(StrCast(target[key]))!.level > ReverseHierarchyMap.get(acl)!.level)) { - if (target.author !== Doc.CurrentUserEmail || key !== `acl-${Doc.CurrentUserEmailNormalized}`) { - target[key] = acl; - layoutDocChanged = true; - } - } - let dataDocChanged = false; const dataDoc = target[DocData]; - if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || ReverseHierarchyMap.get(StrCast(dataDoc[key]))! > ReverseHierarchyMap.get(acl)!)) { - if (GetEffectiveAcl(dataDoc) === AclAdmin && (target.author !== Doc.CurrentUserEmail || key !== `acl-${Doc.CurrentUserEmailNormalized}`)) { - dataDoc[key] = acl; - dataDocChanged = true; - } + if (dataDoc && (allowUpgrade || !dataDoc[key] || ReverseHierarchyMap.get(StrCast(dataDoc[key]))! > ReverseHierarchyMap.get(acl)!)) { + // propagate ACLs to links, children, and annotations - // maps over the links of the document - LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, inheritingFromCollection, visited)); + LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade)); - // maps over the children of the document DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => { - distributeAcls(key, acl, d, inheritingFromCollection, visited); - d !== d[DocData] && distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited); + distributeAcls(key, acl, d, visited, allowUpgrade); + d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade); }); - // maps over the annotations of the document DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => { - distributeAcls(key, acl, d, inheritingFromCollection, visited); - d !== d[DocData] && distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited); + distributeAcls(key, acl, d, visited, allowUpgrade); + d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade); }); + + if (GetEffectiveAcl(dataDoc) === AclAdmin) { + dataDoc[key] = acl; + dataDocChanged = true; + } + } + + let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym]) + // if it is inheriting from a collection, it only inherits if A) allowUpgrade is set B) the key doesn't already exist or c) the right being inherited is more restrictive + if (GetEffectiveAcl(target) === AclAdmin && (allowUpgrade || !target[key] || ReverseHierarchyMap.get(StrCast(target[key]))!.level > ReverseHierarchyMap.get(acl)!.level)) { + target[key] = acl; + layoutDocChanged = true; } layoutDocChanged && updateCachedAcls(target); // updates target[AclSym] when changes to acls have been made -- cgit v1.2.3-70-g09d2 From 0007d043f7f27ce7d1103198ecb0e95e81362619 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 8 Jul 2023 09:19:16 -0400 Subject: fixed adding tabs to not try to distribute acls (this is done through adding a Doc to the docking view's Doc list). --- src/client/views/MainView.tsx | 2 +- src/client/views/collections/CollectionDockingView.tsx | 9 --------- src/fields/util.ts | 4 +++- 3 files changed, 4 insertions(+), 11 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index b708e2587..59636ccbe 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -153,8 +153,8 @@ export class MainView extends React.Component { } this._sidebarContent.proto = undefined; if (!MainView.Live) { - DocServer.setPlaygroundFields(['dockingConfig']); DocServer.setLivePlaygroundFields([ + 'dockingConfig', 'dataTransition', 'viewTransition', 'treeViewOpen', diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 30bc8cbec..77b719d53 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -500,15 +500,6 @@ export class CollectionDockingView extends CollectionSubView() { } }; tabCreated = (tab: any) => { - const aclKeys = Object.keys(Doc.GetProto(this.rootDoc)[DocAcl] ?? {}); - aclKeys - .filter(key => key !== 'acl-Me') - .forEach(key => { - const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]); - const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl; - const permission = HierarchyMapping.get(permissionSymbol)!.name; - distributeAcls(key, permission, tab); - }); this.tabMap.add(tab); tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content) }; diff --git a/src/fields/util.ts b/src/fields/util.ts index 815f3b186..36f619120 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -240,7 +240,9 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc let dataDocChanged = false; const dataDoc = target[DocData]; - if (dataDoc && (allowUpgrade || !dataDoc[key] || ReverseHierarchyMap.get(StrCast(dataDoc[key]))! > ReverseHierarchyMap.get(acl)!)) { + const curVal = ReverseHierarchyMap.get(StrCast(dataDoc[key]))?.level ?? 0; + const aclVal = ReverseHierarchyMap.get(acl)?.level ?? 0; + if (dataDoc && (allowUpgrade || !dataDoc[key] || curVal > aclVal)) { // propagate ACLs to links, children, and annotations LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade)); -- cgit v1.2.3-70-g09d2 From 2f4c55bf026a249910e90451a173234934ebca01 Mon Sep 17 00:00:00 2001 From: geireann Date: Mon, 10 Jul 2023 12:00:31 -0400 Subject: fixed sharing to a user to allow upgrading credentials. --- src/client/util/SharingManager.tsx | 6 +++--- src/client/views/DashboardView.scss | 5 ++++- src/client/views/DocComponent.tsx | 2 +- src/client/views/collections/CollectionDockingView.tsx | 8 ++++---- src/fields/util.ts | 12 ++++++------ 5 files changed, 18 insertions(+), 15 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 828271270..37c813279 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -160,7 +160,7 @@ export class SharingManager extends React.Component<{}> { const acl = `acl-${normalizeEmail(user.email)}`; const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc); docs.map(doc => (this.layoutDocAcls ? doc : Doc.GetProto(doc))).forEach(doc => { - distributeAcls(acl, permission as SharingPermissions, doc); + distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.overrideNested ? true : undefined); if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc); }); @@ -177,7 +177,7 @@ export class SharingManager extends React.Component<{}> { const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc); docs.map(doc => (this.layoutDocAcls ? doc : Doc.GetProto(doc))).forEach(doc => { - distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.overrideNested); + distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.overrideNested ? true : undefined); if (group instanceof Doc) { Doc.AddDocToList(group, 'docsShared', doc); @@ -227,7 +227,7 @@ export class SharingManager extends React.Component<{}> { } else { docs.forEach(doc => { if (GetEffectiveAcl(doc) === AclAdmin) { - distributeAcls(`acl-${shareWith}`, permission, doc, undefined, true); + distributeAcls(`acl-${shareWith}`, permission, doc, undefined); } }); } diff --git a/src/client/views/DashboardView.scss b/src/client/views/DashboardView.scss index 583edac08..4551fb4f4 100644 --- a/src/client/views/DashboardView.scss +++ b/src/client/views/DashboardView.scss @@ -7,6 +7,8 @@ width: 100%; position: absolute; height: 100%; + width:100%; + padding-right: 0px; overflow: auto; .left-menu { @@ -21,7 +23,8 @@ display: flex; flex-direction: row; flex-wrap: wrap; - overflow-y: scroll; + overflow-y: auto; + width: 100%; } } diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 422d2d6d7..10985c3f1 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -197,7 +197,7 @@ export function ViewBoxAnnotatableComponent

() const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]); const permissionSymbol = ReverseHierarchyMap.get(permissionString)?.acl; const permission = permissionSymbol && HierarchyMapping.get(permissionSymbol)?.name; - distributeAcls(key, permission ?? SharingPermissions.Augment, d); + distributeAcls(key, permission ?? SharingPermissions.Augment, d, undefined, false); }) ); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 77b719d53..13d3b2cdc 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -2,13 +2,13 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from 'mo import { observer } from 'mobx-react'; import * as ReactDOM from 'react-dom/client'; import * as GoldenLayout from '../../../client/goldenLayout'; -import { Doc, DocListCast, HierarchyMapping, Opt, ReverseHierarchyMap } from '../../../fields/Doc'; +import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; -import { distributeAcls, GetEffectiveAcl, GetPropAcl, inheritParentAcls } from '../../../fields/util'; +import { GetEffectiveAcl, inheritParentAcls } from '../../../fields/util'; import { emptyFunction, incrementTitleCopy } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { Docs } from '../../documents/Documents'; @@ -28,9 +28,9 @@ import './CollectionDockingView.scss'; import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { TabDocView } from './TabDocView'; -import React = require('react'); import { DocumentManager } from '../../util/DocumentManager'; -import { AclAdmin, AclEdit, DocAcl } from '../../../fields/DocSymbols'; +import { AclAdmin, AclEdit } from '../../../fields/DocSymbols'; +import React = require('react'); const _global = (window /* browser */ || global) /* node */ as any; @observer diff --git a/src/fields/util.ts b/src/fields/util.ts index 4dcbf1fbe..034229319 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -243,19 +243,19 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc const dataDoc = target[DocData]; const curVal = ReverseHierarchyMap.get(StrCast(dataDoc[key]))?.level ?? 0; const aclVal = ReverseHierarchyMap.get(acl)?.level ?? 0; - if (dataDoc && (allowUpgrade || !dataDoc[key] || curVal > aclVal)) { + if (dataDoc && (allowUpgrade !== false|| !dataDoc[key] || curVal > aclVal)) { // propagate ACLs to links, children, and annotations - LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade)); + LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade? true: false)); DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => { - distributeAcls(key, acl, d, visited, allowUpgrade); - d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade); + distributeAcls(key, acl, d, visited, allowUpgrade ? true: false); + d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true: false); }); DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => { - distributeAcls(key, acl, d, visited, allowUpgrade); - d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade); + distributeAcls(key, acl, d, visited, allowUpgrade? true: false); + d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade? true: false); }); if (GetEffectiveAcl(dataDoc) === AclAdmin) { -- cgit v1.2.3-70-g09d2 From 06cfe3cbba127d865e788b00561f8a591af1bd81 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 11 Jul 2023 15:32:08 -0400 Subject: more fixes to simplify sharing --- src/client/documents/Documents.ts | 31 ++++++------ src/client/util/CurrentUserUtils.ts | 6 +-- src/client/util/DocumentManager.ts | 2 +- src/client/util/SharingManager.tsx | 9 ++-- src/client/views/DashboardView.tsx | 15 +++--- src/client/views/DocComponent.tsx | 15 +----- src/client/views/PropertiesView.tsx | 55 ++++++++++------------ .../views/collections/CollectionDockingView.tsx | 4 +- .../collections/collectionFreeForm/MarqueeView.tsx | 8 +--- src/client/views/nodes/button/FontIconBox.tsx | 2 +- src/client/views/search/SearchBox.tsx | 2 +- src/fields/Doc.ts | 16 ++----- src/fields/DocSymbols.ts | 1 - src/fields/util.ts | 39 +++++++-------- 14 files changed, 88 insertions(+), 117 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f1ba852f9..4ebbfbd1c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -169,8 +169,8 @@ export class DocumentOptions { _nativeDimModifiable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers'); _nativeHeightUnfrozen?: BOOLt = new BoolInfo('native height can be changed independent of width by dragging decoration resizers'); - 'acl-Public'?: string; // public permissions - '_acl-Public'?: string; // public permissions + 'acl-Guest'?: string; // public permissions + '_acl-Guest'?: string; // public permissions type?: DTYPEt = new DTypeInfo('type of document', true); type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection _type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection @@ -825,8 +825,7 @@ export namespace Docs { const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, '^_'); // dataProps['acl-Override'] = SharingPermissions.Unset; - dataProps['acl-Public'] = options['acl-Public'] ? options['acl-Public'] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; - + dataProps['acl-Guest'] = options['acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View); dataProps.isSystem = viewProps.isSystem; dataProps.isDataDoc = true; dataProps.author = Doc.CurrentUserEmail; @@ -848,16 +847,17 @@ export namespace Docs { dataDoc.proto = proto; } - const viewFirstProps: { [id: string]: any } = {}; - // viewFirstProps['acl-Public'] = options['_acl-Public'] ? options['_acl-Public'] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; - // viewFirstProps['acl-Override'] = SharingPermissions.Unset; - viewFirstProps.author = Doc.CurrentUserEmail; + const viewFirstProps: { [id: string]: any } = { author: Doc.CurrentUserEmail }; + viewFirstProps['acl-Guest'] = options['_acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View); let viewDoc: Doc; // determines whether viewDoc should be created using placeholder Doc or default if (placeholderDoc) { placeholderDoc._height = options._height !== undefined ? Number(options._height) : undefined; placeholderDoc._width = options._width !== undefined ? Number(options._width) : undefined; viewDoc = Doc.assign(placeholderDoc, viewFirstProps, true, true); + Array.from(Object.keys(placeholderDoc)) + .filter(key => key.startsWith('acl')) + .forEach(key => (dataDoc[key] = viewDoc[key] = placeholderDoc[key])); } else { viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); } @@ -997,7 +997,7 @@ export namespace Docs { I.rotation = 0; I.defaultDoubleClick = 'click'; I.author_date = new DateField(); - I['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + I['acl-Guest'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View; //I['acl-Override'] = SharingPermissions.Unset; I[Initializing] = false; @@ -1169,10 +1169,13 @@ export namespace Docs { const doc = DockDocument( configs.map(c => c.doc), JSON.stringify(layoutConfig), - options, + Doc.CurrentUserEmail === 'guest' ? options : { 'acl-Guest': SharingPermissions.View, ...options }, id ); - configs.map(c => Doc.SetContainer(c.doc, doc)); + configs.map(c => { + Doc.SetContainer(c.doc, doc); + inheritParentAcls(doc, c.doc, false); + }); return doc; } @@ -1332,8 +1335,8 @@ export namespace DocUtils { source, target, { - 'acl-Public': SharingPermissions.Augment, - '_acl-Public': SharingPermissions.Augment, + 'acl-Guest': SharingPermissions.Augment, + '_acl-Guest': SharingPermissions.Augment, title: ComputedField.MakeFunction('generateLinkTitle(self)') as any, link_anchor_1_useSmallAnchor: source.useSmallAnchor ? true : undefined, link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined, @@ -1854,8 +1857,6 @@ export namespace DocUtils { Doc.SetInPlace(ndoc, 'title', ndoc.title + ' ' + NumCast(dragFactory['dragFactory_count']).toString(), true); } - if (ndoc && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, ndoc); - return ndoc; } export function delegateDragFactory(dragFactory: Doc) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 11a8dcaf6..2cee37380 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -765,7 +765,7 @@ export class CurrentUserUtils { linkDocs.title = "LINK DATABASE: " + Doc.CurrentUserEmail; linkDocs.author = Doc.CurrentUserEmail; linkDocs.data = new List([]); - linkDocs["acl-Public"] = SharingPermissions.Augment; + linkDocs["acl-Guest"] = SharingPermissions.Augment; doc.myLinkDatabase = new PrefetchProxy(linkDocs); } } @@ -788,7 +788,7 @@ export class CurrentUserUtils { childContextMenuScripts: new List([addToDashboards!,]), childContextMenuLabels: new List(["Add to Dashboards",]), childContextMenuIcons: new List(["user-plus",]), - "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, + "acl-Guest": SharingPermissions.Augment, "_acl-Guest": SharingPermissions.Augment, childDragAction: "embed", isSystem: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true, // NOTE: treeViewHideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar _layout_showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, layout_boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true, @@ -823,7 +823,7 @@ export class CurrentUserUtils { async () => { const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data); const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || []; - SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]); + SetCachedGroups(["Guest", ...mygroups?.map(g => StrCast(g.title))]); }, { fireImmediately: true }); doc.isSystem ?? (doc.isSystem = true); doc.title ?? (doc.title = Doc.CurrentUserEmail); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 3f0848d00..b921b3116 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -194,7 +194,7 @@ export class DocumentManager { var containerDocContext = srcContext ? [srcContext, doc] : [doc]; while ( containerDocContext.length && - containerDocContext[0]?.embedContainer && + DocCast(containerDocContext[0]?.embedContainer) && DocCast(containerDocContext[0].embedContainer)?._type_collection !== CollectionViewType.Docking && (includeExistingViews || !DocumentManager.Instance.getDocumentView(containerDocContext[0])) ) { diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 37c813279..407396cb4 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -159,7 +159,7 @@ export class SharingManager extends React.Component<{}> { const target = targetDoc || this.targetDoc!; const acl = `acl-${normalizeEmail(user.email)}`; const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc); - docs.map(doc => (this.layoutDocAcls ? doc : Doc.GetProto(doc))).forEach(doc => { + docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => { distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.overrideNested ? true : undefined); if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc); @@ -176,7 +176,7 @@ export class SharingManager extends React.Component<{}> { const acl = `acl-${normalizeEmail(StrCast(group.title))}`; const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc); - docs.map(doc => (this.layoutDocAcls ? doc : Doc.GetProto(doc))).forEach(doc => { + docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => { distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.overrideNested ? true : undefined); if (group instanceof Doc) { @@ -218,7 +218,7 @@ export class SharingManager extends React.Component<{}> { */ shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => { if (layout) this.layoutDocAcls = true; - if (shareWith !== 'Public' && shareWith !== 'Override') { + if (shareWith !== 'Guest') { const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? Doc.CurrentUserEmail : shareWith)); docs.forEach(doc => { if (user) this.setInternalSharing(user, permission, doc); @@ -291,7 +291,6 @@ export class SharingManager extends React.Component<{}> { private sharingOptions(uniform: boolean, override?: boolean) { const dropdownValues: string[] = Object.values(SharingPermissions); if (!uniform) dropdownValues.unshift('-multiple-'); - if (!override) dropdownValues.splice(dropdownValues.indexOf(SharingPermissions.Unset), 1); return dropdownValues.map(permission => (

this.onContextMenu(dashboard, e)} onClick={e => this.clickDashboard(e, dashboard)}> (dashboardTabs); dashboardDoc['pane-count'] = 1; Doc.AddDocToList(dashboards, 'data', dashboardDoc); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 10985c3f1..a41fc8ded 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -190,23 +190,12 @@ export function ViewBoxAnnotatableComponent

() } const added = docs; if (added.length) { - Object.keys(Doc.GetProto(this.rootDoc)[DocAcl]) // apply all collection acls (except pseudo-acl 'Me') to each added doc - .filter(key => key !== 'acl-Me') - .forEach(key => - added.forEach(d => { - const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]); - const permissionSymbol = ReverseHierarchyMap.get(permissionString)?.acl; - const permission = permissionSymbol && HierarchyMapping.get(permissionSymbol)?.name; - distributeAcls(key, permission ?? SharingPermissions.Augment, d, undefined, false); - }) - ); - if ([AclAugment, AclEdit, AclAdmin].includes(effectiveAcl)) { - added.map(doc => { + added.forEach(doc => { doc._dragOnlyWithinContainer = undefined; if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc; Doc.SetContainer(doc, this.rootDoc); - inheritParentAcls(targetDataDoc, doc); + inheritParentAcls(targetDataDoc, doc, true); }); const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 633401d58..2e10bf346 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -308,21 +308,16 @@ export class PropertiesView extends React.Component { /** * @returns the options for the permissions dropdown. */ - getPermissionsSelect(user: string, permission: string) { - const dropdownValues: string[] = Object.values(SharingPermissions); + getPermissionsSelect(user: string, permission: string, showGuestOptions: boolean) { + const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions); if (permission === '-multiple-') dropdownValues.unshift(permission); - if (user !== 'Override') { - dropdownValues.splice(dropdownValues.indexOf(SharingPermissions.Unset), 1); - } return ( ); } @@ -379,7 +374,7 @@ export class PropertiesView extends React.Component {

{/* {name !== "Me" ? this.notifyIcon : null} */}
- {this.colorACLDropDown(name, admin, permission, showExpansionIcon)} + {this.colorACLDropDown(name, admin, permission, false)} {(permission === 'Owner' && name == 'Me') || showExpansionIcon ? this.expansionIcon : null}
@@ -389,13 +384,13 @@ export class PropertiesView extends React.Component { /** * @returns a colored dropdown bar reflective of the permission */ - colorACLDropDown(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) { + colorACLDropDown(name: string, admin: boolean, permission: string, showGuestOptions: boolean) { var shareImage = ReverseHierarchyMap.get(permission)?.image; return (
-
{admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission) : concat(shareImage, ' ', permission)}
+
{admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission, showGuestOptions) : concat(shareImage, ' ', permission)}
@@ -430,15 +425,16 @@ export class PropertiesView extends React.Component { const individualTableEntries = []; const usersAdded: string[] = []; // all shared users being added - organized by denormalized email + const seldoc = this.layoutDocAcls || !this.selectedDoc ? this.selectedDoc : Doc.GetProto(this.selectedDoc); // adds each user to usersAdded SharingManager.Instance.users.forEach(eachUser => { var userOnDoc = true; - if (this.selectedDoc) { - if (this.selectedDoc['acl-' + normalizeEmail(eachUser.user.email)] == '' || this.selectedDoc['acl-' + normalizeEmail(eachUser.user.email)] == undefined) { + if (seldoc) { + if (Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === '' || Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === undefined) { userOnDoc = false; } } - if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email != 'Public' && eachUser.user.email != target.author) { + if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email !== 'guest' && eachUser.user.email != target.author) { usersAdded.push(eachUser.user.email); } }); @@ -447,15 +443,16 @@ export class PropertiesView extends React.Component { usersAdded.sort(this.sortUsers); usersAdded.map(userEmail => { const userKey = `acl-${normalizeEmail(userEmail)}`; - var permission = StrCast(target[userKey]); + var aclField = Doc.GetT(this.layoutDocAcls ? target : Doc.GetProto(target), userKey, 'string', true); + var permission = StrCast(aclField); individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user }); // adds current user var userEmail = Doc.CurrentUserEmail; + if (userEmail == 'guest') userEmail = 'Guest'; const userKey = `acl-${normalizeEmail(userEmail)}`; - if (userEmail == 'guest') userEmail = 'Public'; - if (!usersAdded.includes(userEmail) && userEmail != 'Public' && userEmail != target.author) { + if (!usersAdded.includes(userEmail) && userEmail !== 'Guest' && userEmail != target.author) { var permission; if (this.layoutDocAcls) { if (target[DocAcl][userKey]) permission = HierarchyMapping.get(target[DocAcl][userKey])?.name; @@ -473,7 +470,7 @@ export class PropertiesView extends React.Component { const groupList = GroupManager.Instance?.allGroups || []; groupList.sort(this.sortGroups); groupList.map(group => { - if (group.title != 'Public' && this.selectedDoc) { + if (group.title != 'Guest' && this.selectedDoc) { const groupKey = 'acl-' + normalizeEmail(StrCast(group.title)); if (this.selectedDoc[groupKey] != '' && this.selectedDoc[groupKey] != undefined) { var permission; @@ -489,17 +486,11 @@ export class PropertiesView extends React.Component { }); // public permission - const publicPermission = StrCast((this.layoutDocAcls ? target : Doc.GetProto(target))['acl-Public']); + const publicPermission = StrCast((this.layoutDocAcls ? target : Doc.GetProto(target))['acl-Guest']); return (

- Public / Guest Users -
{this.colorACLDropDown('Public', showAdmin, publicPermission!, false)}
-
- {' '} -

Individual Users with Access to this Document{' '} -
{
{individualTableEntries}
}
{groupTableEntries.length > 0 ? (
@@ -510,6 +501,12 @@ export class PropertiesView extends React.Component {
{
{groupTableEntries}
}
) : null} + Guest +
{this.colorACLDropDown('Guest', true, publicPermission!, true)}
+
+ {' '} +

Individual Users with Access to this Document{' '} +
); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 13d3b2cdc..8d1b46ebb 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -517,7 +517,7 @@ export class CollectionDockingView extends CollectionSubView() { _layout_fitWidth: true, title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); - inheritParentAcls(this.rootDoc, docToAdd); + inheritParentAcls(this.rootDoc, docToAdd, false); CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }); @@ -560,7 +560,7 @@ export class CollectionDockingView extends CollectionSubView() { _freeform_backgroundGrid: true, title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); - inheritParentAcls(this.dataDoc, Doc.GetProto(docToAdd)); + inheritParentAcls(this.dataDoc, docToAdd, false); CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }) diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 764b1e08a..e2718b52d 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -9,7 +9,7 @@ import { RichTextField } from '../../../../fields/RichTextField'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; import { Cast, DocCast, FieldValue, NumCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; -import { GetEffectiveAcl, SharingPermissions } from '../../../../fields/util'; +import { distributeAcls, GetEffectiveAcl, SharingPermissions } from '../../../../fields/util'; import { intersectRect, returnFalse, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents'; @@ -390,11 +390,7 @@ export class MarqueeView extends React.Component { - Doc.SetContainer(d, newCollection); - d['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; - }); + selected.forEach(d => Doc.SetContainer(d, newCollection)); this.hideMarquee(); return newCollection; }); diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 5bba51ec8..a0b0caf98 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -597,7 +597,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b if (contentFrameNumber !== undefined) { CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { fieldKey: color }); } else { - dv.rootDoc['_' + fieldKey] = color; + dv.dataDoc[fieldKey] = color; } }); } else { diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index d13c09443..ab9eebd78 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -222,7 +222,7 @@ export class SearchBox extends ViewBoxBaseComponent() { 'width', 'layout_autoHeight', 'acl-Override', - 'acl-Public', + 'acl-Guest', 'embedContainer', 'zIndex', 'height', diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 698e09915..6121668e3 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -20,7 +20,6 @@ import { AclEdit, AclPrivate, AclReadonly, - AclUnset, Animation, CachedUpdates, DirectLinks, @@ -134,7 +133,6 @@ export const HierarchyMapping: Map = new Map(Array.from(HierarchyMapping.entries()).map(value => [value[1].name, { level: value[1].level, acl: value[0], image: value[1].image }])); @@ -380,7 +378,8 @@ export class Doc extends RefField { return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self); } @computed get __LAYOUT__(): Doc | undefined { - const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null); + const self = this[SelfProxy]; + const templateLayoutDoc = Cast(Doc.LayoutField(self), Doc, null); if (templateLayoutDoc) { let renderFieldKey: any; const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layout_fieldKey, 'layout')]; @@ -389,7 +388,7 @@ export class Doc extends RefField { } else { return Cast(layoutField, Doc, null); } - return Cast(this[SelfProxy][renderFieldKey + '-layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc; + return Cast(self[renderFieldKey + '-layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc; } return undefined; } @@ -480,11 +479,6 @@ export namespace Doc { export function SetContainer(doc: Doc, container: Doc) { doc.embedContainer = container; - if (Doc.GetProto(container).author === doc.author) { - Object.keys(Doc.GetProto(container)) - .filter(key => key.startsWith('acl') && !key.includes(Doc.CurrentUserEmailNormalized)) - .forEach(key => (doc[key] = Doc.GetProto(container)[key])); - } } export function RunCachedUpdate(doc: Doc, field: string) { const update = doc[CachedUpdates][field]; @@ -697,7 +691,7 @@ export namespace Doc { Doc.SetLayout(embedding, Doc.MakeEmbedding(layout)); } embedding.createdFrom = doc; - embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = DocListCast(Doc.GetProto(doc).proto_embeddings).length + 1; + embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = DocListCast(Doc.GetProto(doc).proto_embeddings).length - 1; embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`); embedding.author = Doc.CurrentUserEmail; @@ -1027,7 +1021,6 @@ export namespace Doc { Doc.AddDocToList(Doc.GetProto(copy)[DocData], 'proto_embeddings', copy); } copy.embedContainer = undefined; - Doc.defaultAclPrivate && (copy['acl-Public'] = 'Not Shared'); if (retitle) { copy.title = incrementTitleCopy(StrCast(copy.title)); } @@ -1087,7 +1080,6 @@ export namespace Doc { const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')'); target.layout_fieldKey = targetKey; applied && (Doc.GetProto(applied).type = templateDoc.type); - Doc.defaultAclPrivate && (applied['acl-Public'] = 'Not Shared'); return applied; } return undefined; diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts index eab26ed10..66d1ab094 100644 --- a/src/fields/DocSymbols.ts +++ b/src/fields/DocSymbols.ts @@ -13,7 +13,6 @@ export const DocFields = Symbol('DocFields'); export const DocCss = Symbol('DocCss'); export const DocAcl = Symbol('DocAcl'); export const DirectLinks = Symbol('DocDirectLinks'); -export const AclUnset = Symbol('DocAclUnset'); export const AclPrivate = Symbol('DocAclOwnerOnly'); export const AclReadonly = Symbol('DocAclReadOnly'); export const AclAugment = Symbol('DocAclAugment'); diff --git a/src/fields/util.ts b/src/fields/util.ts index 034229319..e89cb1fb1 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -131,17 +131,19 @@ export function denormalizeEmail(email: string) { /** * Copies parent's acl fields to the child */ -export function inheritParentAcls(parent: Doc, child: Doc) { +export function inheritParentAcls(parent: Doc, child: Doc, layoutOnly: boolean) { if (GetEffectiveAcl(parent) !== AclAdmin) return; - for (const key of Object.keys(parent)) { - // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private. - // const permission: string = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : parent[key]; - const symbol = ReverseHierarchyMap.get(StrCast(parent[key])); - if (symbol) { - const sharePermission = HierarchyMapping.get(symbol.acl!)!.name; - key.startsWith('acl') && distributeAcls(key, sharePermission, child); - } - } + Object.keys(parent) + .filter(key => key.startsWith('acl')) + .forEach(key => { + // if the default acl mode is private, then don't inherit the acl-guest permission, but set it to private. + // const permission: string = key === 'acl-guest' && Doc.defaultAclPrivate ? AclPrivate : parent[key]; + const parAcl = ReverseHierarchyMap.get(StrCast(parent[key]))?.acl; + if (parAcl) { + const sharePermission = HierarchyMapping.get(parAcl)?.name; + sharePermission && distributeAcls(key, sharePermission, child, undefined, false, layoutOnly); + } + }); } /** @@ -160,7 +162,6 @@ export function inheritParentAcls(parent: Doc, child: Doc) { * Unset: Remove a sharing permission (eg., used ) */ export enum SharingPermissions { - Unset = 'None', Admin = 'Admin', Edit = 'Edit', Augment = 'Augment', @@ -233,7 +234,7 @@ function getEffectiveAcl(target: any, user?: string): symbol { * @param allowUpgrade whether permissions can be made less restrictive * inheritingFromCollection is not currently being used but could be used if acl assignment defaults change */ -export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean) { +export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean, layoutOnly = false) { const selfKey = `acl-${Doc.CurrentUserEmailNormalized}`; if (!visited) visited = [] as Doc[]; if (!target || visited.includes(target) || key === selfKey) return; @@ -243,19 +244,19 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc const dataDoc = target[DocData]; const curVal = ReverseHierarchyMap.get(StrCast(dataDoc[key]))?.level ?? 0; const aclVal = ReverseHierarchyMap.get(acl)?.level ?? 0; - if (dataDoc && (allowUpgrade !== false|| !dataDoc[key] || curVal > aclVal)) { + if (!layoutOnly && dataDoc && (allowUpgrade !== false || !dataDoc[key] || curVal > aclVal)) { // propagate ACLs to links, children, and annotations - LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade? true: false)); + LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade ? true : false)); DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => { - distributeAcls(key, acl, d, visited, allowUpgrade ? true: false); - d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true: false); + distributeAcls(key, acl, d, visited, allowUpgrade ? true : false); + d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false); }); DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => { - distributeAcls(key, acl, d, visited, allowUpgrade? true: false); - d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade? true: false); + distributeAcls(key, acl, d, visited, allowUpgrade ? true : false); + d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false); }); if (GetEffectiveAcl(dataDoc) === AclAdmin) { @@ -266,7 +267,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym]) // if it is inheriting from a collection, it only inherits if A) allowUpgrade is set B) the key doesn't already exist or c) the right being inherited is more restrictive - if (GetEffectiveAcl(target) === AclAdmin && (allowUpgrade || !target[key] || ReverseHierarchyMap.get(StrCast(target[key]))!.level > ReverseHierarchyMap.get(acl)!.level)) { + if (GetEffectiveAcl(target) === AclAdmin && (allowUpgrade || !Doc.GetT(target, key, 'boolean', true) || ReverseHierarchyMap.get(StrCast(target[key]))!.level > aclVal)) { target[key] = acl; layoutDocChanged = true; } -- cgit v1.2.3-70-g09d2 From 044f451de061373aa8865082ee9c02f567d17264 Mon Sep 17 00:00:00 2001 From: geireann Date: Wed, 12 Jul 2023 14:55:33 -0400 Subject: fixing dashboard view of shared dashboards to open dashbarods the same as sharing panel - as embeddings. --- src/client/util/CurrentUserUtils.ts | 1 + src/client/views/DashboardView.tsx | 17 ++++++++++++++--- src/client/views/MainView.tsx | 1 - src/client/views/collections/CollectionDockingView.tsx | 4 ++++ src/client/views/collections/TreeView.tsx | 10 ++++------ 5 files changed, 23 insertions(+), 10 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 2cee37380..f7de6b41f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -849,6 +849,7 @@ export class CurrentUserUtils { doc.savedFilters ?? (doc.savedFilters = new List()); doc.filterDocCount = 0; doc.treeViewFreezeChildren = "remove|add"; + doc.activePage = doc.activeDashboard === undefined ? 'home': doc.activePage; this.setupLinkDocs(doc, linkDatabaseId); this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 9b16ecfa7..0c871b2b2 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -57,8 +57,11 @@ export class DashboardView extends React.Component { }; clickDashboard = (e: React.MouseEvent, dashboard: Doc) => { - Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard); - Doc.ActiveDashboard = dashboard; + if (this.selectedDashboardGroup === DashboardGroup.SharedDashboards) { + DashboardView.openSharedDashboard(dashboard); + } else { + Doc.ActiveDashboard = dashboard; + } Doc.ActivePage = 'dashboard'; }; @@ -170,7 +173,10 @@ export class DashboardView extends React.Component { .filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && !['acl-Me', 'acl-Guest'].includes(key)) .some(key => dashboard[DocAcl][key] !== AclPrivate); return ( -
this.onContextMenu(dashboard, e)} onClick={e => this.clickDashboard(e, dashboard)}> +
this.onContextMenu(dashboard, e)} + onClick={e => this.clickDashboard(e, dashboard)}> { + Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard); + DashboardView.openDashboard(Doc.BestEmbedding(dashboard)); + } + /// opens a dashboard as the ActiveDashboard (and adds the dashboard to the users list of dashboards if it's not already there). /// this also sets the readonly state of the dashboard based on the current mode of dash (from its url) public static openDashboard = (doc: Doc | undefined, fromHistory = false) => { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 59636ccbe..2f877d74d 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -154,7 +154,6 @@ export class MainView extends React.Component { this._sidebarContent.proto = undefined; if (!MainView.Live) { DocServer.setLivePlaygroundFields([ - 'dockingConfig', 'dataTransition', 'viewTransition', 'treeViewOpen', diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 8d1b46ebb..b2eaa964c 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -386,6 +386,10 @@ export class CollectionDockingView extends CollectionSubView() { .map(f => f as Doc); const changesMade = this.props.Document.dockingConfig !== json; if (changesMade) { + if (json.startsWith('{"settings"')) { + alert("Porblems looming") + debugger; + } if (![AclAdmin, AclEdit].includes(GetEffectiveAcl(this.dataDoc))) { this.layoutDoc.dockingConfig = json; this.layoutDoc.data = new List(docs); diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 7767c5b79..91d1ff11e 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -221,12 +221,10 @@ export class TreeView extends React.Component { this.treeViewOpen = !this.treeViewOpen; } else { // choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns, (2) has no context field ... otherwise make a new embedding - const bestEmbedding = - docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) - ? docView.props.Document - : DocListCast(this.props.document.proto_embeddings).find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail); - const nextBestEmbedding = DocListCast(this.props.document.proto_embeddings).find(doc => doc.author === Doc.CurrentUserEmail); - this.props.addDocTab(bestEmbedding ?? nextBestEmbedding ?? Doc.MakeEmbedding(this.props.document), OpenWhere.lightbox); + const bestEmbedding = docView.rootDoc.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) + ? docView.rootDoc + : Doc.BestEmbedding(docView.rootDoc); + this.props.addDocTab(bestEmbedding, OpenWhere.lightbox); } }; -- cgit v1.2.3-70-g09d2 From f96fb8dc3e430f93b7a7f3a57fa3fb3d83c813c9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 13 Jul 2023 20:51:20 -0400 Subject: removed shared dashboards from SharedDocs -- only available through the Dashbaord view now. Need to change hgihlight of Dashboard button to show badge w/ new dashboards the same as is done is shard docs. --- src/client/documents/Documents.ts | 1 + src/client/util/CurrentUserUtils.ts | 9 +++--- src/client/util/SharingManager.tsx | 24 +++++++++++--- src/client/views/DashboardView.tsx | 37 ++++++++++++---------- src/client/views/StyleProvider.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 4 --- src/client/views/nodes/button/FontIconBadge.scss | 5 +-- src/client/views/topbar/TopBar.tsx | 8 +++-- 8 files changed, 55 insertions(+), 35 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a39145151..a16742623 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -395,6 +395,7 @@ export class DocumentOptions { sidebar_color?: string; // background color of text sidebar sidebar_collectionType?: string; // collection type of text sidebar + data_dashboards?: List; // list of dashboards used in shareddocs; text?: string; textTransform?: string; letterSpacing?: string; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 0c53caaee..7832b1c87 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -783,11 +783,12 @@ export class CurrentUserUtils { const sharedDocOpts:DocumentOptions = { title: "My Shared Docs", userColor: "rgb(202, 202, 202)", + data_dashboards: new List(), isFolder:true, undoIgnoreFields:new List(['treeViewSortCriterion']), - childContextMenuFilters: new List([dashboardFilter!,]), - childContextMenuScripts: new List([addToDashboards!,]), - childContextMenuLabels: new List(["Add to Dashboards",]), - childContextMenuIcons: new List(["user-plus",]), + // childContextMenuFilters: new List([dashboardFilter!,]), + // childContextMenuScripts: new List([addToDashboards!,]), + // childContextMenuLabels: new List(["Add to Dashboards",]), + // childContextMenuIcons: new List(["user-plus",]), "acl-Guest": SharingPermissions.Augment, "_acl-Guest": SharingPermissions.Augment, childDragAction: "embed", isSystem: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true, // NOTE: treeViewHideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 807e812f2..587e1e585 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -49,6 +49,7 @@ const indType = '!indType/'; const groupType = '!groupType/'; const storage = 'data'; +const dashStorage = 'data_dashboards'; /** * A user who also has a sharing doc. @@ -162,8 +163,9 @@ export class SharingManager extends React.Component<{}> { const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc); docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => { distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.overrideNested ? true : undefined); - if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); - else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc); + if (permission !== SharingPermissions.None) { + Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc); + } else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc); }); }, 'set Doc permissions'); @@ -186,8 +188,8 @@ export class SharingManager extends React.Component<{}> { this.users .filter(({ user: { email } }) => JSON.parse(StrCast(group.members)).includes(email)) .forEach(({ user, sharingDoc }) => { - if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added - else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists + if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc); // add the doc to the sharingDoc if it hasn't already been added + else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists }); } }); @@ -206,7 +208,13 @@ export class SharingManager extends React.Component<{}> { else { DocListCastAsync(user.sharingDoc[storage]).then(userdocs => DocListCastAsync(group.docsShared).then(dl => { - const filtered = dl?.filter(doc => !userdocs?.includes(doc)); + const filtered = dl?.filter(doc => !doc.dockingConfig && !userdocs?.includes(doc)); + filtered && userdocs?.push(...filtered); + }) + ); + DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs => + DocListCastAsync(group.docsShared).then(dl => { + const filtered = dl?.filter(doc => doc.dockingConfig && !userdocs?.includes(doc)); filtered && userdocs?.push(...filtered); }) ); @@ -250,6 +258,12 @@ export class SharingManager extends React.Component<{}> { userdocs?.splice(0, userdocs.length, ...remaining); }) ); + DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs => + DocListCastAsync(group.docsShared).then(dl => { + const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || []; + userdocs?.splice(0, userdocs.length, ...remaining); + }) + ); } }; diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 0c871b2b2..b8c89d2ff 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -57,7 +57,7 @@ export class DashboardView extends React.Component { }; clickDashboard = (e: React.MouseEvent, dashboard: Doc) => { - if (this.selectedDashboardGroup === DashboardGroup.SharedDashboards) { + if (this.selectedDashboardGroup === DashboardGroup.SharedDashboards) { DashboardView.openSharedDashboard(dashboard); } else { Doc.ActiveDashboard = dashboard; @@ -70,17 +70,17 @@ export class DashboardView extends React.Component { if (whichGroup === DashboardGroup.MyDashboards) { return allDashboards.filter(dashboard => Doc.GetProto(dashboard).author === Doc.CurrentUserEmail); } - const sharedDashboards = DocListCast(Doc.MySharedDocs.data).filter(doc => doc.dockingConfig); + const sharedDashboards = DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig); return sharedDashboards; }; isUnviewedSharedDashboard = (dashboard: Doc): boolean => { - // const sharedDashboards = DocListCast(Doc.MySharedDocs.data).filter(doc => doc._type_collection === CollectionViewType.Docking); + // const sharedDashboards = DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc._type_collection === CollectionViewType.Docking); return !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard); }; getSharedDashboards = () => { - const sharedDashs = DocListCast(Doc.MySharedDocs.data).filter(doc => doc._type_collection === CollectionViewType.Docking); + const sharedDashs = DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc._type_collection === CollectionViewType.Docking); return sharedDashs.filter(dashboard => !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard)); }; @@ -163,7 +163,13 @@ export class DashboardView extends React.Component { {'My Dashboards (' + this.getDashboards(DashboardGroup.MyDashboards).length + ')'}
this.selectDashboardGroup(DashboardGroup.SharedDashboards)}> - {'Shared Dashboards (' + this.getDashboards(DashboardGroup.SharedDashboards).length + ')'} + Shared Dashboards{' '} + !DocListCast(Doc.MySharedDocs.viewed).includes(dash)) ? 'lightgreen' : 'undefined', + }}> + {'(' + this.getDashboards(DashboardGroup.SharedDashboards).length + ')'} +
@@ -173,9 +179,11 @@ export class DashboardView extends React.Component { .filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && !['acl-Me', 'acl-Guest'].includes(key)) .some(key => dashboard[DocAcl][key] !== AclPrivate); return ( -
this.onContextMenu(dashboard, e)} +
this.onContextMenu(dashboard, e)} onClick={e => this.clickDashboard(e, dashboard)}> { + public static openSharedDashboard = (dashboard: Doc) => { Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard); DashboardView.openDashboard(Doc.BestEmbedding(dashboard)); - } + }; /// opens a dashboard as the ActiveDashboard (and adds the dashboard to the users list of dashboards if it's not already there). /// this also sets the readonly state of the dashboard based on the current mode of dash (from its url) @@ -373,8 +381,7 @@ export class DashboardView extends React.Component { }; public static createNewDashboard = (id?: string, name?: string, background?: string) => { - const dashboards = Doc.MyDashboards; - const dashboardCount = DocListCast(dashboards.data).length + 1; + const dashboardCount = DocListCast(Doc.MyDashboards.data).length + 1; const freeformOptions: DocumentOptions = { x: 0, y: 400, @@ -391,7 +398,7 @@ export class DashboardView extends React.Component { dashboardDoc['pane-count'] = 1; - Doc.AddDocToList(dashboards, 'data', dashboardDoc); + Doc.AddDocToList(Doc.MyDashboards, 'data', dashboardDoc); DashboardView.SetupDashboardTrails(dashboardDoc); @@ -453,10 +460,6 @@ export class DashboardView extends React.Component { } } -export function AddToList(MySharedDocs: Doc, arg1: string, dash: any) { - throw new Error('Function not implemented.'); -} - ScriptingGlobals.add(function createNewDashboard() { return DashboardView.createNewDashboard(); }, 'creates a new dashboard when called'); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 9ba0e5e26..f3471c350 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -280,7 +280,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt toggleLockedPosition(doc)}> - +
); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index b2eaa964c..8d1b46ebb 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -386,10 +386,6 @@ export class CollectionDockingView extends CollectionSubView() { .map(f => f as Doc); const changesMade = this.props.Document.dockingConfig !== json; if (changesMade) { - if (json.startsWith('{"settings"')) { - alert("Porblems looming") - debugger; - } if (![AclAdmin, AclEdit].includes(GetEffectiveAcl(this.dataDoc))) { this.layoutDoc.dockingConfig = json; this.layoutDoc.data = new List(docs); diff --git a/src/client/views/nodes/button/FontIconBadge.scss b/src/client/views/nodes/button/FontIconBadge.scss index 78f506e57..2ff5c651f 100644 --- a/src/client/views/nodes/button/FontIconBadge.scss +++ b/src/client/views/nodes/button/FontIconBadge.scss @@ -1,11 +1,12 @@ .fontIconBadge { - background: red; + background: lightgreen; width: 15px; height: 15px; top: 8px; + color: black; display: block; position: absolute; right: 5; border-radius: 50%; text-align: center; -} \ No newline at end of file +} diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 9f2eafcee..0a0bac998 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -5,7 +5,7 @@ import { action, computed, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { FaBug, FaCamera, FaStamp } from 'react-icons/fa'; -import { Doc } from '../../../fields/Doc'; +import { Doc, DocListCast } from '../../../fields/Doc'; import { AclAdmin } from '../../../fields/DocSymbols'; import { StrCast } from '../../../fields/Types'; import { GetEffectiveAcl } from '../../../fields/util'; @@ -59,7 +59,11 @@ export class TopBar extends React.Component { return (
{Doc.ActiveDashboard ? ( - } color={this.textColor} /> + !DocListCast(Doc.MySharedDocs.viewed).includes(dash)) ? 'portrait' : 'home'} />} + color={this.textColor} + /> ) : (
dash logo -- cgit v1.2.3-70-g09d2