aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.DS_Storebin10244 -> 10244 bytes
-rw-r--r--src/client/documents/Documents.ts7
-rw-r--r--src/client/util/GroupManager.scss6
-rw-r--r--src/client/util/SharingManager.scss157
-rw-r--r--src/client/util/SharingManager.tsx232
-rw-r--r--src/client/views/DocComponent.tsx47
-rw-r--r--src/client/views/DocumentDecorations.scss103
-rw-r--r--src/client/views/DocumentDecorations.tsx114
-rw-r--r--src/client/views/Main.tsx2
-rw-r--r--src/client/views/MainView.tsx19
-rw-r--r--src/client/views/PropertiesView.scss99
-rw-r--r--src/client/views/PropertiesView.tsx208
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx16
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx8
-rw-r--r--src/client/views/nodes/DocumentView.tsx28
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx15
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts20
-rw-r--r--src/client/views/topbar/TopBar.tsx2
-rw-r--r--src/fields/Doc.ts20
-rw-r--r--src/fields/util.ts94
20 files changed, 892 insertions, 305 deletions
diff --git a/src/.DS_Store b/src/.DS_Store
index be99aa5af..06389d6ae 100644
--- a/src/.DS_Store
+++ b/src/.DS_Store
Binary files differ
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 00864c6fd..85bbc9fed 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -858,13 +858,13 @@ 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;
}
const viewFirstProps: { [id: string]: any } = {};
- viewFirstProps['acl-Public'] = options['_acl-Public'] ? options['_acl-Public'] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
+ // viewFirstProps['acl-Public'] = options['_acl-Public'] ? options['_acl-Public'] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
// viewFirstProps['acl-Override'] = SharingPermissions.Unset;
viewFirstProps.author = Doc.CurrentUserEmail;
let viewDoc: Doc;
@@ -1015,7 +1015,8 @@ export namespace Docs {
I['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
//I['acl-Override'] = SharingPermissions.Unset;
I[Initializing] = false;
- return I;
+
+ return InstanceFromProto(I, '', options);
}
export function PdfDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) {
diff --git a/src/client/util/GroupManager.scss b/src/client/util/GroupManager.scss
index 9438bdd72..3157190bb 100644
--- a/src/client/util/GroupManager.scss
+++ b/src/client/util/GroupManager.scss
@@ -1,6 +1,7 @@
.group-interface {
width: 380px;
height: 300px;
+ position: relative;
.dialogue-box {
.group-create {
@@ -56,8 +57,9 @@
flex-direction: column;
.overlay {
- transform: translate(-20px, -20px);
- border-radius: 10px;
+ transform: translate(-10px, -10px);
+ width: 400px;
+ height: 320px;
}
.delete-button {
diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss
index 932e94664..6dd38ef30 100644
--- a/src/client/util/SharingManager.scss
+++ b/src/client/util/SharingManager.scss
@@ -6,7 +6,7 @@
transform: translate(-20px, -20px);
}
- select {
+ .select {
text-align: justify;
text-align-last: end
}
@@ -23,7 +23,25 @@
z-index: 999;
}
+ .share-title {
+ display: inline-flex;
+ gap: 5px;
+ }
+
+ .share-copy-link {
+ display: inline;
+ border-radius: 4px;
+ border: solid gray 1px;
+ font-size: x-small;
+ background: #E8E8E8;
+ color: black;
+ margin-top: -15px;
+ margin-bottom: 15px;
+ width: fit-content;
+ }
+
.share-container {
+
.share-setup {
display: flex;
margin-bottom: 20px;
@@ -44,11 +62,15 @@
outline: none;
text-align: justify; // for Edge
text-align-last: end;
+ font-size: 13px;
+ min-width: 90px;
+ height: 36;
+ margin-left: 2px;
}
.share-button {
- height: 105%;
- margin-left: 2%;
+ height: 36;
+ margin-left: 3%;
background-color: black;
}
}
@@ -76,15 +98,16 @@
float: right;
align-items: baseline;
margin-top: -12;
+ margin-bottom: 10;
.layoutDoc-acls,
.myDocs-acls {
flex-direction: column;
- margin-right: 12;
label {
font-weight: normal;
font-style: italic;
+ padding-right: 12;
}
input {
@@ -102,6 +125,7 @@
.group-container {
width: 50%;
display: flex;
+ top:0;
flex-direction: column;
.user-sort {
@@ -120,9 +144,10 @@
.users-list {
font-style: italic;
background: #e8e8e8;
+ border: 2px solid gray;
padding-left: 10px;
padding-right: 10px;
- width: 100%;
+ width: 97%;
overflow-y: scroll;
overflow-x: hidden;
text-align: left;
@@ -190,54 +215,150 @@
}
}
+ .title-individual{
+ height: 25px;
+ padding-left: 2;
+ width: 97%;
+ margin-top: 10px;
+ margin-left: -8px;
+ font-size: 14;
+ margin-bottom: -4;
+ border: 2px solid gray;
+ border-bottom: none;
+ align-items: center;
+ display: flex;
+ }
+
+ .title-group{
+ height: 25px;
+ padding-left: 2;
+ width: 97%;
+ margin-top: 10px;
+ margin-left: -.5px;
+ font-size: 14;
+ margin-bottom: -4;
+ border: 2px solid gray;
+ border-bottom: none;
+ align-items: center;
+ display: flex;
+ }
+
.container {
display: flex;
position: relative;
margin-top: 5px;
- margin-bottom: 10px;
+ margin-left: -5px;
font-size: 22px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- width: 100%;
+ width: 97%;
text-align: left;
font-style: normal;
- font-size: 14;
+ font-size: 12.5;
font-weight: normal;
- padding: 0;
- align-items: center;
+
+ padding: 3px;
+ border-bottom: 0.5px solid grey;
.group-info {
cursor: pointer;
}
&:hover .padding {
+ overflow-x: unset;
white-space: unset;
+ overflow-wrap: break-word;
}
.padding {
- padding: 0 10px 0 0;
- color: black;
+ max-width: 150px;
+ overflow-x: hidden;
+ display: inline-block;
text-overflow: ellipsis;
- overflow: hidden;
white-space: nowrap;
- max-width: 40%;
}
.permissions-dropdown {
- border: none;
- height: 25;
- background-color: #e8e8e8;
+ display: flex;
+ align-items: flex-end;
+ text-align: right;
+ margin-left: auto;
+ margin-right: -12px;
+
}
.edit-actions {
display: flex;
position: absolute;
- right: -10;
+ align-items: flex-end;
+ right: -10;
}
+ }
+
+ .permissions-dropdown-None{
+ height: 100%;
+ min-width: 85px;
+ text-align: right;
+ margin-right: -12px;
+ padding: 0px;
+ padding-left: 3px;
+ background: grey;
+ color: rgb(71, 71, 71);
+ border-radius: 6px;
+ border: 1px solid rgb(71, 71, 71);
+ }
+ .permissions-dropdown-Edit,
+ .permissions-dropdown-Admin {
+ height: 100%;
+ min-width: 85px;
+ text-align: right;
+ margin-right: -12px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(254, 254, 199);
+ color: rgb(75, 75, 5);
+ border-radius: 6px;
+ border: 1px solid rgb(75, 75, 5);
+ }
+ .permissions-dropdown-Augment{
+ height: 100%;
+ min-width: 85px;
+ text-align: right;
+ margin-right: -12px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(208, 255, 208);
+ color:rgb(19, 80, 19);
+ border-radius: 6px;
+ border: 1px solid rgb(19, 80, 19);
}
+ .permissions-dropdown-View{
+ height: 100%;
+ min-width: 85px;
+ text-align: right;
+ margin-right: -12px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(213, 213, 255);
+ color: rgb(25, 25, 101);
+ border-radius: 6px;
+ border: 1px solid rgb(25, 25, 101);
+ }
+ .permissions-dropdown-Not-Shared{
+ height: 100%;
+ min-width: 85px;
+ text-align: right;
+ margin-right: -12px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(255, 207, 207);
+ color: rgb(138, 47, 47);
+ border-radius: 6px;
+ border: 1px solid rgb(138, 47, 47);
+ }
.no-users {
margin-top: 20px;
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 97e64ab71..b557dd5d6 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,15 +1,16 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { intersection } from 'lodash';
+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, DocListCast, DocListCastAsync, HierarchyMapping } from '../../fields/Doc';
-import { AclAdmin, AclPrivate, DocAcl, AclUnset, DocData } from '../../fields/DocSymbols';
+import { Doc, DocCastAsync, 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';
-import { NumCast, StrCast } from '../../fields/Types';
+import { DocCast, NumCast, StrCast } from '../../fields/Types';
import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util';
import { Utils } from '../../Utils';
import { DocServer } from '../DocServer';
@@ -23,6 +24,7 @@ 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;
@@ -79,6 +81,7 @@ export class SharingManager extends React.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 overridePrevious: 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() {
@@ -108,6 +111,8 @@ export class SharingManager extends React.Component<{}> {
}),
500
);
+ this.layoutDocAcls = false;
+ this.overridePrevious = false;
});
constructor(props: {}) {
@@ -157,9 +162,20 @@ export class SharingManager extends React.Component<{}> {
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 previous' checkbox is selected
+ var childDocs = DocListCast(target.data);
+ childDocs.map(doc => {
+ if (this.overridePrevious || 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[DocData]))
+ .map(doc => (this.layoutDocAcls ? doc : Doc.GetProto(doc)))
.map(doc => {
doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined, isDashboard);
@@ -168,9 +184,7 @@ export class SharingManager extends React.Component<{}> {
} 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);
@@ -186,11 +200,21 @@ export class SharingManager extends React.Component<{}> {
setInternalGroupSharing = (group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
const target = targetDoc || this.targetDoc!;
const key = normalizeEmail(StrCast(group.title));
- const acl = `acl-${key}`;
+ 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.overridePrevious || 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]))
@@ -248,7 +272,8 @@ export class SharingManager extends React.Component<{}> {
/**
* Called from the properties sidebar to change permissions of a user.
*/
- shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, docs: Doc[]) => {
+ shareFromPropertiesSidebar = (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 => {
@@ -259,10 +284,17 @@ export class SharingManager extends React.Component<{}> {
const dashboards = DocListCast(Doc.MyDashboards.data);
docs.forEach(doc => {
const isDashboard = dashboards.indexOf(doc) !== -1;
- if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined, isDashboard);
+ if (this.overridePrevious) {
+ 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);
+ }
this.setDashboardBackground(doc, permission as SharingPermissions);
});
}
+ this.layoutDocAcls = false;
};
/**
@@ -316,6 +348,7 @@ export class SharingManager extends React.Component<{}> {
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);
const members: string[] = JSON.parse(StrCast(group.members));
const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
@@ -331,22 +364,14 @@ export class SharingManager extends React.Component<{}> {
// return;
// }
// targetDoc["acl-" + PublicKey] = permission;
- // }
+ // }s
- // private get sharingUrl() {
- // if (!this.targetDoc) {
- // return undefined;
- // }
- // const baseUrl = Utils.prepend("/doc/" + this.targetDoc[Id]);
- // return `${baseUrl}?sharing=true`;
- // }
-
- // copy = action(() => {
- // if (this.sharingUrl) {
- // Utils.CopyText(this.sharingUrl);
- // this.copied = true;
- // }
- // });
+ /**
+ * Copies the Public sharing url to the user's clipboard.
+ */
+ private copyURL = (e: any) => {
+ Utils.CopyText(Utils.shareUrl(this.targetDoc![Id]));
+ };
/**
* Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share
@@ -355,13 +380,11 @@ export class SharingManager extends React.Component<{}> {
const dropdownValues: string[] = Object.values(SharingPermissions);
if (!uniform) dropdownValues.unshift('-multiple-');
if (!override) dropdownValues.splice(dropdownValues.indexOf(SharingPermissions.Unset), 1);
- return dropdownValues
- .filter(permission => !Doc.noviceMode || ![SharingPermissions.SelfEdit].includes(permission as any))
- .map(permission => (
- <option key={permission} value={permission}>
- {permission}
- </option>
- ));
+ return dropdownValues.map(permission => (
+ <option key={permission} value={permission}>
+ {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
+ </option>
+ ));
}
private focusOn = (contents: string) => {
@@ -435,6 +458,8 @@ export class SharingManager extends React.Component<{}> {
2000
);
+ this.layoutDocAcls = false;
+ this.overridePrevious = false;
this.selectedUsers = null;
}
};
@@ -464,6 +489,7 @@ export class SharingManager extends React.Component<{}> {
if (!this.targetDoc) return null;
TraceMobx();
const groupList = GroupManager.Instance?.allGroups || [];
+
const sortedUsers = this.users
.slice()
.sort(this.sortUsers)
@@ -494,7 +520,7 @@ export class SharingManager extends React.Component<{}> {
docs = newDocs.filter(doc => GetEffectiveAcl(doc) === AclAdmin);
}
- const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData];
+ const targetDoc: Doc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData];
// tslint:disable-next-line: no-unnecessary-callback-wrapper
const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc));
@@ -505,22 +531,33 @@ export class SharingManager extends React.Component<{}> {
// the list of users shared with
const userListContents: (JSX.Element | null)[] = users
- .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
+ // .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 permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
-
+ // 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-';
+
return !permissions ? null : (
<div key={userKey} className={'container'}>
<span className={'padding'}>{user.email}</span>
<div className="edit-actions">
{admin || this.myDocAcls ? (
- <select className={'permissions-dropdown'} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value)}>
+ <select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value)}>
{this.sharingOptions(uniform)}
</select>
) : (
- <div className={'permissions-dropdown'}>{permissions}</div>
+ <div className={`permissions-dropdown-${permissions}`}>
+ {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)}
+ &nbsp;
+ </div>
)}
</div>
</div>
@@ -531,6 +568,15 @@ export class SharingManager extends React.Component<{}> {
const sameAuthor = docs.every(doc => doc?.author === docs[0]?.author);
// 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 = HierarchyMapping.get(effectiveAcls[0])!.name
userListContents.unshift(
sameAuthor ? (
<div key={'owner'} className={'container'}>
@@ -544,7 +590,10 @@ export class SharingManager extends React.Component<{}> {
<div key={'me'} className={'container'}>
<span className={'padding'}>Me</span>
<div className="edit-actions">
- <div className={'permissions-dropdown'}>{effectiveAcls.every(acl => acl === effectiveAcls[0]) ? HierarchyMapping.get(effectiveAcls[0])!.name : '-multiple-'}</div>
+ <div className={`permissions-dropdown-${curUserPermission}`}>
+ {effectiveAcls.every(acl => acl === effectiveAcls[0]) ? concat(ReverseHierarchyMap.get(curUserPermission!)?.image, ' ', curUserPermission) : '-multiple-'}
+ &nbsp;
+ </div>
</div>
</div>
) : null
@@ -554,27 +603,42 @@ export class SharingManager extends React.Component<{}> {
const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true));
groupListMap.unshift({ title: 'Public' }); //, { title: "ALL" });
const groupListContents = groupListMap.map(group => {
- const groupKey = `acl-${StrCast(group.title)}`;
+ 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-';
+ // 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-';
return !permissions ? null : (
<div key={groupKey} className={'container'}>
<div className={'padding'}>{StrCast(group.title)}</div>
+ &nbsp;
{group instanceof Doc ? (
<div className="group-info" onClick={action(() => (GroupManager.Instance.currentGroup = group))}>
<FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} />
</div>
) : null}
- <div className="edit-actions">
+ <div className={"edit-actions"}>
{admin || this.myDocAcls ? (
- <select className={'permissions-dropdown'} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}>
+ <select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}>
{this.sharingOptions(uniform, group.title === 'Override')}
</select>
) : (
- <div className={'permissions-dropdown'}>{permissions}</div>
+ <div className={`permissions-dropdown-${permissions}`}>
+ {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)}
+ &nbsp;
+ </div>
)}
</div>
</div>
@@ -584,19 +648,26 @@ export class SharingManager extends React.Component<{}> {
<div className="sharing-interface">
{GroupManager.Instance?.currentGroup ? <GroupMemberView group={GroupManager.Instance.currentGroup} onCloseButtonClick={action(() => (GroupManager.Instance.currentGroup = undefined))} /> : null}
<div className="sharing-contents">
- <p className={'share-title'}>
+ <p className="share-title">
+ <IconButton size={Size.SMALL} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties/sharing-and-permissions/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
<b>Share </b>
{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}
+ {/* <button className="share-copy-link" onClick={this.copyURL}>
+ <FontAwesomeIcon title={"Copy Public URL"} icon={'copy'} size={'sm'} onClick={this.copyURL}/>
+ &nbsp;
+ Copy Public URL
+ </button> */}
+ {/* <IconButton size={Size.SMALL} tooltip="Copy Public URL" onClick={this.copyURL} icon={<FontAwesomeIcon icon="copy" />} /> */}
</p>
+ <button className="share-copy-link" onClick={this.copyURL}>
+ <FontAwesomeIcon title={"Copy Public URL"} icon={'copy'} size={'sm'} onClick={this.copyURL}/>
+ &nbsp;
+ Copy Public URL
+ </button>
<div className={'close-button'} onClick={this.close}>
<FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} />
</div>
- {/* {this.linkVisible ?
- <div>
- {this.sharingUrl}
- </div> :
- (null)} */}
- {
+ {admin ? (
<div className="share-container">
<div className="share-setup">
<Select
@@ -615,9 +686,11 @@ export class SharingManager extends React.Component<{}> {
}),
}}
/>
- <select className="permissions-select" onChange={this.handlePermissionsChange} value={this.permissions}>
- {this.sharingOptions(true)}
- </select>
+ <div className='permissions-select'>
+ <select className={`permissions-dropdown-${this.permissions}`} onChange={this.handlePermissionsChange} value={this.permissions}>
+ {this.sharingOptions(true)}
+ </select>
+ </div>
<button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}>
Share
</button>
@@ -630,36 +703,53 @@ export class SharingManager extends React.Component<{}> {
<div className="acl-container">
{Doc.noviceMode ? null : (
<div className="layoutDoc-acls">
+ <input type="checkbox" onChange={action(() => (this.overridePrevious = !this.overridePrevious))} checked={this.overridePrevious} /> <label>Override previous </label>
<input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
</div>
)}
</div>
</div>
- }
+ ) : (
+ <div className='share-container'>
+ <div className='acl-container'>
+ <div className='layoutDoc-acls'>
+ <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
+ </div>
+ </div>
+ </div>
+ )}
<div className="main-container">
<div className={'individual-container'}>
<div className="user-sort" onClick={action(() => (this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}>
- Individuals{' '}
- {this.individualSort === 'ascending' ? (
- <FontAwesomeIcon icon={'caret-up'} size={'xs'} />
- ) : this.individualSort === 'descending' ? (
- <FontAwesomeIcon icon={'caret-down'} size={'xs'} />
- ) : (
- <FontAwesomeIcon icon={'caret-right'} size={'xs'} />
- )}
+ <div className='title-individual'>
+ Individuals &nbsp;
+ {this.individualSort === 'ascending' ? (
+ <FontAwesomeIcon icon={'caret-up'} size={'xs'} />
+ ) : this.individualSort === 'descending' ? (
+ <FontAwesomeIcon icon={'caret-down'} size={'xs'} />
+ ) : (
+ <FontAwesomeIcon icon={'caret-right'} size={'xs'} />
+ )}
+ </div>
</div>
<div className={'users-list'}>{userListContents}</div>
</div>
<div className={'group-container'}>
<div className="user-sort" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}>
- Groups{' '}
- {this.groupSort === 'ascending' ? (
- <FontAwesomeIcon icon={'caret-up'} size={'xs'} />
- ) : this.groupSort === 'descending' ? (
- <FontAwesomeIcon icon={'caret-down'} size={'xs'} />
- ) : (
- <FontAwesomeIcon icon={'caret-right'} size={'xs'} />
- )}
+ <div className='title-group'>
+ Groups &nbsp;
+ <div className="group-info" onClick={action(() => GroupManager.Instance?.open())}>
+ <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} />
+ </div>
+ &nbsp;
+ {this.groupSort === 'ascending' ? (
+ <FontAwesomeIcon icon={'caret-up'} size={'xs'} />
+ ) : this.groupSort === 'descending' ? (
+ <FontAwesomeIcon icon={'caret-down'} size={'xs'} />
+ ) : (
+ <FontAwesomeIcon icon={'caret-right'} size={'xs'} />
+ )}
+ </div>
</div>
<div className={'groups-list'}>{groupListContents}</div>
</div>
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 70d208a0b..f3aa8451a 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,10 +1,10 @@
import { action, computed, observable } from 'mobx';
import { DateField } from '../../fields/DateField';
-import { DocListCast, Opt, Doc } from '../../fields/Doc';
+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 } 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 {
@@ -191,24 +192,37 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
}
const added = docs;
if (added.length) {
- const aclKeys = Object.keys(this.props.Document[DocAcl] ?? {});
+ const aclKeys = Object.keys(Doc.GetProto(this.props.Document)[DocAcl] ?? {});
+
aclKeys.forEach(key =>
added.forEach(d => {
- if (d.author === denormalizeEmail(key.substring(4)) && !d.createdFrom) {
- distributeAcls(key, SharingPermissions.Admin, 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 => {
- 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
@@ -216,9 +230,18 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
doc._dragOnlyWithinContainer = 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<Doc>;
if (annoDocs instanceof List) annoDocs.push(...added.filter(add => !annoDocs.includes(add)));
else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added);
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index ccac5ffe4..ca3610cc0 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -112,6 +112,33 @@ $resizeHandler: 8px;
}
}
+ .documentDecorations-lockButton {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: grey;
+ border: solid 1.5px rgb(72, 71, 71);
+ color: grey;
+ transition: 0.1s ease;
+ opacity: 1;
+ pointer-events: all;
+ width: 20px;
+ height: 20px;
+ min-width: 20px;
+ border-radius: 100%;
+ opacity: 0.5;
+ cursor: pointer;
+
+ &:hover {
+ color: rgb(72, 71, 71);
+ opacity: 1;
+ }
+
+ > svg {
+ margin: 0;
+ }
+ }
+
.documentDecorations-minimizeButton {
display: flex;
align-items: center;
@@ -152,6 +179,7 @@ $resizeHandler: 8px;
display: flex;
height: 20px;
border-radius: 8px;
+ gap: 2px;
outline: none;
border: none;
opacity: 0.3;
@@ -186,6 +214,79 @@ $resizeHandler: 8px;
}
}
+ .documentDecorations-share {
+ background: none;
+ opacity: 1;
+ grid-column: 3;
+ pointer-events: auto;
+ min-width: fit-content;
+ text-align: center;
+ display: flex;
+ height: 21px;
+ opacity: 0.3;
+ &:hover {
+ opacity: 1;
+ }
+
+
+ .checkbox{
+ display: inline;
+
+ .checkbox-box{
+ display: inline;
+ position: relative;
+ top: -2.5;
+ left: 35;
+ zoom: .7;
+ }
+
+ & .checkbox-text{
+ display: inline;
+ position: relative;
+ top: 1.5;
+ font-size: 8px;
+ }
+ }
+
+ .documentDecorations-shareNone{
+ width: calc(100% + 10px);
+ background: grey;
+ color: rgb(71, 71, 71);
+ border-radius: 8px;
+ border: 2px solid rgb(71, 71, 71);
+ }
+ .documentDecorations-shareEdit,
+ .documentDecorations-shareAdmin{
+ width: calc(100% + 10px);
+ background: rgb(254, 254, 199);
+ color: rgb(75, 75, 5);
+ border-radius: 8px;
+ border: 2px solid rgb(75, 75, 5);
+ }
+ .documentDecorations-shareAugment{
+ width: calc(100% + 10px);
+ background: rgb(208, 255, 208);
+ color:rgb(19, 80, 19);
+ border-radius: 8px;
+ border: 2px solid rgb(19, 80, 19);
+
+ }
+ .documentDecorations-shareView{
+ width: calc(100% + 10px);
+ background: rgb(213, 213, 255);
+ color: rgb(25, 25, 101);
+ border-radius: 8px;
+ border: 2px solid rgb(25, 25, 101);
+ }
+ .documentDecorations-shareNot-Shared{
+ width: calc(100% + 10px);
+ background: rgb(255, 207, 207);
+ color: rgb(146, 58, 58);
+ border-radius: 8px;
+ border: 2px solid rgb(146, 58, 58);
+ }
+ }
+
.documentDecorations-centerCont {
grid-column: 2;
background: none;
@@ -264,7 +365,7 @@ $resizeHandler: 8px;
.documentDecorations-lock {
position: relative;
background: black;
- color: gray;
+ color: rgb(145, 144, 144);
height: 14;
width: 14;
pointer-events: all;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3f71111e3..4454a3ec1 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -5,34 +5,35 @@ 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 } from '../../fields/Doc';
-import { AclAdmin, AclEdit, DocData, Height, Width } from '../../fields/DocSymbols';
+import { Doc, DocListCast, Field, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, DocData, Height, Width } from '../../fields/DocSymbols';
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 } from '../../fields/util';
-import { aggregateBounds, emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils';
-import { Docs } from '../documents/Documents';
+import { GetEffectiveAcl, GetEffectiveLayoutAcl, normalizeEmail, SharingPermissions } from '../../fields/util';
import { DocumentType } from '../documents/DocumentTypes';
+import { Docs } from '../documents/Documents';
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 { Colors } from './global/globalEnums';
-import { InkingStroke } from './InkingStroke';
import { InkStrokeProperties } from './InkStrokeProperties';
+import { InkingStroke } from './InkingStroke';
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 { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { ImageBox } from './nodes/ImageBox';
+import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import React = require('react');
import _ = require('lodash');
@@ -64,6 +65,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@observable private _isRotating: boolean = false;
@observable private _isRounding: boolean = false;
@observable private _isResizing: boolean = false;
+ @observable private showLayoutAcl: boolean = false;
constructor(props: any) {
super(props);
@@ -162,33 +164,46 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
};
@action onContainerDown = (e: React.PointerEvent): void => {
- setupMoveUpEvents(
- this,
- e,
- e => this.onBackgroundMove(true, e),
- e => {},
- emptyFunction
- );
+ const first = SelectionManager.Views()[0];
+ const effectiveLayoutAcl = GetEffectiveLayoutAcl(first.rootDoc);
+ if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
+ setupMoveUpEvents(
+ this,
+ e,
+ e => this.onBackgroundMove(true, e),
+ e => {},
+ emptyFunction
+ );
+ }
};
@action onTitleDown = (e: React.PointerEvent): void => {
- setupMoveUpEvents(
- this,
- e,
- e => this.onBackgroundMove(true, e),
- e => {},
- action(e => {
- !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString);
- this._editingTitle = true;
- this._keyinput.current && setTimeout(this._keyinput.current.focus);
- })
- );
+ const first = SelectionManager.Views()[0];
+ const effectiveLayoutAcl = GetEffectiveLayoutAcl(first.rootDoc);
+ if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
+ setupMoveUpEvents(
+ this,
+ e,
+ e => this.onBackgroundMove(true, e),
+ e => {},
+ action(e => {
+ !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString);
+ this._editingTitle = true;
+ this._keyinput.current && setTimeout(this._keyinput.current.focus);
+ })
+ );
+ }
};
onBackgroundDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, e => this.onBackgroundMove(false, e), emptyFunction, emptyFunction);
@action
onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
+ const first = SelectionManager.Views()[0];
+ const effectiveLayoutAcl = GetEffectiveLayoutAcl(first.rootDoc);
+ if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment){
+ return false;
+ }
const dragDocView = SelectionManager.Views()[0];
const containers = new Set<Doc | undefined>();
SelectionManager.Views().forEach(v => containers.add(DocCast(v.rootDoc.embedContainer)));
@@ -481,6 +496,8 @@ 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);
+ 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 };
var fixedAspect = Doc.NativeAspect(first.layoutDoc);
@@ -746,9 +763,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
setTimeout(action(() => (this._showNothing = true)));
return null;
}
+
+ // sharing
+ const acl = this.showLayoutAcl ? GetEffectiveLayoutAcl(seldocview.rootDoc) : GetEffectiveAcl(seldocview.rootDoc);
+ 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 = 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
@@ -769,7 +793,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().rootDoc[DocData]) : AclEdit;
return collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin;
});
-
const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
<Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
<div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()} onPointerDown={pointerDown ?? (e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => click!(e)))}>
@@ -802,6 +825,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
const radiusHandle = (borderRadius / docMax) * maxDist;
const radiusHandleLocation = Math.min(radiusHandle, maxDist);
+ const sharingMenu = docShareMode ? (
+ <div className='documentDecorations-share' >
+ <div className={`documentDecorations-share${shareMode}`}>
+ &nbsp;
+ {shareSymbolIcon + ' ' + shareMode}
+ &nbsp;
+ {!Doc.noviceMode ?
+ <div className='checkbox'>
+ <div className='checkbox-box'>
+ <input type="checkbox" checked={this.showLayoutAcl} onChange={action(() => (this.showLayoutAcl = !this.showLayoutAcl))} />
+ </div>
+ <div className='checkbox-text'> Layout </div>
+ </div>
+ : null }
+ &nbsp;
+ </div>
+ </div>
+ ) : (
+ <div />
+ );
+
const titleArea = this._editingTitle ? (
<input
ref={this._keyinput}
@@ -816,8 +860,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
onPointerDown={e => e.stopPropagation()}
/>
) : (
- <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown}>
- <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${hideTitle ? '' : this.selectionTitle}`}</span>
+ <div className="documentDecorations-title" key="title" onPointerDown={e => {e.stopPropagation}}>
+ { hideTitle ? null : <span className={`documentDecorations-titleSpan${colorScheme}`} onPointerDown={this.onTitleDown}>{this.selectionTitle}</span>}
+ {sharingMenu}
{!useLock ? null : (
<Tooltip key="lock" title={<div className="dash-tooltip">toggle ability to interact with document</div>} placement="top">
<div className="documentDecorations-lock" style={{ color: seldocview.rootDoc._lockedPosition ? 'red' : undefined }} onPointerDown={this.onLockDown} onContextMenu={e => e.preventDefault()}>
@@ -827,6 +872,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
)}
</div>
);
+
return (
<div className={`documentDecorations${colorScheme}`} style={{ opacity: this._showNothing ? 0.1 : undefined }}>
<div
@@ -859,8 +905,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
<div className="documentDecorations-topbar" style={{ display: hideDeleteButton && hideTitle && hideOpenButton ? 'none' : undefined }} onPointerDown={this.onContainerDown}>
{hideDeleteButton ? null : topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')}
{hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')}
- {hideTitle ? null : titleArea}
- {hideOpenButton ? null : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as new embedding, shift: in new collection)')}
+ {titleArea}
+ {hideOpenButton ? <div /> : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')}
</div>
{hideResizers ? null : (
<>
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index b0b757388..ed39cde13 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -27,7 +27,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0 }; // bcz: not sure
root.render(<FieldLoader />);
window.location.search.includes('safe') && CollectionView.SetSafeMode(true);
const info = await CurrentUserUtils.loadCurrentUser();
- if (info.email === 'guest') DocServer.Control.makeReadOnly();
+ // if (info.email === 'guest') DocServer.Control.makeReadOnly();
await CurrentUserUtils.loadUserDocument(info.id);
setTimeout(() => {
document.getElementById('root')!.addEventListener(
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index ab2e0f7c5..258674d53 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -157,33 +157,28 @@ export class MainView extends React.Component {
'dataTransition',
'viewTransition',
'treeViewOpen',
- 'layout_showSidebar',
'carousel_index',
'itemIndex', // for changing slides in presentations
'layout_sidebarWidthPercent',
'layout_currentTimecode',
'layout_timelineHeightPercent',
+ 'layout_hideMinimap',
+ 'layout_showSidebar',
+ 'layout_scrollTop',
+ 'layout_fitWidth',
+ 'layout_curPage',
'presStatus',
'freeform_panX',
'freeform_panY',
+ 'freeform_scale',
'overlayX',
'overlayY',
- 'layout_fitWidth',
- 'nativeWidth',
- 'nativeHeight',
'text_scrollHeight',
'text_height',
- 'layout_hideMinimap',
- 'freeform_scale',
- 'layout_scrollTop',
'hidden',
- 'layout_curPage',
- 'type_collection',
+ //'type_collection',
'chromeHidden',
'currentFrame',
- 'width',
- 'height',
- 'nativeWidth',
]); // can play with these fields on someone else's
}
DocServer.GetRefField('rtfProto').then(
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index 897be9a32..f2be966b9 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -135,12 +135,12 @@
}
.propertiesView-acls-checkbox {
- margin-top: -20px;
+ margin-top: -15px;
+ margin-bottom: -10px;
.propertiesView-acls-checkbox-text {
- font-size: 7px;
- margin-top: -10px;
- margin-left: 6px;
+ display: inline;
+ font-size: 9px;
}
}
}
@@ -160,6 +160,59 @@
}
}
+ .propertiesView-shareDropDown{
+ margin-right: 10px;
+ min-width: 65px;
+
+ & .propertiesView-shareDropDownNone{
+ height: 16px;
+ padding: 0px;
+ padding-left: 3px;
+ background: grey;
+ color: rgb(71, 71, 71);
+ border-radius: 6px;
+ border: 1px solid rgb(71, 71, 71);
+ }
+ & .propertiesView-shareDropDownEdit,
+ .propertiesView-shareDropDownAdmin{
+ height: 16px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(254, 254, 199);
+ color: rgb(75, 75, 5);
+ border-radius: 6px;
+ border: 1px solid rgb(75, 75, 5);
+ }
+ & .propertiesView-shareDropDownAugment{
+ height: 16px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(208, 255, 208);
+ color:rgb(19, 80, 19);
+ border-radius: 6px;
+ border: 1px solid rgb(19, 80, 19);
+
+ }
+ & .propertiesView-shareDropDownView{
+ height: 16px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(213, 213, 255);
+ color: rgb(25, 25, 101);
+ border-radius: 6px;
+ border: 1px solid rgb(25, 25, 101);
+ }
+ & .propertiesView-shareDropDownNot-Shared{
+ height: 16px;
+ padding: 0px;
+ padding-left: 3px;
+ background: rgb(255, 207, 207);
+ color: rgb(138, 47, 47);
+ border-radius: 6px;
+ border: 1px solid rgb(138, 47, 47);
+ }
+ }
+
.propertiesView-filters {
//border-bottom: 1px solid black;
//padding: 8.5px;
@@ -317,13 +370,13 @@
}
.expansion-button {
- margin-left: -20;
+ margin-left: -15px;
+ margin-right: 20px;
.expansion-button-icon {
width: 11px;
height: 11px;
color: black;
- margin-left: 27px;
&:hover {
color: rgb(131, 131, 131);
@@ -340,18 +393,13 @@
padding: 5px; // remove when adding buttons
border-radius: 6px; // remove when adding buttons
margin-right: 10px; // remove when adding buttons
- // width: 100%;
- // display: inline-table;
background-color: #ececec;
- max-height: 130px;
- overflow-y: auto;
- width: 92%;
+ width: 97%;
.propertiesView-sharingTable-item {
display: flex;
- // padding: 5px;
padding: 3px;
- align-items: center;
+ align-items: right;
border-bottom: 0.5px solid grey;
&:hover .propertiesView-sharingTable-item-name {
@@ -372,19 +420,9 @@
.propertiesView-sharingTable-item-permission {
display: flex;
align-items: flex-end;
+ text-align: right;
margin-left: auto;
-
- .permissions-select {
- border: none;
- background-color: inherit;
- width: 87px;
- text-align: justify; // for Edge
- text-align-last: end;
-
- &:hover {
- cursor: pointer;
- }
- }
+ margin-right: -12px;
}
&:last-child {
@@ -393,6 +431,17 @@
}
}
+ .propertiesView-permissions-select {
+ background-color: inherit;
+ background: inherit;
+ border: none;
+ background: inherit;
+ width: max;
+ text-align: left;
+ display: flex;
+ right: 35px;
+ }
+
.propertiesView-fields {
//border-bottom: 1px solid black;
//padding: 8.5px;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 09aac053a..14291b537 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -3,38 +3,39 @@ import { IconLookup } from '@fortawesome/fontawesome-svg-core';
import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Checkbox, Tooltip } from '@material-ui/core';
-import { intersection } from 'lodash';
-import { action, computed, Lambda, observable } from 'mobx';
+import { concat, intersection } from 'lodash';
+import { Lambda, action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import { ColorState, SketchPicker } from 'react-color';
-import { Doc, Field, FieldResult, HierarchyMapping, NumListCast, Opt, StrListCast } from '../../fields/Doc';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
+import { Doc, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
import { AclAdmin, DocAcl, DocData, Height, Width } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { InkField } from '../../fields/InkField';
import { List } from '../../fields/List';
import { ComputedField } from '../../fields/ScriptField';
import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
-import { denormalizeEmail, GetEffectiveAcl, SharingPermissions } from '../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
+import { GetEffectiveAcl, SharingPermissions, normalizeEmail } from '../../fields/util';
import { DocumentType } from '../documents/DocumentTypes';
import { DocumentManager } from '../util/DocumentManager';
+import { GroupManager } from '../util/GroupManager';
import { LinkManager } from '../util/LinkManager';
import { SelectionManager } from '../util/SelectionManager';
import { SharingManager } from '../util/SharingManager';
import { Transform } from '../util/Transform';
-import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
+import { UndoManager, undoBatch, undoable } from '../util/UndoManager';
import { EditableView } from './EditableView';
import { FilterPanel } from './FilterPanel';
-import { Colors } from './global/globalEnums';
import { InkStrokeProperties } from './InkStrokeProperties';
-import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
-import { KeyValueBox } from './nodes/KeyValueBox';
-import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
import { PropertiesButtons } from './PropertiesButtons';
import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector';
import { PropertiesDocContextSelector } from './PropertiesDocContextSelector';
import './PropertiesView.scss';
import { DefaultStyleProvider } from './StyleProvider';
+import { Colors } from './global/globalEnums';
+import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
+import { KeyValueBox } from './nodes/KeyValueBox';
+import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -301,7 +302,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@undoBatch
changePermissions = (e: any, user: string) => {
const docs = (SelectionManager.Views().length < 2 ? [this.selectedDoc] : SelectionManager.Views().map(dv => dv.props.Document)).filter(doc => doc).map(doc => (this.layoutDocAcls ? doc! : DocCast(doc)[DocData]));
- SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs);
+ SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs, this.layoutDocAcls);
};
/**
@@ -310,15 +311,16 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
getPermissionsSelect(user: string, permission: string) {
const dropdownValues: string[] = Object.values(SharingPermissions);
if (permission === '-multiple-') dropdownValues.unshift(permission);
- if (user !== 'Override') dropdownValues.splice(dropdownValues.indexOf(SharingPermissions.Unset), 1);
+ if (user !== 'Override') {
+ dropdownValues.splice(dropdownValues.indexOf(SharingPermissions.Unset), 1);
+ }
return (
- <select className="permissions-select" value={permission} onChange={e => this.changePermissions(e, user)}>
+ <select className="propertiesView-permissions-select" value={permission} onChange={e => this.changePermissions(e, user)}>
{dropdownValues
- .filter(permission => !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any))
+ .filter(permission => !Doc.noviceMode || ![SharingPermissions.View].includes(permission as any))
.map(permission => (
- <option key={permission} value={permission}>
- {' '}
- {permission}{' '}
+ <option className="propertiesView-permisssions-select" key={permission} value={permission}>
+ {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
</option>
))}
</select>
@@ -361,6 +363,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
* @returns a row of the permissions panel
*/
sharingItem(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) {
+ if (name == Doc.CurrentUserEmail) {
+ name = 'Me';
+ }
return (
<div
className="propertiesView-sharingTable-item"
@@ -374,58 +379,156 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</div>
{/* {name !== "Me" ? this.notifyIcon : null} */}
<div className="propertiesView-sharingTable-item-permission">
- {admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission) : permission}
- {permission === 'Owner' || showExpansionIcon ? this.expansionIcon : null}
+ {this.colorACLDropDown(name, admin, permission, showExpansionIcon)}
+ {(permission === 'Owner' && name == 'Me') || showExpansionIcon ? this.expansionIcon : null}
</div>
</div>
);
}
/**
+ * @returns a colored dropdown bar reflective of the permission
+ */
+ colorACLDropDown(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) {
+ var shareImage = ReverseHierarchyMap.get(permission)?.image;
+ return (
+ <div>
+ <div className={'propertiesView-shareDropDown'}>
+ <div className={`propertiesView-shareDropDown${permission}`}>
+ <div className="propertiesView-shareDropDown">
+ {admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission) : concat(shareImage, ' ', permission)}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ /**
+ * Sorting algorithm to sort users.
+ */
+ sortUsers = (u1: String, u2: String) => {
+ return u1 > u2 ? -1 : u1 === u2 ? 0 : 1;
+ };
+
+ /**
+ * Sorting algorithm to sort groups.
+ */
+ sortGroups = (group1: Doc, group2: Doc) => {
+ const g1 = StrCast(group1.title);
+ const g2 = StrCast(group2.title);
+ return g1 > g2 ? -1 : g1 === g2 ? 0 : 1;
+ };
+
+ /**
* @returns the sharing and permissions panel.
*/
@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 target = docs[0];
- // tslint:disable-next-line: no-unnecessary-callback-wrapper
- const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc));
- const showAdmin = effectiveAcls.every(acl => acl === AclAdmin);
+ const showAdmin = GetEffectiveAcl(target) == AclAdmin
+ const individualTableEntries = [];
+ const usersAdded: string[] = []; // all shared users being added - organized by denormalized email
- // users in common between all docs
- const commonKeys: string[] = intersection(...docs.map(doc => doc?.[DocAcl] && Object.keys(doc[DocAcl]).filter(key => key !== 'acl-Me')));
+ // 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) {
+ userOnDoc = false;
+ }
+ }
+ if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email != 'Public' && eachUser.user.email != target.author) {
+ usersAdded.push(eachUser.user.email);
+ }
+ });
- const tableEntries = [];
+ // sorts and then adds each user to the table
+ 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]);
+ individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user
+ });
- // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => {
- if (commonKeys.length) {
- for (const key of commonKeys) {
- const name = denormalizeEmail(key.substring(4));
- const uniform = docs.every(doc => doc?.[DocAcl]?.[key] === docs[0]?.[DocAcl]?.[key]);
- if (name !== Doc.CurrentUserEmail && name !== target.author && name !== 'Public' && name !== 'Override' /* && sidebarUsersDisplayed![name] !== false*/) {
- tableEntries.push(this.sharingItem(name, showAdmin, uniform ? HierarchyMapping.get(target[DocAcl][key])!.name : '-multiple-'));
+ // adds current user
+ var userEmail = Doc.CurrentUserEmail;
+ const userKey = `acl-${normalizeEmail(userEmail)}`;
+ if (userEmail == 'guest') userEmail = 'Public';
+ if (!usersAdded.includes(userEmail) && userEmail != 'Public' && userEmail != target.author) {
+ 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]);
+ individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user
+ }
+
+ // shift owner to top
+ individualTableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, 'Owner'), false);
+
+ // adds groups
+ const groupTableEntries: JSX.Element[] = [];
+ const groupList = GroupManager.Instance?.allGroups || [];
+ 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]){
+ permission = HierarchyMapping.get(target[DocAcl][groupKey])?.name;
+ }
+ else if (target['embedContainer']) permission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))[groupKey]);
+ else permission = StrCast(Doc.GetProto(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 ownerSame = Doc.CurrentUserEmail !== target.author && docs.filter(doc => doc).every(doc => doc.author === docs[0].author);
- // shifts the current user, owner, public to the top of the doc.
- // tableEntries.unshift(this.sharingItem("Override", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Override"] === docs[0]["acl-Override"]) ? (AclMap.get(target[AclSym]?.["acl-Override"]) || "None") : "-multiple-"));
- if (ownerSame) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, 'Owner'));
- tableEntries.unshift(this.sharingItem('Public', showAdmin, StrCast(docs.filter(doc => doc).every(doc => doc['acl-Public'] === target['acl-Public']) ? target['acl-Public'] || SharingPermissions.None : '-multiple-')));
- tableEntries.unshift(
- this.sharingItem(
- 'Me',
- showAdmin,
- docs.filter(doc => doc).every(doc => doc.author === Doc.CurrentUserEmail) ? 'Owner' : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? HierarchyMapping.get(effectiveAcls[0])!.name : '-multiple-',
- !ownerSame
- )
+ return (
+ <div>
+ <br/>
+ Public / Guest Users
+ <div>{this.colorACLDropDown('Public', showAdmin, publicPermission!, false)}</div>
+ <div>
+ {' '}
+ <br></br> Individual Users with Access to this Document{' '}
+ </div>
+ <div className="propertiesView-sharingTable">{<div> {individualTableEntries}</div>}</div>
+ {groupTableEntries.length>0 ?
+ <div>
+ <div>
+ {' '}
+ <br></br> Groups with Access to this Document{' '}
+ </div>
+ <div className="propertiesView-sharingTable">{<div> {groupTableEntries}</div>}</div>
+ </div>
+ : null}
+ </div>
);
-
- return <div className="propertiesView-sharingTable">{tableEntries}</div>;
}
@computed get fieldsCheckbox() {
@@ -920,12 +1023,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
{!this.openSharing ? null : (
<div className="propertiesView-sharing-content">
<div className="propertiesView-buttonContainer">
- {!Doc.noviceMode ? (
- <div className="propertiesView-acls-checkbox">
- <Checkbox color="primary" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} />
- <div className="propertiesView-acls-checkbox-text">Layout</div>
- </div>
- ) : null}
+ <div className="propertiesView-acls-checkbox">
+ <div className="propertiesView-acls-checkbox-text"> Show / Contol Layout Permissions </div>
+ <Checkbox color="primary" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} />
+ </div>
+
{/* <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}>
<button onPointerDown={() => SharingManager.Instance.distributeOverCollection(this.selectedDoc!)}>
<FontAwesomeIcon icon="redo-alt" color="white" size="1x" />
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 2ed55b3ca..32fb4d8df 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';
@@ -30,6 +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';
const _global = (window /* browser */ || global) /* node */ as any;
@observer
@@ -494,6 +495,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)
};
@@ -554,7 +564,7 @@ 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));
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 5febbe83e..9b0abc48b 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<SubCollectionViewProps & Marque
newCollection.x = this.Bounds.left;
newCollection.y = this.Bounds.top;
newCollection.layout_fitWidth = true;
- selected.forEach(d => (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;
});
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 19f9f15a4..dab269474 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1084,8 +1084,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@computed get innards() {
TraceMobx();
const ffscale = () => this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1;
- const layout_showTitle = this.layout_showTitle?.split(':')[0];
- const layout_showTitleHover = this.layout_showTitle?.includes(':hover');
+ const showTitle = this.layout_showTitle?.split(':')[0];
+ const showTitleHover = this.layout_showTitle?.includes(':hover');
const captionView = !this.layout_showCaption ? null : (
<div
className="documentView-captionWrapper"
@@ -1109,27 +1109,27 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
/>
</div>
);
- const targetDoc = layout_showTitle?.startsWith('_') ? this.layoutDoc : this.rootDoc;
+ const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.rootDoc;
const background = StrCast(
SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.userColor,
Doc.UserDoc().layout_showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : 'rgba(0,0,0,0.4)'
);
- const layout_sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', '');
- const titleView = !layout_showTitle ? null : (
+ const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', '');
+ const titleView = !showTitle ? null : (
<div
- className={`documentView-titleWrapper${layout_showTitleHover ? '-hover' : ''}`}
+ className={`documentView-titleWrapper${showTitleHover ? '-hover' : ''}`}
key="title"
style={{
position: this.headerMargin ? 'relative' : 'absolute',
height: this.titleHeight,
- width: !this.headerMargin ? `calc(${layout_sidebarWidthPercent || 100}% - 18px)` : (layout_sidebarWidthPercent || 100) + '%', // leave room for annotation button
+ width: !this.headerMargin ? `calc(${sidebarWidthPercent || 100}% - 18px)` : (sidebarWidthPercent || 100) + '%', // leave room for annotation button
color: lightOrDark(background),
background,
pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
}}>
<EditableView
ref={this._titleRef}
- contents={layout_showTitle
+ contents={showTitle
.split(';')
.map(field => field.trim())
.map(field => targetDoc[field]?.toString())
@@ -1138,7 +1138,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
fontSize={10}
GetValue={() => {
this.props.select(false);
- return layout_showTitle.split(';').length === 1 ? layout_showTitle + '=' + Field.toString(targetDoc[layout_showTitle.split(';')[0]] as any as Field) : '#' + layout_showTitle;
+ return showTitle.split(';').length === 1 ? showTitle + '=' + Field.toString(targetDoc[showTitle.split(';')[0]] as any as Field) : '#' + showTitle;
}}
SetValue={undoBatch((input: string) => {
if (input?.startsWith('#')) {
@@ -1148,17 +1148,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'author_date';
}
} else {
- var value = input.replace(new RegExp(layout_showTitle + '='), '') as string | number;
- if (layout_showTitle !== 'title' && Number(value).toString() === value) value = Number(value);
- if (layout_showTitle.includes('Date') || layout_showTitle === 'author') return true;
- Doc.SetInPlace(targetDoc, layout_showTitle, value, true);
+ var value = input.replace(new RegExp(showTitle + '='), '') as string | number;
+ if (showTitle !== 'title' && Number(value).toString() === value) value = Number(value);
+ if (showTitle.includes('Date') || showTitle === 'author') return true;
+ Doc.SetInPlace(targetDoc, showTitle, value, true);
}
return true;
})}
/>
</div>
);
- return this.props.hideTitle || (!layout_showTitle && !this.layout_showCaption) ? (
+ return this.props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
this.contents
) : (
<div className="documentView-styleWrapper">
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 44cb56d53..202a9f851 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -12,8 +12,8 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, Height, UpdatingFromServer, Width } from '../../../../fields/DocSymbols';
+import { Doc, DocListCast, StrListCast, Field, Opt } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, Height, Width, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
@@ -311,7 +311,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"'));
- if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) {
+ if ([AclEdit, AclAdmin, AclSelfEdit, AclAugment].includes(effectiveAcl)) {
const accumTags = [] as string[];
state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => {
if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) {
@@ -1829,8 +1829,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
default:
if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
case ' ':
- [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) &&
- this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
+ if (e.code !== 'Space') {
+ [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.rootDoc)) &&
+ this._editorView!.dispatch(
+ this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))
+ );
+ }
+ break;
}
this.startUndoTypingBatch();
};
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 8d57cc081..d2273c91c 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -4,8 +4,7 @@ import { Schema } from 'prosemirror-model';
import { splitListItem, wrapInList } from 'prosemirror-schema-list';
import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
import { liftTarget } from 'prosemirror-transform';
-import { Doc } from '../../../../fields/Doc';
-import { AclAugment, AclSelfEdit } from '../../../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit} from '../../../../fields/DocSymbols';
import { GetEffectiveAcl } from '../../../../fields/util';
import { Utils } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
@@ -13,6 +12,7 @@ import { RTFMarkup } from '../../../util/RTFMarkup';
import { SelectionManager } from '../../../util/SelectionManager';
import { OpenWhere } from '../DocumentView';
import { liftListItem, sinkListItem } from './prosemirrorPatches.js';
+import { Doc } from '../../../../fields/Doc';
const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
@@ -49,15 +49,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
const canEdit = (state: any) => {
switch (GetEffectiveAcl(props.DataDoc)) {
case AclAugment:
- return false;
- case AclSelfEdit:
- for (var i = state.selection.from; i < state.selection.to; i++) {
- const marks = state.doc.resolve(i)?.marks?.();
- if (marks?.some((mark: any) => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail)) {
- return false;
- }
+ const prevNode = state.selection.$cursor.nodeBefore;
+ const prevUser = prevNode.marks[prevNode.marks.length-1].attrs.userid
+ if (prevUser != Doc.CurrentUserEmail){
+ return false;
}
- break;
}
return true;
};
@@ -267,7 +263,7 @@ export function buildKeymap<S extends Schema<any>>(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));
@@ -338,7 +334,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
//Command to create a blank space
bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
- if (!canEdit(state)) 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/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 20cf563c1..9f2eafcee 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -114,7 +114,7 @@ export class TopBar extends React.Component {
}}
/>
<Button
- text={GetEffectiveAcl(Doc.GetProto(Doc.ActiveDashboard)) === AclAdmin ? 'Share' : 'View Original'}
+ text={GetEffectiveAcl(Doc.ActiveDashboard) === AclAdmin ? 'Share' : 'View Original'}
onClick={() => {
SharingManager.Instance.open(undefined, Doc.ActiveDashboard);
}}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index f13dab68c..99712fb04 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -126,21 +126,19 @@ export enum aclLevel {
unshared = 0,
viewable = 1,
augmentable = 2,
- selfEditable = 2.5,
editable = 3,
admin = 4,
}
// prettier-ignore
-export const HierarchyMapping: Map<symbol, { level:aclLevel; name: SharingPermissions }> = new Map([
- [AclPrivate, { level: aclLevel.unshared, name: SharingPermissions.None }],
- [AclReadonly, { level: aclLevel.viewable, name: SharingPermissions.View }],
- [AclAugment, { level: aclLevel.augmentable, name: SharingPermissions.Augment}],
- [AclSelfEdit, { level: aclLevel.selfEditable, name: SharingPermissions.SelfEdit }],
- [AclEdit, { level: aclLevel.editable, name: SharingPermissions.Edit }],
- [AclAdmin, { level: aclLevel.admin, name: SharingPermissions.Admin }],
- [AclUnset, { level: aclLevel.unset, name: SharingPermissions.Unset }],
+export const HierarchyMapping: Map<symbol, { level:aclLevel; name: SharingPermissions; image: string }> = new Map([
+ [AclPrivate, { level: aclLevel.unshared, name: SharingPermissions.None, image: '▲' }],
+ [AclReadonly, { level: aclLevel.viewable, name: SharingPermissions.View, image: '♦' }],
+ [AclAugment, { level: aclLevel.augmentable, name: SharingPermissions.Augment, image: '⬟' }],
+ [AclEdit, { level: aclLevel.editable, name: SharingPermissions.Edit, image: '⬢' }],
+ [AclAdmin, { level: aclLevel.admin, name: SharingPermissions.Admin, image: '⬢' }],
+ [AclUnset, { level: aclLevel.unset, name: SharingPermissions.Unset, image: '▲' }],
]);
-export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol }> = new Map(Array.from(HierarchyMapping.entries()).map(value => [value[1].name, { level: value[1].level, acl: value[0] }]));
+export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol ; image: string}> = 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.
@@ -1090,7 +1088,7 @@ export namespace Doc {
target[targetKey] = new PrefetchProxy(templateDoc);
} else {
titleTarget && (Doc.GetProto(target).title = titleTarget);
- const setDoc = [AclAdmin, AclEdit].includes(GetEffectiveAcl(Doc.GetProto(target))) ? Doc.GetProto(target) : target;
+ const setDoc = [AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(Doc.GetProto(target))) ? Doc.GetProto(target) : target;
setDoc[targetKey] = new PrefetchProxy(templateDoc);
}
}
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 0f164a709..0f613d926 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -8,15 +8,14 @@ import { UndoManager } from '../client/util/UndoManager';
import { returnZero } from '../Utils';
import CursorField from './CursorField';
import { aclLevel, Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap, StrListCast, updateCachedAcls } from './Doc';
-import { AclAdmin, AclEdit, AclPrivate, AclSelfEdit, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, Update, UpdatingFromServer, Width } from './DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclSelfEdit, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, Update, UpdatingFromServer, Width } from './DocSymbols';
import { Id, OnUpdate, Parent, ToValue } from './FieldSymbols';
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, ScriptField } from './ScriptField';
+import { ComputedField } from './ScriptField';
import { ScriptCast, StrCast } from './Types';
function _readOnlySetter(): never {
@@ -63,8 +62,8 @@ 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 === AclAdmin || writeMode !== DocServer.WriteMode.LiveReadonly;
- const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclSelfEdit && value instanceof RichTextField)) && !DocServer.Control.isReadOnly();
+ 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();
if (writeToDoc) {
if (value === undefined) {
@@ -129,13 +128,15 @@ export function denormalizeEmail(email: string) {
* Copies parent's acl fields to the child
*/
export function inheritParentAcls(parent: Doc, child: Doc) {
- return;
- // const dataDoc = parent[DataSym];
- // for (const key of Object.keys(dataDoc)) {
- // // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private.
- // const permission = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : dataDoc[key];
- // key.startsWith('acl') && distributeAcls(key, permission, child);
- // }
+ 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)
+ }
+ }
}
/**
@@ -157,10 +158,9 @@ export enum SharingPermissions {
Unset = 'None',
Admin = 'Admin',
Edit = 'Edit',
- SelfEdit = 'Self Edit',
Augment = 'Augment',
View = 'View',
- None = 'Not Shared',
+ None = 'Not-Shared',
}
// return acl from cache or cache the acl and return.
@@ -168,15 +168,29 @@ 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(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)
+ 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);
+ }
+
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
@@ -204,18 +218,13 @@ function getEffectiveAcl(target: any, user?: string): symbol {
// there are issues with storing fields with . in the name, so they are replaced with _ during creation
// as a result we need to restore them again during this comparison.
const entity = denormalizeEmail(key.substring(4)); // an individual or a group
- if (HierarchyMapping.get(value as symbol)!.level > HierarchyMapping.get(effectiveAcl)!.level) {
- if (GetCachedGroupByName(entity) || userChecked === entity || entity === 'Me') {
+ if (GetCachedGroupByName(entity) || userChecked === entity || entity === 'Me') {
+ if (HierarchyMapping.get(value as symbol)!.level > HierarchyMapping.get(effectiveAcl)!.level) {
effectiveAcl = value as symbol;
}
}
}
- // if there's an overriding acl set through the properties panel or sharing menu, that's what's returned if the user isn't an admin of the document
- //const override = targetAcls['acl-Override'];
- // if (override !== AclUnset && override !== undefined) effectiveAcl = override;
-
- // if we're in playground mode, return AclEdit (or AclAdmin if that's the user's effectiveAcl)
return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)!.level < aclLevel.editable ? AclEdit : effectiveAcl;
}
// authored documents are private until an ACL is set.
@@ -223,6 +232,43 @@ function getEffectiveAcl(target: any, user?: string): symbol {
if (targetAuthor && targetAuthor !== userChecked) return AclPrivate;
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;
+ }
+ 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;
+}
+
+
/**
* 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)
@@ -234,7 +280,6 @@ function getEffectiveAcl(target: any, user?: string): symbol {
export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean, visited?: Doc[], isDashboard?: boolean) {
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 !== Doc.GetProto(target)) {
@@ -250,7 +295,6 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
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));
@@ -289,7 +333,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);
- if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) return true;
+ 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;