diff options
Diffstat (limited to 'src/client/util')
| -rw-r--r-- | src/client/util/CurrentUserUtils.ts | 18 | ||||
| -rw-r--r-- | src/client/util/DocumentManager.ts | 2 | ||||
| -rw-r--r-- | src/client/util/GroupManager.scss | 6 | ||||
| -rw-r--r-- | src/client/util/LinkFollower.ts | 2 | ||||
| -rw-r--r-- | src/client/util/LinkManager.ts | 65 | ||||
| -rw-r--r-- | src/client/util/RTFMarkup.tsx | 2 | ||||
| -rw-r--r-- | src/client/util/ScriptManager.ts | 38 | ||||
| -rw-r--r-- | src/client/util/SharingManager.scss | 162 | ||||
| -rw-r--r-- | src/client/util/SharingManager.tsx | 390 | ||||
| -rw-r--r-- | src/client/util/UndoManager.ts | 9 |
10 files changed, 405 insertions, 289 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index cde3daf5a..4481933d5 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -265,7 +265,7 @@ export class CurrentUserUtils { }[] = [ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }}, {key: "Flashcard", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}}, - {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, }}, + {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 300, _height: 35, }}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}}, {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }}, {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }}, @@ -767,7 +767,7 @@ export class CurrentUserUtils { linkDocs.title = "LINK DATABASE: " + Doc.CurrentUserEmail; linkDocs.author = Doc.CurrentUserEmail; linkDocs.data = new List<Doc>([]); - linkDocs["acl-Public"] = SharingPermissions.Augment; + linkDocs["acl-Guest"] = SharingPermissions.Augment; doc.myLinkDatabase = new PrefetchProxy(linkDocs); } } @@ -786,11 +786,11 @@ export class CurrentUserUtils { title: "My Shared Docs", userColor: "rgb(202, 202, 202)", isFolder:true, undoIgnoreFields:new List<string>(['treeViewSortCriterion']), - childContextMenuFilters: new List<ScriptField>([dashboardFilter!,]), - childContextMenuScripts: new List<ScriptField>([addToDashboards!,]), - childContextMenuLabels: new List<string>(["Add to Dashboards",]), - childContextMenuIcons: new List<string>(["user-plus",]), - "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, + // childContextMenuFilters: new List<ScriptField>([dashboardFilter!,]), + // childContextMenuScripts: new List<ScriptField>([addToDashboards!,]), + // childContextMenuLabels: new List<string>(["Add to Dashboards",]), + // childContextMenuIcons: new List<string>(["user-plus",]), + "acl-Guest": SharingPermissions.Augment, "_acl-Guest": SharingPermissions.Augment, childDragAction: "embed", isSystem: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true, // NOTE: treeViewHideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar _layout_showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, layout_boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true, @@ -798,6 +798,7 @@ export class CurrentUserUtils { }; DocUtils.AssignDocField(doc, "mySharedDocs", opts => Docs.Create.TreeDocument([], opts, sharingDocumentId + "layout", sharingDocumentId), sharedDocOpts, undefined, sharedScripts); + if (!DocCast(doc.mySharedDocs).data_dashboards) DocCast(doc.mySharedDocs).data_dashboards = new List<Doc>(); } /// Import option on the left side button panel @@ -825,7 +826,7 @@ export class CurrentUserUtils { async () => { const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data); const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || []; - SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]); + SetCachedGroups(["Guest", ...mygroups?.map(g => StrCast(g.title))]); }, { fireImmediately: true }); doc.isSystem ?? (doc.isSystem = true); doc.title ?? (doc.title = Doc.CurrentUserEmail); @@ -856,6 +857,7 @@ export class CurrentUserUtils { doc.userTheme ?? (doc.userTheme = ColorScheme.Dark); doc.filterDocCount = 0; doc.treeViewFreezeChildren = "remove|add"; + doc.activePage = doc.activeDashboard === undefined ? 'home': doc.activePage; this.setupLinkDocs(doc, linkDatabaseId); this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 3f0848d00..b921b3116 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -194,7 +194,7 @@ export class DocumentManager { var containerDocContext = srcContext ? [srcContext, doc] : [doc]; while ( containerDocContext.length && - containerDocContext[0]?.embedContainer && + DocCast(containerDocContext[0]?.embedContainer) && DocCast(containerDocContext[0].embedContainer)?._type_collection !== CollectionViewType.Docking && (includeExistingViews || !DocumentManager.Instance.getDocumentView(containerDocContext[0])) ) { diff --git a/src/client/util/GroupManager.scss b/src/client/util/GroupManager.scss index 70fa88f6d..253ed5d2a 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/LinkFollower.ts b/src/client/util/LinkFollower.ts index f74409e42..3e526c4c0 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -103,7 +103,7 @@ export class LinkFollower { Doc.AddDocToList(sourceDocParent, Doc.LayoutFieldKey(sourceDocParent), target); movedTarget = true; } - target.embedContainer = sourceDocParent; + Doc.SetContainer(target, sourceDocParent); const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)]; if (sourceDoc.followLinkXoffset !== undefined && moveTo[0] !== target.x) { target.x = moveTo[0]; diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index ce422f849..c7f092565 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -46,38 +46,41 @@ export class LinkManager { LinkManager._instance = this; this.createlink_relationshipLists(); LinkManager.userLinkDBs = []; - const addLinkToDoc = (link: Doc) => { - Promise.all([link]).then(linkdoc => { - const link = DocCast(linkdoc[0]); - Promise.all([link.proto]).then(linkproto => { - Promise.all([link.link_anchor_1, link.link_anchor_2]).then(linkdata => { - const a1 = DocCast(linkdata[0]); - const a2 = DocCast(linkdata[1]); - a1 && - a2 && - Promise.all([Doc.GetProto(a1), Doc.GetProto(a2)]).then( - action(protos => { - (protos[0] as Doc)?.[DirectLinks].add(link); - (protos[1] as Doc)?.[DirectLinks].add(link); + // since this is an action, not a reaction, we get only one shot to add this link to the Anchor docs + // Thus make sure all promised values are resolved from link -> link.proto -> link.link_anchor_[1,2] -> link.link_anchor_[1,2].proto + // Then add the link to the anchor protos. + const addLinkToDoc = (lprom: Doc) => + PromiseValue(lprom).then((link: Opt<Doc>) => + PromiseValue(link?.proto as Doc).then((lproto: Opt<Doc>) => + Promise.all([lproto?.link_anchor_1 as Doc, lproto?.link_anchor_2 as Doc].map(PromiseValue)).then((lAnchs: Opt<Doc>[]) => + Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt<Doc>[]) => + Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then( + action(lAnchProtoProtos => { + link && lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].add(link); + link && lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].add(link); }) - ); - }); - }); - }); - }; - const remLinkFromDoc = (link: Doc) => { - const a1 = link?.link_anchor_1; - const a2 = link?.link_anchor_2; - Promise.all([a1, a2]).then( - action(() => { - if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) { - Doc.GetProto(a1)[DirectLinks].delete(link); - Doc.GetProto(a2)[DirectLinks].delete(link); - Doc.GetProto(link)[DirectLinks].delete(link); - } - }) + ) + ) + ) + ) ); - }; + + const remLinkFromDoc = (lprom: Doc) => + PromiseValue(lprom).then((link: Opt<Doc>) => + PromiseValue(link?.proto as Doc).then((lproto: Opt<Doc>) => + Promise.all([lproto?.link_anchor_1 as Doc, lproto?.link_anchor_2 as Doc].map(PromiseValue)).then((lAnchs: Opt<Doc>[]) => + Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt<Doc>[]) => + Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then( + action(lAnchProtoProtos => { + link && lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].delete(link); + link && lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].delete(link); + }) + ) + ) + ) + ) + ); + const watchUserLinkDB = (userLinkDBDoc: Doc) => { LinkManager._links.push(...DocListCast(userLinkDBDoc.data)); const toRealField = (field: Field) => (field instanceof ProxyField ? field.value : field); // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields @@ -156,7 +159,7 @@ export class LinkManager { return this.relatedLinker(anchor); } // finds all links that contain the given anchor public getAllDirectLinks(anchor: Doc): Doc[] { - return Array.from(Doc.GetProto(anchor)[DirectLinks] ?? []); + return Array.from(Doc.GetProto(anchor)[DirectLinks]); } // finds all links that contain the given anchor relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] { diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx index 247267710..b93d4f293 100644 --- a/src/client/util/RTFMarkup.tsx +++ b/src/client/util/RTFMarkup.tsx @@ -52,7 +52,7 @@ export class RTFMarkup extends React.Component<{}> { {` add a sidebar text document inline`} </p> <p> - <b style={{ fontSize: 'larger' }}>{`\`\` `}</b> + <b style={{ fontSize: 'larger' }}>{`\`\`\` `}</b> {` create a code snippet block`} </p> <p> diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts index 42a6493ea..87509f2ea 100644 --- a/src/client/util/ScriptManager.ts +++ b/src/client/util/ScriptManager.ts @@ -1,12 +1,11 @@ -import { Doc, DocListCast } from "../../fields/Doc"; -import { List } from "../../fields/List"; -import { listSpec } from "../../fields/Schema"; -import { Cast, StrCast } from "../../fields/Types"; -import { Docs } from "../documents/Documents"; -import { ScriptingGlobals } from "./ScriptingGlobals"; +import { Doc, DocListCast } from '../../fields/Doc'; +import { List } from '../../fields/List'; +import { listSpec } from '../../fields/Schema'; +import { Cast, StrCast } from '../../fields/Types'; +import { Docs } from '../documents/Documents'; +import { ScriptingGlobals } from './ScriptingGlobals'; export class ScriptManager { - static _initialized = false; private static _instance: ScriptManager; public static get Instance(): ScriptManager { @@ -24,11 +23,7 @@ export class ScriptManager { } public getAllScripts(): Doc[] { const sdoc = ScriptManager.Instance.ScriptManagerDoc; - if (sdoc) { - const docs = DocListCast(sdoc.data); - return docs; - } - return []; + return sdoc ? DocListCast(sdoc.data) : []; } public addScript(scriptDoc: Doc): boolean { @@ -59,36 +54,35 @@ export class ScriptManager { } public static addScriptToGlobals(scriptDoc: Doc): void { - ScriptingGlobals.removeGlobal(StrCast(scriptDoc.name)); - const params = Cast(scriptDoc["data-params"], listSpec("string"), []); + const params = Cast(scriptDoc['data-params'], listSpec('string'), []); const paramNames = params.reduce((o: string, p: string) => { if (params.indexOf(p) === params.length - 1) { - o = o + p.split(":")[0].trim(); + o = o + p.split(':')[0].trim(); } else { - o = o + p.split(":")[0].trim() + ","; + o = o + p.split(':')[0].trim() + ','; } return o; - }, "" as string); + }, '' as string); const f = new Function(paramNames, StrCast(scriptDoc.script)); Object.defineProperty(f, 'name', { value: StrCast(scriptDoc.name), writable: false }); - let parameters = "("; + let parameters = '('; params.forEach((element: string, i: number) => { if (i === params.length - 1) { - parameters = parameters + element + ")"; + parameters = parameters + element + ')'; } else { - parameters = parameters + element + ", "; + parameters = parameters + element + ', '; } }); - if (parameters === "(") { + if (parameters === '(') { ScriptingGlobals.add(f, StrCast(scriptDoc.description)); } else { ScriptingGlobals.add(f, StrCast(scriptDoc.description), parameters); } } -}
\ No newline at end of file +} diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss index 1be27fa62..05401e2cd 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 } @@ -21,7 +21,30 @@ top: 2px; } + .share-title { + display: inline-flex; + gap: 5px; + + .share-info { + align-self: center; + cursor: pointer; + } + } + + .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; @@ -42,11 +65,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; } } @@ -74,15 +101,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 { @@ -100,6 +128,7 @@ .group-container { width: 50%; display: flex; + top:0; flex-direction: column; .user-sort { @@ -118,9 +147,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; @@ -188,54 +218,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..8e5ae7bc0 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -1,15 +1,15 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { intersection } from 'lodash'; +import { Colors } 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, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc'; +import { AclAdmin, AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; -import { List } from '../../fields/List'; -import { NumCast, StrCast } from '../../fields/Types'; +import { StrCast } from '../../fields/Types'; import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util'; import { Utils } from '../../Utils'; import { DocServer } from '../DocServer'; @@ -21,8 +21,10 @@ import { SearchBox } from '../views/search/SearchBox'; import { DocumentManager } from './DocumentManager'; import { GroupManager, UserOptions } from './GroupManager'; import { GroupMemberView } from './GroupMemberView'; +import { LinkManager } from './LinkManager'; import { SelectionManager } from './SelectionManager'; import './SharingManager.scss'; +import { undoable } from './UndoManager'; export interface User { email: string; @@ -47,6 +49,7 @@ const indType = '!indType/'; const groupType = '!groupType/'; const storage = 'data'; +const dashStorage = 'data_dashboards'; /** * A user who also has a sharing doc. @@ -73,13 +76,14 @@ export class SharingManager extends React.Component<{}> { @observable private individualSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of individuals @observable private groupSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of groups private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup - private distributeAclsButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the distribute button, used for the position of the popup // if both showUserOptions and showGroupOptions are false then both are displayed @observable private showUserOptions: boolean = false; // whether to show individuals as options when sharing (in the react-select component) @observable private showGroupOptions: boolean = false; // // whether to show groups as options when sharing (in the react-select component) private populating: boolean = false; // whether the list of users is populating or not + @observable private overrideNested: boolean = false; // whether child docs in a collection/dashboard should be changed to be less private - initially selected so default is override @observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used @observable private myDocAcls: boolean = false; // whether the My Docs checkbox is selected or not + @observable private _buttonDown = false; // private get linkVisible() { // return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false; @@ -93,6 +97,7 @@ export class SharingManager extends React.Component<{}> { DictationOverlay.Instance.hasActiveModal = true; this.isOpen = this.targetDoc !== undefined; this.permissions = SharingPermissions.Augment; + this.overrideNested = true; }); }; @@ -108,6 +113,7 @@ export class SharingManager extends React.Component<{}> { }), 500 ); + this.layoutDocAcls = false; }); constructor(props: {}) { @@ -138,7 +144,7 @@ export class SharingManager extends React.Component<{}> { if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) { if (!this.users.find(user => user.user.email === newUser.email)) { this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) }); - // LinkManager.addLinkDB(sharer.linkDatabase); + LinkManager.addLinkDB(linkDatabase); } } }) @@ -150,79 +156,44 @@ export class SharingManager extends React.Component<{}> { /** * Shares the document with a user. */ - setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc?: Doc) => { + setInternalSharing = undoable((recipient: ValidatedUser, permission: string, targetDoc: Doc | undefined) => { const { user, sharingDoc } = recipient; const target = targetDoc || this.targetDoc!; const acl = `acl-${normalizeEmail(user.email)}`; - const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`; - const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1; - - const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); - return !docs - .map(doc => (this.layoutDocAcls ? doc : doc[DocData])) - .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; - } - - distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard); - - this.setDashboardBackground(doc, permission as SharingPermissions); - if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); - return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc); - }) - .some(success => !success); - }; + const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc); + docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => { + distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.overrideNested ? true : undefined); + if (permission !== SharingPermissions.None) { + Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc); + } else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc); + }); + }, 'set Doc permissions'); /** * Sets the permission on the target for the group. * @param group * @param permission */ - setInternalGroupSharing = (group: Doc | { title: string }, permission: string, targetDoc?: Doc) => { + setInternalGroupSharing = undoable((group: Doc | { title: string }, permission: string, targetDoc?: Doc) => { const target = targetDoc || this.targetDoc!; - const key = normalizeEmail(StrCast(group.title)); - 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); - - // ! ensures it returns true if document has been shared successfully, false otherwise - return !docs - .map(doc => (this.layoutDocAcls ? doc : doc[DocData])) - .map(doc => { - doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc, undefined, undefined, isDashboard); - - if (permission === SharingPermissions.None) { - if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 1) - 1; - } else { - if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 0) + 1; - } + const acl = `acl-${normalizeEmail(StrCast(group.title))}`; - distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard); - this.setDashboardBackground(doc, permission as SharingPermissions); + const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc); + docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => { + distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.overrideNested ? true : undefined); - if (group instanceof Doc) { - const members: string[] = JSON.parse(StrCast(group.members)); - const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); + if (group instanceof Doc) { + Doc.AddDocToList(group, 'docsShared', doc); - // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc - group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(doc) : (group.docsShared = new List<Doc>([doc])); - - return users - .map(({ user, sharingDoc }) => { - if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added - else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists - }) - .some(success => !success); - } - }) - .some(success => success); - }; + this.users + .filter(({ user: { email } }) => JSON.parse(StrCast(group.members)).includes(email)) + .forEach(({ user, sharingDoc }) => { + if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc); // add the doc to the sharingDoc if it hasn't already been added + else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists + }); + } + }); + }, 'set group permissions'); /** * Shares the documents shared with a group with a new user who has been added to that group. @@ -237,7 +208,13 @@ export class SharingManager extends React.Component<{}> { else { DocListCastAsync(user.sharingDoc[storage]).then(userdocs => DocListCastAsync(group.docsShared).then(dl => { - const filtered = dl?.filter(doc => !userdocs?.includes(doc)); + const filtered = dl?.filter(doc => !doc.dockingConfig && !userdocs?.includes(doc)); + filtered && userdocs?.push(...filtered); + }) + ); + DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs => + DocListCastAsync(group.docsShared).then(dl => { + const filtered = dl?.filter(doc => doc.dockingConfig && !userdocs?.includes(doc)); filtered && userdocs?.push(...filtered); }) ); @@ -248,44 +225,23 @@ export class SharingManager extends React.Component<{}> { /** * Called from the properties sidebar to change permissions of a user. */ - shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, docs: Doc[]) => { - if (shareWith !== 'Public' && shareWith !== 'Override') { + shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => { + if (layout) this.layoutDocAcls = true; + if (shareWith !== 'Guest') { const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? Doc.CurrentUserEmail : shareWith)); docs.forEach(doc => { if (user) this.setInternalSharing(user, permission, doc); - else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc); + else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc, undefined, true); }); } else { - 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); - this.setDashboardBackground(doc, permission as SharingPermissions); - }); - } - }; - - /** - * Sets the background of the Dashboard if it has been shared as a visual indicator - */ - setDashboardBackground = (doc: Doc, permission: SharingPermissions) => { - if (Doc.IndexOf(doc, DocListCast(Doc.MyDashboards.data)) !== -1) { - if (permission !== SharingPermissions.None) { - doc.isShared = true; - doc.backgroundColor = 'green'; - } else { - const acls = doc[DocData][DocAcl]; - if ( - Object.keys(acls) - .filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && key !== 'acl-Me') - .every(key => [AclUnset, AclPrivate].includes(acls[key])) - ) { - doc.isShared = undefined; - doc.backgroundColor = undefined; + if (GetEffectiveAcl(doc) === AclAdmin) { + distributeAcls(`acl-${shareWith}`, permission, doc, undefined); } - } + }); } - }; + this.layoutDocAcls = false; + }, 'sidebar set permissions'); /** * Removes the documents shared with a user through a group when the user is removed from the group. @@ -295,13 +251,19 @@ export class SharingManager extends React.Component<{}> { removeMember = (group: Doc, emailId: string) => { const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!; - if (group.docsShared) { + if (group.docsShared && user) { DocListCastAsync(user.sharingDoc[storage]).then(userdocs => DocListCastAsync(group.docsShared).then(dl => { const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || []; userdocs?.splice(0, userdocs.length, ...remaining); }) ); + DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs => + DocListCastAsync(group.docsShared).then(dl => { + const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || []; + userdocs?.splice(0, userdocs.length, ...remaining); + }) + ); } }; @@ -311,11 +273,9 @@ export class SharingManager extends React.Component<{}> { */ removeGroup = (group: Doc) => { if (group.docsShared) { - const dashboards = DocListCast(Doc.MyDashboards.data); DocListCast(group.docsShared).forEach(doc => { const acl = `acl-${StrCast(group.title)}`; - const isDashboard = dashboards.indexOf(doc) !== -1; - distributeAcls(acl, SharingPermissions.None, doc, undefined, undefined, isDashboard); + distributeAcls(acl, SharingPermissions.None, doc); const members: string[] = JSON.parse(StrCast(group.members)); const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); @@ -331,37 +291,26 @@ 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 */ - private sharingOptions(uniform: boolean, override?: boolean) { - const dropdownValues: string[] = Object.values(SharingPermissions); + private sharingOptions(uniform: boolean, showGuestOptions?: boolean) { + const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : 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) => { @@ -406,38 +355,43 @@ export class SharingManager extends React.Component<{}> { /** * Handles changes in the permission chosen to share with someone with */ - @action - handlePermissionsChange = (event: React.ChangeEvent<HTMLSelectElement>) => { - this.permissions = event.currentTarget.value as SharingPermissions; - }; + handlePermissionsChange = undoable( + action((event: React.ChangeEvent<HTMLSelectElement>) => { + this.permissions = event.currentTarget.value as SharingPermissions; + }), + 'permission change' + ); /** * Calls the relevant method for sharing, displays the popup, and resets the relevant variables. */ - @action - share = () => { - if (this.selectedUsers) { - this.selectedUsers.forEach(user => { - if (user.value.includes(indType)) { - this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions); - } else { - this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions); - } - }); - - const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect(); - TaskCompletionBox.popupX = left - 1.5 * width; - TaskCompletionBox.popupY = top - 1.5 * height; - TaskCompletionBox.textDisplayed = 'Document shared!'; - TaskCompletionBox.taskCompleted = true; - setTimeout( - action(() => (TaskCompletionBox.taskCompleted = false)), - 2000 - ); + share = undoable( + action(() => { + if (this.selectedUsers) { + this.selectedUsers.forEach(user => { + if (user.value.includes(indType)) { + this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions, undefined); + } else { + this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions); + } + }); + + const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect(); + TaskCompletionBox.popupX = left - 1.5 * width; + TaskCompletionBox.popupY = top - 1.5 * height; + TaskCompletionBox.textDisplayed = 'Document shared!'; + TaskCompletionBox.taskCompleted = true; + setTimeout( + action(() => (TaskCompletionBox.taskCompleted = false)), + 2000 + ); - this.selectedUsers = null; - } - }; + this.layoutDocAcls = false; + this.selectedUsers = null; + } + }), + 'share Doc' + ); /** * Sorting algorithm to sort users. @@ -464,6 +418,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) @@ -485,8 +440,7 @@ export class SharingManager extends React.Component<{}> { const users = this.individualSort === 'ascending' ? this.users.slice().sort(this.sortUsers) : this.individualSort === 'descending' ? this.users.slice().sort(this.sortUsers).reverse() : this.users; const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList; - // handles the case where multiple documents are selected - let docs = SelectionManager.Views().length < 2 ? [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DocData]] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DocData])); + let docs = SelectionManager.Views().length < 2 ? [this.targetDoc] : SelectionManager.Views().map(docView => docView.rootDoc); if (this.myDocAcls) { const newDocs: Doc[] = []; @@ -501,26 +455,32 @@ export class SharingManager extends React.Component<{}> { const admin = this.myDocAcls ? Boolean(docs.length) : effectiveAcls.every(acl => acl === AclAdmin); // users in common between all docs - const commonKeys = intersection(...docs.map(doc => (this.layoutDocAcls ? doc : doc[DocData])).map(doc => doc?.[DocAcl] && Object.keys(doc[DocAcl]))); + const commonKeys = intersection(docs).reduce((list, doc) => (doc?.[DocAcl] ? [...list, ...Object.keys(doc[DocAcl])] : list), [] as string[]); // the list of users shared with - const userListContents: (JSX.Element | null)[] = users - .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email)) + const userListContents = users + // .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email)) + .filter(({ user }) => docs[0]?.author !== user.email) .map(({ user, linkDatabase, sharingDoc, userColor }) => { const userKey = `acl-${normalizeEmail(user.email)}`; - const uniform = docs.map(doc => (this.layoutDocAcls ? doc : doc[DocData])).every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]); - const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; + const uniform = docs.every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]); + // const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; + let permissions = targetDoc[DocAcl][userKey] ? HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name : StrCast(targetDoc[userKey]); + permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; return !permissions ? null : ( <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, undefined)}> {this.sharingOptions(uniform)} </select> ) : ( - <div className={'permissions-dropdown'}>{permissions}</div> + <div className={`permissions-dropdown-${permissions}`}> + {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)} + + </div> )} </div> </div> @@ -531,6 +491,9 @@ 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)}`; + const curUserPermission = StrCast(targetDoc[userKey]); + // const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name userListContents.unshift( sameAuthor ? ( <div key={'owner'} className={'container'}> @@ -544,7 +507,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-'} + + </div> </div> </div> ) : null @@ -552,29 +518,31 @@ export class SharingManager extends React.Component<{}> { // 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" }); + groupListMap.unshift({ title: 'Guest' }); //, { title: "ALL" }); const groupListContents = groupListMap.map(group => { - const groupKey = `acl-${StrCast(group.title)}`; - const uniform = docs - .map(doc => (this.layoutDocAcls ? doc : doc[DocData])) - .every(doc => (this.layoutDocAcls ? doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey] : doc?.[DocData]?.[DocAcl]?.[groupKey] === docs[0]?.[DocData]?.[DocAcl]?.[groupKey])); - const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-'; + let groupKey = `acl-${StrCast(group.title)}`; + const uniform = docs.every(doc => doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey]); + const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-'; return !permissions ? null : ( <div key={groupKey} className={'container'}> <div className={'padding'}>{StrCast(group.title)}</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' }} /> </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)}> - {this.sharingOptions(uniform, group.title === 'Override')} + <select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}> + {this.sharingOptions(uniform, group.title === 'Guest')} </select> ) : ( - <div className={'permissions-dropdown'}>{permissions}</div> + <div className={`permissions-dropdown-${permissions}`}> + {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)} + + </div> )} </div> </div> @@ -584,19 +552,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"> + <div className="share-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties/sharing-and-permissions/', '_blank')}> + <FontAwesomeIcon icon={'question-circle'} size={'sm'} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties/sharing-and-permissions/', '_blank')} /> + </div> <b>Share </b> {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')} </p> - <div className={'close-button'} onClick={this.close}> - <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} /> + <button + className="share-copy-link" + style={{ background: this._buttonDown ? Colors.LIGHT_BLUE : undefined }} + onPointerDown={action(e => (this._buttonDown = true))} + onPointerUp={action(e => (this._buttonDown = false))} + onClick={this.copyURL}> + <FontAwesomeIcon title="Copy Public URL" icon="copy" size="sm" /> + 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 +590,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 +607,41 @@ export class SharingManager extends React.Component<{}> { <div className="acl-container"> {Doc.noviceMode ? null : ( <div className="layoutDoc-acls"> + <input type="checkbox" onChange={action(() => (this.overrideNested = !this.overrideNested))} checked={this.overrideNested} /> <label>Override Nested </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 + <FontAwesomeIcon icon={this.individualSort === 'ascending' ? 'caret-up' : this.individualSort === 'descending' ? 'caret-down' : 'caret-right'} size="xs" /> + </div> </div> - <div className={'users-list'}>{userListContents}</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 + <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> + + <FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'} size="xs" /> + </div> </div> <div className={'groups-list'}>{groupListContents}</div> </div> diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index b59af6656..9a6719ea5 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -1,5 +1,6 @@ import { observable, action, runInAction } from 'mobx'; import { Field } from '../../fields/Doc'; +import { RichTextField } from '../../fields/RichTextField'; import { Without } from '../../Utils'; function getBatchName(target: any, key: string | symbol): string { @@ -96,7 +97,13 @@ export namespace UndoManager { export function AddEvent(event: UndoEvent, value?: any): void { if (currentBatch && batchCounter.get() && !undoing) { - console.log(' '.slice(0, batchCounter.get()) + 'UndoEvent : ' + event.prop + ' = ' + (value instanceof Array ? value.map(val => Field.toScriptString(val)).join(',') : Field.toScriptString(value))); + console.log( + ' '.slice(0, batchCounter.get()) + + 'UndoEvent : ' + + event.prop + + ' = ' + + (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toScriptString(val)).join(',') : Field.toScriptString(value)) + ); currentBatch.push(event); tempEvents?.push(event); } |
