diff options
-rw-r--r-- | package-lock.json | 6 | ||||
-rw-r--r-- | src/client/documents/Documents.ts | 2 | ||||
-rw-r--r-- | src/client/util/SharingManager.scss | 8 | ||||
-rw-r--r-- | src/client/util/SharingManager.tsx | 126 | ||||
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 60 | ||||
-rw-r--r-- | src/client/views/PropertiesView.scss | 7 | ||||
-rw-r--r-- | src/client/views/PropertiesView.tsx | 124 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 18 | ||||
-rw-r--r-- | src/fields/Doc.ts | 7 | ||||
-rw-r--r-- | src/fields/util.ts | 47 |
10 files changed, 292 insertions, 113 deletions
diff --git a/package-lock.json b/package-lock.json index 42a7babb6..d74073e7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22164,12 +22164,6 @@ "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", "dev": true }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 3e89c8347..f5510d7e1 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -12,7 +12,7 @@ import { SchemaHeaderField } from '../../fields/SchemaHeaderField'; import { ComputedField, ScriptField } from '../../fields/ScriptField'; import { Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types'; import { AudioField, CsvField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from '../../fields/URLField'; -import { inheritParentAcls, SharingPermissions } from '../../fields/util'; +import { inheritParentAcls, normalizeEmail, SharingPermissions } from '../../fields/util'; import { Upload } from '../../server/SharedMediaTypes'; import { aggregateBounds, OmitKeys, Utils } from '../../Utils'; import { YoutubeBox } from '../apis/youtube/YoutubeBox'; diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss index 932e94664..f47c48805 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 } @@ -212,9 +212,9 @@ cursor: pointer; } - &:hover .padding { - white-space: unset; - } + // &:hover .padding { + // white-space: unset; + // } .padding { padding: 0 10px 0 0; diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 3c05af4bb..5e61f6d3c 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -12,6 +12,7 @@ import { Cast, NumCast, PromiseValue, StrCast } from '../../fields/Types'; import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util'; import { Utils } from '../../Utils'; import { DocServer } from '../DocServer'; +import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; import { CollectionView } from '../views/collections/CollectionView'; import { DictationOverlay } from '../views/DictationOverlay'; import { MainViewModal } from '../views/MainViewModal'; @@ -157,21 +158,42 @@ export class SharingManager extends React.Component<{}> { const acl = `acl-${normalizeEmail(user.email)}`; const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`; const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1; + + // var docs: Doc[] = []; + // const dashboardList = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); + // const dashboard = dashboardList[0] + // docs.push(dashboard!) + // const tabs = DocListCast(dashboard?.data) + // tabs.forEach(tab => { + // docs.push(tab) + // var newDocs = DocListCast(tab.data) + // newDocs.forEach(newDoc => + // docs.push(newDoc)) + // }) + + // if (!docs){ + // return null; + // } const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); + + //var docs: Doc[] = []; + docs.push(targetDoc!) + + // ! ensures it returns true if document has been shared successfully, false otherwise return !docs .map(doc => (this.layoutDocAcls ? doc : doc[DataSym])) .map(doc => { doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined, isDashboard); - + if (permission === SharingPermissions.None) { if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 1) - 1; } else { if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1; } + doc = targetDoc 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); else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc) || doc); @@ -190,8 +212,18 @@ export class SharingManager extends React.Component<{}> { const acl = `acl-${key}`; const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1; - const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); - + var docs: Doc[] = []; + const dashboardList = SelectionManager.Views().length < 2 ? [this.targetDoc] : SelectionManager.Views().map(docView => docView.props.Document); + const dashboard = dashboardList[0] + const tabs = DocListCast(dashboard?.data) + tabs.forEach(tab => { + var newDocs = DocListCast(tab.data) + newDocs.forEach(newDoc => docs.push(newDoc)) + }) + + if (!docs){ + return null; + } // ! ensures it returns true if document has been shared successfully, false otherwise return !docs .map(doc => (this.layoutDocAcls ? doc : doc[DataSym])) @@ -334,13 +366,20 @@ export class SharingManager extends React.Component<{}> { // targetDoc["acl-" + PublicKey] = permission; // } - // private get sharingUrl() { - // if (!this.targetDoc) { - // return undefined; - // } - // const baseUrl = Utils.prepend("/doc/" + this.targetDoc[Id]); - // return `${baseUrl}?sharing=true`; - // } + private get sharingUrl() { + if (!this.targetDoc) { + return undefined; + } + const baseUrl = Utils.prepend("/doc/" + this.targetDoc[Id]); + return `${baseUrl}?sharing=true`; + } + + /** + * Copies the Public sharing url to the user's clipboard. + */ + private copyURL = (e: any) => { + Utils.CopyText(this.sharingUrl!) + } // copy = action(() => { // if (this.sharingUrl) { @@ -357,7 +396,7 @@ export class SharingManager extends React.Component<{}> { 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)) + .filter(permission => permission != SharingPermissions.SelfEdit && (!Doc.noviceMode || ![SharingPermissions.SelfEdit].includes(permission as any))) .map(permission => ( <option key={permission} value={permission}> {permission} @@ -422,6 +461,7 @@ export class SharingManager extends React.Component<{}> { this.selectedUsers.forEach(user => { if (user.value.includes(indType)) { this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions); + } else { this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions); } @@ -466,6 +506,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) @@ -505,13 +546,48 @@ export class SharingManager extends React.Component<{}> { // users in common between all docs const commonKeys = intersection(...docs.map(doc => (this.layoutDocAcls ? doc : doc[DataSym])).map(doc => doc?.[AclSym] && Object.keys(doc[AclSym]))); + // including all users on dashboard + // SharingManager.Instance.users.forEach(eachUser => { + // var userOnDashboard = true; + // if(Doc.ActiveDashboard){ + // if(Doc.ActiveDashboard['acl-'+normalizeEmail(eachUser.user.email)]=='' || Doc.ActiveDashboard['acl-'+normalizeEmail(eachUser.user.email)]==undefined){ + // userOnDashboard = false; + // } + // } + // if (userOnDashboard && !commonKeys.includes(`acl-${normalizeEmail(eachUser.user.email)}`)){ + // users.push(eachUser.user); + // } + // }); + + // 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 dashboardList = SelectionManager.Views().length < 2 ? [targetDoc] : SelectionManager.Views().map(docView => docView.props.Document); + // const dashboard = dashboardList[0] + const dashboard = Doc.ActiveDashboard + var docToUse = dashboard + + // const tabs = DocListCast(dashboard?.data) + // tabs.forEach(tab => { + // if (tab.title == targetDoc.title){ + // docToUse = tab + // } + // var newdocsList = DocListCast(tab.data) + // newdocsList.forEach(newDoc => { + // if (newDoc.title == targetDoc.title){ + // docToUse = newDoc + // } + // }) + // }) + + docToUse = Doc.GetProto(this.targetDoc!) + const userKey = `acl-${normalizeEmail(user.email)}`; const uniform = docs.map(doc => (this.layoutDocAcls ? doc : doc[DataSym])).every(doc => doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey]); - const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; + const permissions = uniform ? StrCast(docToUse?.[userKey]) : '-multiple-'; return !permissions ? null : ( <div key={userKey} className={'container'}> @@ -551,7 +627,7 @@ export class SharingManager extends React.Component<{}> { </div> ) : null ); - + // the list of groups shared with const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true)); groupListMap.unshift({ title: 'Public' }); //, { title: "ALL" }); @@ -560,11 +636,27 @@ export class SharingManager extends React.Component<{}> { const uniform = docs .map(doc => (this.layoutDocAcls ? doc : doc[DataSym])) .every(doc => (this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey])); - const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-'; + const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-'; return !permissions ? null : ( <div key={groupKey} className={'container'}> - <div className={'padding'}>{StrCast(group.title)}</div> + <div className={'padding'} >{StrCast(group.title)}</div> + <div> + {StrCast(group.title)==='Public' ? ( + <div title={"Copy Public URL"} onClick={this.copyURL}> + {/* <IconButton + size={Size.SMALL} + isCircle={true} + hoverStyle="gray" + onClick={() => this.copyURL} + icon={<FontAwesomeIcon icon="copy" />} + /> */} + <FontAwesomeIcon icon={'copy'} size={'sm'} /> + </div> + ) : ( + <div/> + )} + </div> {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' }} /> diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index b2b18b245..3e0a1f7a4 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -6,12 +6,12 @@ import { action, computed, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { FaUndo } from 'react-icons/fa'; import { DateField } from '../../fields/DateField'; -import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from '../../fields/Doc'; +import { AclAdmin, AclAugment, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from '../../fields/Doc'; import { Document } from '../../fields/documentSchemas'; import { InkField } from '../../fields/InkField'; import { ScriptField } from '../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../fields/Types'; -import { GetEffectiveAcl, SharingPermissions } from '../../fields/util'; +import { GetEffectiveAcl, SharingPermissions, normalizeEmail } from '../../fields/util'; import { emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils'; import { Docs } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; @@ -157,27 +157,35 @@ 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 effectiveAcl = GetEffectiveAcl(first.rootDoc) + if (effectiveAcl==AclAdmin || effectiveAcl==AclEdit || effectiveAcl==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 effectiveAcl = GetEffectiveAcl(first.rootDoc) + if (effectiveAcl==AclAdmin || effectiveAcl==AclEdit || effectiveAcl==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); @@ -485,6 +493,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 = GetEffectiveAcl(first.rootDoc) + if (!(effectiveAcl==AclAdmin || effectiveAcl==AclEdit || effectiveAcl==AclAugment)) return false; if (!first) return false; let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY }; var fixedAspect = Doc.NativeAspect(first.layoutDoc); @@ -737,7 +747,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } // sharing - const docShareMode = seldocview.rootDoc["acl-Public"]; + const docShareMode = Doc.GetProto(seldocview.rootDoc)["acl-Public"]; const shareMode = StrCast(docShareMode); var shareSymbolIcon = null; switch(shareMode){ @@ -781,10 +791,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P seldocview.props.hideDeleteButton || seldocview.rootDoc.hideDeleteButton || SelectionManager.Views().some(docView => { - const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit; - return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin); + // const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit; + // return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin); + const effectiveAcl = GetEffectiveAcl(Doc.GetProto(seldocview.rootDoc)) + return docView.rootDoc.stayInCollection || (effectiveAcl !== AclAdmin && effectiveAcl !== 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 @@ -852,7 +863,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P <div className={'documentDecorations-share'}> <div className={`documentDecorations-share${shareMode}`}> <span>{shareSymbolIcon + " " + shareMode}</span> - {/* <span>{shareMode}</span> */} </div> </div> ) : (<div/> ); diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss index 3edc7fea8..50bf6ce74 100644 --- a/src/client/views/PropertiesView.scss +++ b/src/client/views/PropertiesView.scss @@ -384,17 +384,14 @@ 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; width: 92%; .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 { @@ -429,7 +426,7 @@ background: inherit; border: none; background: inherit; - width: 87px; + // width: 100%; text-align: justify; // for Edge text-align-last: end; &:hover { diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index dfc43e6c8..a6a5347ad 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -13,7 +13,7 @@ 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 { denormalizeEmail, GetEffectiveAcl, normalizeEmail, SharingPermissions } from '../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { DocumentManager } from '../util/DocumentManager'; @@ -34,6 +34,7 @@ import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector import { PropertiesDocContextSelector } from './PropertiesDocContextSelector'; import './PropertiesView.scss'; import { DefaultStyleProvider } from './StyleProvider'; +import { SourceMapDevToolPlugin } from 'webpack'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -366,11 +367,12 @@ 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)[DataSym])); if (user=="Public"){ - this.selectedDoc!['acl-' +user] = e.currentTarget.value as SharingPermissions; + docs[0]['acl-' +user] = e.currentTarget.value as SharingPermissions; } else{ - 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)[DataSym])); + SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs); } }; @@ -381,15 +383,17 @@ 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); + // dropdownValues.splice(dropdownValues.indexOf(SharingPermissions.Unset), 1); + } return ( <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 => permission != SharingPermissions.SelfEdit && (!Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any))) .map(permission => ( <option className="propertiesView-permisssions-select" key={permission} value={permission}> - {' '} - {permission}{' '} + {' '}{permission}{' '} </option> ))} </select> @@ -432,6 +436,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" @@ -445,7 +452,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { {/* {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} + {(permission === 'Owner' && name=='Me')|| showExpansionIcon ? this.expansionIcon : null} </div> </div> ); @@ -470,6 +477,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { dropDownText = "▲ "; break; } + return ( <div> <div className={'propertiesView-shareDropDown'}> @@ -490,7 +498,24 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { */ @computed get sharingTable() { // const docToUse = this.selectedDocumentView?.rootDoc; + + // const dashboard = Doc.ActiveDashboard + // var docToUse = dashboard + // const tabs = DocListCast(dashboard?.data) + // tabs.forEach(tab => { + // if (tab.title == this.selectedDoc?.title){ + // docToUse = tab + // } + // var newdocsList = DocListCast(tab.data) + // newdocsList.forEach(newDoc => { + // if (newDoc.title == this.selectedDoc?.title){ + // docToUse = newDoc + // } + // }) + // }) + const docToUse = this.selectedDoc; + if (!docToUse){ return null; } @@ -501,44 +526,69 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { 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 effectiveAcls = docs.map(doc => GetEffectiveAcl(doc)); + // const effectiveAcls = GetEffectiveAcl(docToUse) + const effectiveAcls = GetEffectiveAcl(target) + + // const showAdmin = effectiveAcls.every(acl => acl === AclAdmin); + const showAdmin= effectiveAcls==AclAdmin || docToUse!['acl-'+normalizeEmail(Doc.CurrentUserEmail)]=='Owner'; // users in common between all docs const commonKeys: string[] = intersection(...docs.map(doc => doc?.[AclSym] && Object.keys(doc[AclSym]).filter(key => key !== 'acl-Me'))); const tableEntries = []; + const usersAdded: string[] = []; // all shared users being added - organized by denormalized email + const ownerSame = Doc.CurrentUserEmail !== target.author && docs.filter(doc => doc).every(doc => doc.author === docs[0].author); + + usersAdded.push(target.author); + SharingManager.Instance.users.forEach(eachUser => { + var userOnDashboard = true; + var permission = StrCast(target[`acl-${normalizeEmail(eachUser.user.email)}`]) + if(Doc.ActiveDashboard){ + if(Doc.ActiveDashboard['acl-'+normalizeEmail(eachUser.user.email)]=='' || Doc.ActiveDashboard['acl-'+normalizeEmail(eachUser.user.email)]==undefined){ + userOnDashboard = false; + } + } + if (userOnDashboard && !usersAdded.includes(eachUser.user.email) && eachUser.user.email!='Public'){ + tableEntries.unshift(this.sharingItem(eachUser.user.email, showAdmin, permission, false)); // adds each user + usersAdded.push(eachUser.user.email); + } + }); - // 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?.[AclSym]?.[key] === docs[0]?.[AclSym]?.[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[AclSym][key])!.name : '-multiple-')); + // commonKeys.forEach(user => { + // const userEmail = user.slice(4) + // var userOnDashboard = true; + // if(Doc.ActiveDashboard){ + // if(Doc.ActiveDashboard[user]=='' || Doc.ActiveDashboard[user]==undefined){ + // userOnDashboard = false; // } // } - // } - 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(target.author, showAdmin, 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 - // ) - // ); - - SharingManager.Instance.users.forEach(eachUser => tableEntries.unshift(this.sharingItem(eachUser.user.email, false, "test", false))); - - docs.map(doc => tableEntries.unshift(this.sharingItem(doc.author, showAdmin, 'Owner', true))); + // if (userOnDashboard && !usersAdded.includes(denormalizeEmail(userEmail)) && userEmail!='Public'){ + // tableEntries.unshift(this.sharingItem(denormalizeEmail(userEmail), showAdmin, StrCast(docToUse![`acl-${normalizeEmail((userEmail))}`]), false)); // adds each user + // usersAdded.push(denormalizeEmail(userEmail)); + // } + // }) + + // current user + const userEmail = Doc.CurrentUserEmail + var userOnDashboard = true; + if(Doc.ActiveDashboard){ + if(Doc.ActiveDashboard['acl-'+normalizeEmail(userEmail)]=='' || Doc.ActiveDashboard['acl-'+normalizeEmail(userEmail)]==undefined){ + userOnDashboard = false; + } + } + if (userOnDashboard && !usersAdded.includes(denormalizeEmail(userEmail)) && userEmail!='Public'){ + tableEntries.unshift(this.sharingItem(denormalizeEmail(userEmail), showAdmin, StrCast(target[`acl-${normalizeEmail((userEmail))}`]), false)); // adds each user + usersAdded.push(denormalizeEmail(userEmail)); + } + + // if (ownerSame ) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, 'Owner'), false); // shift owner to top + tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, 'Owner'), false); // shift owner to top return ( - <div> Sharing Mode - <div>{this.publicACLDropDown(true, StrCast(docToUse['acl-Public']), false)}</div> + <div > Sharing Mode + <div>{ + this.publicACLDropDown(true, StrCast(target['acl-Public']), false)} + </div> <div> <br></br> Who has access to the Dashboard? </div> <div className="propertiesView-sharingTable">{ <div> {tableEntries}</div> diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index a2c1195cb..0e4330fa5 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -19,7 +19,7 @@ import { RichTextField } from '../../../../fields/RichTextField'; import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { ComputedField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; +import { GetEffectiveAcl, normalizeEmail, TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from '../../../DocServer'; @@ -295,11 +295,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const curProto = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); - const effectiveAcl = GetEffectiveAcl(dataDoc); - + // const effectiveAcl = GetEffectiveAcl(dataDoc); + const effectiveAcl = GetEffectiveAcl(this.rootDoc); + 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('#')) { @@ -1689,7 +1690,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps e.stopPropagation(); for (var i = state.selection.from; i <= state.selection.to; i++) { const node = state.doc.resolve(i); - if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) { + if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) { e.preventDefault(); } } @@ -1708,7 +1709,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps default: if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break; case ' ': - [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) && + if (e.code == "Space"){ + break; + } + [AclEdit, AclAugment, AclSelfEdit, 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) }))); } this.startUndoTypingBatch(); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 4d82551db..5e2e40979 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -135,7 +135,6 @@ export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol }> // this recursively updates all protos as well. export function updateCachedAcls(doc: Doc) { if (!doc) return; - const target = (doc as any)?.__fields ?? doc; const permissions: { [key: string]: symbol } = !target.author || target.author === Doc.CurrentUserEmail ? { 'acl-Me': AclAdmin } : {}; Object.keys(target).filter(key => key.startsWith('acl') && (permissions[key] = ReverseHierarchyMap.get(StrCast(target[key]))!.acl)); @@ -1117,7 +1116,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); } } @@ -1295,7 +1294,9 @@ export namespace Doc { } // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message) export function IsBrushedDegreeUnmemoized(doc: Doc) { - if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return DocBrushStatus.unbrushed; + if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) { + return DocBrushStatus.unbrushed; + } const status = brushManager.BrushedDoc.has(doc) ? DocBrushStatus.selfBrushed : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed; if (status === DocBrushStatus.unbrushed) { const lastBrushed = Array.from(brushManager.BrushedDoc.keys()).lastElement(); diff --git a/src/fields/util.ts b/src/fields/util.ts index eca4d1351..c1e1a7111 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -9,9 +9,11 @@ import { returnZero } from '../Utils'; import CursorField from './CursorField'; import { AclAdmin, + AclAugment, AclEdit, aclLevel, AclPrivate, + AclReadonly, AclSelfEdit, AclSym, DataSym, @@ -37,6 +39,8 @@ import { RichTextField } from './RichTextField'; import { SchemaHeaderField } from './SchemaHeaderField'; import { ComputedField } from './ScriptField'; import { ScriptCast, StrCast } from './Types'; +import { SharingManager } from '../client/util/SharingManager'; +import { PropertiesView } from '../client/views/PropertiesView'; function _readOnlySetter(): never { throw new Error("Documents can't be modified in read-only mode"); @@ -82,8 +86,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 || (effectiveAcl === AclSelfEdit && value instanceof RichTextField)) && !DocServer.Control.isReadOnly(); if (writeToDoc) { if (value === undefined) { @@ -139,13 +143,13 @@ 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); } + return; } /** @@ -182,6 +186,7 @@ const getEffectiveAclCache = computedFn(function (target: any, user?: string) { * Calculates the effective access right to a document for the current user. */ export function GetEffectiveAcl(target: any, user?: string): symbol { + target = Doc.GetProto(target) 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) @@ -205,8 +210,24 @@ export function SetCachedGroups(groups: string[]) { } function getEffectiveAcl(target: any, user?: string): symbol { const targetAcls = target[AclSym]; - if (targetAcls?.['acl-Me'] === AclAdmin || GetCachedGroupByName('Admin')) return AclAdmin; - + if (targetAcls?.['acl-Me'] === AclAdmin || GetCachedGroupByName(SharingPermissions.Admin)) return AclAdmin; + if (target['acl-'+normalizeEmail(Doc.CurrentUserEmail)]){ + if (target['acl-'+normalizeEmail(Doc.CurrentUserEmail)] == SharingPermissions.Admin){ + return AclAdmin + } + if (target['acl-'+normalizeEmail(Doc.CurrentUserEmail)] == SharingPermissions.Edit){ + return AclEdit + } + if (target['acl-'+normalizeEmail(Doc.CurrentUserEmail)] == SharingPermissions.Augment){ + return AclAugment + } + if (target['acl-'+normalizeEmail(Doc.CurrentUserEmail)] == SharingPermissions.View){ + return AclReadonly + } + if (target['acl-'+normalizeEmail(Doc.CurrentUserEmail)] == SharingPermissions.None){ + return AclPrivate + } + } 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) { let effectiveAcl = AclPrivate; @@ -225,13 +246,22 @@ function getEffectiveAcl(target: any, user?: string): symbol { //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. const targetAuthor = target.__fields?.author || target.author; // target may be a Doc of Proxy, so check __fields.author and .author if (targetAuthor && targetAuthor !== userChecked) return AclPrivate; return AclAdmin; + let acl = AclPrivate + if (user){ + acl = target['acl-'+user] + } + else{ + acl = target['acl-'+normalizeEmail(Doc.CurrentUserEmail)] + } + console.log(target['acl-'+normalizeEmail(Doc.CurrentUserEmail)]) + return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(acl)!.level < aclLevel.editable ? AclEdit : acl; } /** * Recursively distributes the access right for a user across the children of a document and its annotations. @@ -247,9 +277,11 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc if ((target._viewType === CollectionViewType.Docking && visited.length > 1) || Doc.GetProto(visited[0]) !== Doc.GetProto(target)) { target[key] = acl; + Doc.GetProto(target)[key] = acl if (target !== Doc.GetProto(target)) { //apparently we can't call updateCachedAcls twice (once for the main dashboard, and again for the nested dashboard...???) updateCachedAcls(target); + } return; } @@ -260,7 +292,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)); @@ -299,7 +330,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 && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) 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; |