From f4830de4f8c4794ec98e54be9ba8730e46155c35 Mon Sep 17 00:00:00 2001 From: usodhi <61431818+usodhi@users.noreply.github.com> Date: Mon, 6 Jul 2020 18:18:17 +0530 Subject: trying first implementation of storing acls --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index fc63dfbf5..a0dbcd980 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from " import { ReplaceStep } from 'prosemirror-transform'; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../../fields/DateField'; -import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym, AclEdit } from "../../../../fields/Doc"; import { documentSchema } from '../../../../fields/documentSchemas'; import applyDevTools = require("prosemirror-dev-tools"); import { removeMarkWithAttrs } from "./prosemirrorPatches"; @@ -24,7 +24,7 @@ import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { createSchema, makeInterface } from "../../../../fields/Schema"; import { Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../fields/Types"; -import { TraceMobx, OVERRIDE_ACL } from '../../../../fields/util'; +import { TraceMobx, OVERRIDE_ACL, getEffectiveAcl } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../../DocServer"; @@ -226,7 +226,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const curProto = Cast(Cast(this.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()); - if (!this.dataDoc[AclSym]) { + // if (!this.dataDoc[AclSym]) { // what? + console.log("here"); + if (getEffectiveAcl(this.dataDoc) === AclEdit) { if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) { this._applyingChange = true; (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); -- cgit v1.2.3-70-g09d2 From 12285b8c0aff514a2345874508c35b6de1026ef4 Mon Sep 17 00:00:00 2001 From: usodhi <61431818+usodhi@users.noreply.github.com> Date: Thu, 9 Jul 2020 12:25:46 +0530 Subject: acls now work I think + some cleanup --- src/client/DocServer.ts | 1 - src/client/util/GroupManager.tsx | 13 +++- src/client/util/GroupMemberView.tsx | 2 +- src/client/util/SharingManager.tsx | 2 - src/client/views/DocComponent.tsx | 1 - src/client/views/GlobalKeyHandler.ts | 2 + src/client/views/collections/CollectionView.tsx | 21 +++--- src/client/views/nodes/DocumentContentsView.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 22 +++--- .../views/nodes/formattedText/FormattedTextBox.tsx | 1 - src/fields/Doc.ts | 24 +------ src/fields/util.ts | 84 +++++++++++----------- 12 files changed, 84 insertions(+), 90 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 860a8fd92..bac324c77 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -40,7 +40,6 @@ export namespace DocServer { export var PlaygroundFields: string[]; export function setPlaygroundFields(livePlaygroundFields: string[]) { - console.log("here"); DocServer.PlaygroundFields = livePlaygroundFields; livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground)); } diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 83b206f94..23bdd248b 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -14,6 +14,7 @@ import Select from 'react-select'; import "./GroupManager.scss"; import { StrCast } from "../../fields/Types"; import GroupMemberView from "./GroupMemberView"; +import { setGroups } from "../../fields/util"; library.add(fa.faWindowClose); @@ -54,7 +55,7 @@ export default class GroupManager extends React.Component<{}> { if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName)); }); }) - .finally(() => console.log(this.currentUserGroups)); + .finally(() => setGroups(this.currentUserGroups)); // (this.GroupManagerDoc?.data as List).forEach(group => { // Promise.resolve(group).then(resolvedGroup => { @@ -178,6 +179,10 @@ export default class GroupManager extends React.Component<{}> { groupDoc.groupName = groupName; groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]); groupDoc.members = JSON.stringify(memberEmails); + if (memberEmails.includes(Doc.CurrentUserEmail)) { + this.currentUserGroups.push(groupName); + setGroups(this.currentUserGroups); + } this.addGroup(groupDoc); } @@ -204,6 +209,12 @@ export default class GroupManager extends React.Component<{}> { // SharingManager.Instance.setInternalGroupSharing(group, "Not Shared"); Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group); SharingManager.Instance.removeGroup(group); + const members: string[] = JSON.parse(StrCast(group.members)); + if (members.includes(Doc.CurrentUserEmail)) { + const index = this.currentUserGroups.findIndex(groupName => groupName === group.groupName); + index !== -1 && this.currentUserGroups.splice(index, 1); + setGroups(this.currentUserGroups); + } if (group === this.currentGroup) { runInAction(() => this.currentGroup = undefined); } diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx index cc279b6b2..742caa676 100644 --- a/src/client/util/GroupMemberView.tsx +++ b/src/client/util/GroupMemberView.tsx @@ -51,7 +51,7 @@ export default class GroupMemberView extends React.Component - + : null} diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index bec6b973b..d64302456 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -359,8 +359,6 @@ export default class SharingManager extends React.Component<{}> { share = () => { this.selectedUsers?.forEach(user => { if (user.value.includes(indType)) { - console.log(user); - console.log(this.users.find(u => u.user.email === user.label)); this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions); } else { diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index e8c34d931..781673e59 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -138,7 +138,6 @@ export function ViewBoxAnnotatableComponent

!docList.includes(d)); - console.log("here"); const effectiveAcl = getEffectiveAcl(this.dataDoc); if (added.length) { if (effectiveAcl === AclReadonly) { diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index a3a023164..45d53a5f5 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -22,6 +22,7 @@ import { DocumentView } from "./nodes/DocumentView"; import { DocumentLinksButton } from "./nodes/DocumentLinksButton"; import PDFMenu from "./pdf/PDFMenu"; import { ContextMenu } from "./ContextMenu"; +import GroupManager from "../util/GroupManager"; const modifiers = ["control", "meta", "shift", "alt"]; type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise; @@ -107,6 +108,7 @@ export default class KeyManager { GoogleAuthenticationManager.Instance.cancel(); HypothesisAuthenticationManager.Instance.cancel(); SharingManager.Instance.close(); + GroupManager.Instance.close(); break; case "delete": case "backspace": diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 31f0c1df3..6a6a475c8 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -8,7 +8,7 @@ import * as React from 'react'; import Lightbox from 'react-image-lightbox-with-rotate'; import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app import { DateField } from '../../../fields/DateField'; -import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt } from '../../../fields/Doc'; +import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt, AclEdit } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; @@ -132,8 +132,7 @@ export class CollectionView extends Touchable !docList.includes(d)); - console.log("here"); - const effectiveAcl = getEffectiveAcl(this.dataDoc); + const effectiveAcl = getEffectiveAcl(this.props.Document); if (added.length) { if (effectiveAcl === AclReadonly) { return false; @@ -167,13 +166,15 @@ export class CollectionView extends Touchable { - const docs = doc instanceof Doc ? [doc] : doc as Doc[]; - const targetDataDoc = this.props.Document[DataSym]; - const value = DocListCast(targetDataDoc[this.props.fieldKey]); - const result = value.filter(v => !docs.includes(v)); - if (result.length !== value.length) { - targetDataDoc[this.props.fieldKey] = new List(result); - return true; + if (getEffectiveAcl(this.props.Document) === AclEdit) { + const docs = doc instanceof Doc ? [doc] : doc as Doc[]; + const targetDataDoc = this.props.Document[DataSym]; + const value = DocListCast(targetDataDoc[this.props.fieldKey]); + const result = value.filter(v => !docs.includes(v)); + if (result.length !== value.length) { + targetDataDoc[this.props.fieldKey] = new List(result); + return true; + } } return false; } diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index d480c76d0..e34ceb994 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -184,7 +184,6 @@ export class DocumentContentsView extends React.Component 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns - console.log("here"); return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || getEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) : (Docu @undoBatch @action - setAcl = (acl: "readOnly" | "addOnly" | "ownerOnly" | "write") => { + setAcl = (acl: SharingPermissions) => { this.dataDoc.ACL = this.props.Document.ACL = acl; DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => { if (d.author === Doc.CurrentUserEmail) d.ACL = acl; @@ -735,7 +735,7 @@ export class DocumentView extends DocComponent(Docu } @undoBatch @action - testAcl = (acl: "readOnly" | "addOnly" | "ownerOnly" | "write") => { + testAcl = (acl: SharingPermissions) => { this.dataDoc.author = this.props.Document.author = "ADMIN"; this.dataDoc.ACL = this.props.Document.ACL = acl; DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => { @@ -845,12 +845,12 @@ export class DocumentView extends DocComponent(Docu const existingAcls = cm.findByDescription("Privacy..."); const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : []; - aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" }); - aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" }); - aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" }); + aclItems.push({ description: "Make Add Only", event: () => this.setAcl(SharingPermissions.Add), icon: "concierge-bell" }); + aclItems.push({ description: "Make Read Only", event: () => this.setAcl(SharingPermissions.View), icon: "concierge-bell" }); + aclItems.push({ description: "Make Private", event: () => this.setAcl(SharingPermissions.None), icon: "concierge-bell" }); + aclItems.push({ description: "Make Editable", event: () => this.setAcl(SharingPermissions.Edit), icon: "concierge-bell" }); + aclItems.push({ description: "Test Private", event: () => this.testAcl(SharingPermissions.None), icon: "concierge-bell" }); + aclItems.push({ description: "Test Readonly", event: () => this.testAcl(SharingPermissions.View), icon: "concierge-bell" }); !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" }); // const recommender_subitems: ContextMenuProps[] = []; @@ -1206,9 +1206,9 @@ export class DocumentView extends DocComponent(Docu } render() { - console.log("here"); - if (getEffectiveAcl(this.props.Document) === AclPrivate) return (null); if (!(this.props.Document instanceof Doc)) return (null); + if (getEffectiveAcl(this.props.Document) === AclPrivate) return (null); + if (this.props.Document.hidden) return (null); const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document); const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null))); const finalOpacity = this.props.opacity ? this.props.opacity() : opacity; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index a0dbcd980..ccf83cbf9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -227,7 +227,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp 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()); // if (!this.dataDoc[AclSym]) { // what? - console.log("here"); if (getEffectiveAcl(this.dataDoc) === AclEdit) { if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) { this._applyingChange = true; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index c965dc282..27eabf451 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -109,7 +109,6 @@ const AclMap = new Map([ ]); export function fetchProto(doc: Doc) { - console.log("in fetchproto"); if (doc.author !== Doc.CurrentUserEmail) { // storing acls for groups needs to be extended here - AclSym should store a datastructure that stores information about permissions const permissions: { [key: string]: symbol } = {}; @@ -118,22 +117,8 @@ export function fetchProto(doc: Doc) { if (key.startsWith("ACL")) permissions[key] = AclMap.get(StrCast(doc[key]))!; }); - doc[AclSym] = permissions; - // const acl = Doc.Get(doc, "ACL", true); - // switch (acl) { - // case "ownerOnly": - // doc[AclSym] = AclPrivate; - // return undefined; - // case "readOnly": - // doc[AclSym] = AclReadonly; - // break; - // case "addOnly": - // doc[AclSym] = AclAddonly; - // break; - // // case "edit": - // // doc[AclSym] = AclEdit; - // } + if (Object.keys(permissions).length) doc[AclSym] = permissions; } if (doc.proto instanceof Promise) { @@ -208,7 +193,7 @@ export class Doc extends RefField { private [Self] = this; private [SelfProxy]: any; - public [AclSym]: any; + public [AclSym]: { [key: string]: symbol }; public [WidthSym] = () => NumCast(this[SelfProxy]._width); public [HeightSym] = () => NumCast(this[SelfProxy]._height); public [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; } @@ -232,8 +217,8 @@ export class Doc extends RefField { return Cast(this[SelfProxy][renderFieldKey + "-layout[" + templateLayoutDoc[Id] + "]"], Doc, null) || templateLayoutDoc; } return undefined; - } + } private [CachedUpdates]: { [key: string]: () => void | Promise } = {}; public static CurrentUserEmail: string = ""; @@ -840,7 +825,6 @@ 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) { - console.log("here"); if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0; return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0; } @@ -850,7 +834,6 @@ export namespace Doc { })(doc); } export function BrushDoc(doc: Doc) { - console.log("here"); if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc; brushManager.BrushedDoc.set(doc, true); brushManager.BrushedDoc.set(Doc.GetProto(doc), true); @@ -888,7 +871,6 @@ export namespace Doc { } const highlightManager = new HighlightBrush(); export function IsHighlighted(doc: Doc) { - console.log("here"); if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return false; return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc)); } diff --git a/src/fields/util.ts b/src/fields/util.ts index a3e7a36f8..a1af1d3c5 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,5 +1,5 @@ import { UndoManager } from "../client/util/UndoManager"; -import { Doc, Field, FieldResult, UpdatingFromServer, LayoutSym, AclSym, AclPrivate, AclEdit, AclReadonly, AclAddonly } from "./Doc"; +import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, fetchProto } from "./Doc"; import { SerializationHelper } from "../client/util/SerializationHelper"; import { ProxyField, PrefetchProxy } from "./Proxy"; import { RefField } from "./RefField"; @@ -9,7 +9,6 @@ import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols"; import { DocServer } from "../client/DocServer"; import { ComputedField } from "./ScriptField"; import { ScriptCast } from "./Types"; -import GroupManager from "../client/util/GroupManager"; function _readOnlySetter(): never { @@ -108,57 +107,65 @@ export function OVERRIDE_ACL(val: boolean) { _overrideAcl = val; } -const HierarchyMapping = new Map([ - [AclPrivate, 0], - [AclReadonly, 1], - [AclAddonly, 2], - [AclEdit, 3] -]); +let currentUserGroups: string[] = []; +let currentUserEmail: string;// = Doc.CurrentUserEmail; -export function getEffectiveAcl(target: any): symbol { +export function setGroups(groups: string[]) { + currentUserGroups = groups; + currentUserEmail = Doc.CurrentUserEmail; +} - console.log("in getEffectiveAcl"); - if (target[AclSym].ACL) return target[AclSym].ACL; +export function getEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol { - let effectiveAcl = AclEdit; + const HierarchyMapping = new Map([ + [AclPrivate, 0], + [AclReadonly, 1], + [AclAddonly, 2], + [AclEdit, 3] + ]); - for (const [key, value] of Object.entries(target[AclSym])) { - if (key.startsWith("ACL-")) { - if (GroupManager.Instance.currentUserGroups.includes(key.substring(4)) || Doc.CurrentUserEmail === key.substring(4).replace("_", ".")) { - if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) { - effectiveAcl = value as symbol; - if (effectiveAcl === AclEdit) break; - } - } - } + if (!target[AclSym] && target instanceof Doc) { + fetchProto(target); } - return effectiveAcl; -} + if (target[AclSym] && Object.keys(target[AclSym]).length) { + + if (target.author === currentUserEmail) return AclEdit; + + if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit; -function testPermission(target: any, in_prop: string | symbol | number): boolean { + if (target[AclSym].ACL) return target[AclSym].ACL; - console.log("here"); - // if (target[AclSym].ACL !== AclEdit && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return false; - if (target[AclSym].ACL === AclEdit) return true; - for (const [key, value] of Object.entries(target[AclSym])) { - if (key.startsWith("ACL-")) { - if (GroupManager.Instance.currentUserGroups.includes(key.substring(4))) { - if (value === AclEdit) return true; + let effectiveAcl = AclPrivate; + let aclPresent = false; + + for (const [key, value] of Object.entries(target[AclSym])) { + if (key.startsWith("ACL-")) { + if (currentUserGroups.includes(key.substring(4)) || currentUserEmail === key.substring(4).replace("_", ".")) { + if (HierarchyMapping.get(value as symbol)! >= HierarchyMapping.get(effectiveAcl)!) { + aclPresent = true; + effectiveAcl = value as symbol; + if (effectiveAcl === AclEdit) break; + } + } } } + return aclPresent ? effectiveAcl : AclEdit; + } + else { + return AclEdit; } - return _overrideAcl || DocServer.PlaygroundFields.includes(in_prop.toString()); } + const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox", "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"]; export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { - console.log("in setter") let prop = in_prop; - // if (target[AclSym] && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return true; // generalise to a testpermission function - if (!testPermission(target, in_prop)) return true; + if (getEffectiveAcl(target, in_prop) !== AclEdit) { + return true; + } if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { if (!prop.startsWith("_")) { console.log(prop + " is deprecated - switch to _" + prop); @@ -176,11 +183,9 @@ export function setter(target: any, in_prop: string | symbol | number, value: an } export function getter(target: any, in_prop: string | symbol | number, receiver: any): any { - console.log("in getter") let prop = in_prop; - const effectiveAcl = getEffectiveAcl(target); - if (in_prop === AclSym) return _overrideAcl ? undefined : effectiveAcl; - if (effectiveAcl === AclPrivate && !_overrideAcl) return undefined; + if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym]; + if (getEffectiveAcl(target) === AclPrivate && !_overrideAcl) return undefined; if (prop === LayoutSym) { return target.__LAYOUT__; } @@ -217,7 +222,6 @@ function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreP } if (field === undefined && !ignoreProto && prop !== "proto") { const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters - console.log("here"); if (proto instanceof Doc && getEffectiveAcl(proto) !== AclPrivate) { return getFieldImpl(proto[Self], prop, receiver, ignoreProto); } -- cgit v1.2.3-70-g09d2 From d70c9004215aea00514030be4137ccc2247b541a Mon Sep 17 00:00:00 2001 From: usodhi <61431818+usodhi@users.noreply.github.com> Date: Thu, 9 Jul 2020 12:42:20 +0530 Subject: removed some unnecessary imports --- src/client/util/SharingManager.tsx | 2 +- src/client/views/DocComponent.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 2 +- src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 3 +-- 6 files changed, 6 insertions(+), 7 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index d64302456..fd3b2dd04 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -1,7 +1,7 @@ import { observable, runInAction, action } from "mobx"; import * as React from "react"; import MainViewModal from "../views/MainViewModal"; -import { Doc, Opt, DocCastAsync, DocListCast } from "../../fields/Doc"; +import { Doc, Opt, DocListCast } from "../../fields/Doc"; import { DocServer } from "../DocServer"; import { Cast, StrCast } from "../../fields/Types"; import * as RequestPromise from "request-promise"; diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 781673e59..43ffe225f 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -1,4 +1,4 @@ -import { Doc, Opt, DataSym, DocListCast, AclSym, AclReadonly, AclAddonly } from '../../fields/Doc'; +import { Doc, Opt, DataSym, DocListCast, AclReadonly, AclAddonly } from '../../fields/Doc'; import { Touchable } from './Touchable'; import { computed, action, observable } from 'mobx'; import { Cast, BoolCast, ScriptCast } from '../../fields/Types'; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 6a6a475c8..032012b2d 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -8,7 +8,7 @@ import * as React from 'react'; import Lightbox from 'react-image-lightbox-with-rotate'; import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app import { DateField } from '../../../fields/DateField'; -import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt, AclEdit } from '../../../fields/Doc'; +import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index e34ceb994..1e6966c05 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -1,6 +1,6 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; -import { Doc, Opt, Field, AclSym, AclPrivate } from "../../../fields/Doc"; +import { Doc, Opt, Field, AclPrivate } from "../../../fields/Doc"; import { Cast, StrCast, NumCast } from "../../../fields/Types"; import { OmitKeys, Without, emptyPath } from "../../../Utils"; import { DirectoryImportBox } from "../../util/Import & Export/DirectoryImportBox"; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index b9ae8b444..9dc30c683 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as rp from "request-promise"; -import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclSym, AclReadonly, AclPrivate } from "../../../fields/Doc"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclPrivate } from "../../../fields/Doc"; import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ccf83cbf9..aa89b4a10 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from " import { ReplaceStep } from 'prosemirror-transform'; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../../fields/DateField'; -import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym, AclEdit } from "../../../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit } from "../../../../fields/Doc"; import { documentSchema } from '../../../../fields/documentSchemas'; import applyDevTools = require("prosemirror-dev-tools"); import { removeMarkWithAttrs } from "./prosemirrorPatches"; @@ -226,7 +226,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const curProto = Cast(Cast(this.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()); - // if (!this.dataDoc[AclSym]) { // what? if (getEffectiveAcl(this.dataDoc) === AclEdit) { if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) { this._applyingChange = true; -- cgit v1.2.3-70-g09d2 From bc0d6410ac42af595cea1fb242e10e464da321ae Mon Sep 17 00:00:00 2001 From: usodhi <61431818+usodhi@users.noreply.github.com> Date: Thu, 9 Jul 2020 20:41:28 +0530 Subject: change DocListCast to async in SharingManager + prevented textbox from showing up on click --- src/client/util/SharingManager.tsx | 71 +++++++++++----------- src/client/views/DocComponent.tsx | 4 +- src/client/views/collections/CollectionView.tsx | 6 +- .../collections/collectionFreeForm/MarqueeView.tsx | 6 +- src/client/views/nodes/DocumentContentsView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 4 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- src/fields/Doc.ts | 16 ++--- src/fields/util.ts | 24 +++----- 9 files changed, 70 insertions(+), 69 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index fd3b2dd04..af68edab6 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -1,7 +1,7 @@ import { observable, runInAction, action } from "mobx"; import * as React from "react"; import MainViewModal from "../views/MainViewModal"; -import { Doc, Opt, DocListCast } from "../../fields/Doc"; +import { Doc, Opt, DocListCastAsync } from "../../fields/Doc"; import { DocServer } from "../DocServer"; import { Cast, StrCast } from "../../fields/Types"; import * as RequestPromise from "request-promise"; @@ -43,19 +43,6 @@ export enum SharingPermissions { // [SharingPermissions.Edit, "green"] // ]); -// export const HierarchyMapping = new Map([ -// [SharingPermissions.None, 0], -// [SharingPermissions.View, 1], -// [SharingPermissions.Add, 2], -// [SharingPermissions.Edit, 3] - -// // ["0", SharingPermissions.None], -// // ["1", SharingPermissions.View], -// // ["2", SharingPermissions.Add], -// // ["3", SharingPermissions.Edit] - -// ]); - interface GroupOptions { label: string; options: UserOptions[]; @@ -88,8 +75,6 @@ export default class SharingManager extends React.Component<{}> { @observable private overlayOpacity = 0.4; @observable private selectedUsers: UserOptions[] | null = null; @observable private permissions: SharingPermissions = SharingPermissions.Edit; - @observable private sharedUsers: ValidatedUser[] = []; - @observable private sharedGroups: Doc[] = []; // private get linkVisible() { // return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false; @@ -162,13 +147,16 @@ export default class SharingManager extends React.Component<{}> { target[ACL] = permission; - group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List).push(target) : group.docsShared = new List([target]); + group.docsShared ? DocListCastAsync(group.docsShared).then(resolved => Doc.IndexOf(target, resolved!) === -1 && (group.docsShared as List).push(target)) : group.docsShared = new List([target]); + // group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List).push(target) : group.docsShared = new List([target]); users.forEach(({ notificationDoc }) => { - if (permission !== SharingPermissions.None) Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target); - else Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); - + DocListCastAsync(notificationDoc[storage]).then(res => console.log(res)); + DocListCastAsync(notificationDoc[storage]).then(resolved => { + if (permission !== SharingPermissions.None) Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target); + else Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); + }); }); } @@ -176,7 +164,12 @@ export default class SharingManager extends React.Component<{}> { const user: ValidatedUser = this.users.find(user => user.user.email === email)!; if (group.docsShared) { - DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc)); + DocListCastAsync(group.docsShared).then(docsShared => { + docsShared?.forEach(doc => { + DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc)); + }); + }); + // DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc)); } } @@ -184,20 +177,28 @@ export default class SharingManager extends React.Component<{}> { const user: ValidatedUser = this.users.find(user => user.user.email === email)!; if (group.docsShared) { - DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc)); + DocListCastAsync(group.docsShared).then(docsShared => { + docsShared?.forEach(doc => { + DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc)); + }); + }); + // DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc)); } } removeGroup = (group: Doc) => { if (group.docsShared) { - DocListCast(group.docsShared).forEach(doc => { - const ACL = `ACL-${StrCast(group.groupName)}`; - doc[ACL] = "Not Shared"; + DocListCastAsync(group.docsShared).then(resolved => { + resolved?.forEach(doc => { + const ACL = `ACL-${StrCast(group.groupName)}`; + doc[ACL] = "Not Shared"; + + const members: string[] = JSON.parse(StrCast(group.members)); + const users: ValidatedUser[] = this.users.filter(user => members.includes(user.user.email)); - const members: string[] = JSON.parse(StrCast(group.members)); - const users: ValidatedUser[] = this.users.filter(user => members.includes(user.user.email)); + users.forEach(user => Doc.RemoveDocFromList(user.notificationDoc, storage, doc)); + }); - users.forEach(user => Doc.RemoveDocFromList(user.notificationDoc, storage, doc)); }); } } @@ -217,14 +218,16 @@ export default class SharingManager extends React.Component<{}> { if (permission !== SharingPermissions.None) { - !this.sharedUsers.includes(recipient) && this.sharedUsers.push(recipient); - - Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target); + console.log(target); + console.log(notificationDoc); + DocListCastAsync(notificationDoc[storage]).then(resolved => { + Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target); + }); } else { - const index = this.sharedUsers.findIndex(user => user === recipient); - index !== -1 && this.sharedUsers.splice(index, 1); - Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); + DocListCastAsync(notificationDoc[storage]).then(resolved => { + Doc.IndexOf(target, resolved!) === -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); + }); } } diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 43ffe225f..eb58d8a3e 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -7,7 +7,7 @@ import { InteractionUtils } from '../util/InteractionUtils'; import { List } from '../../fields/List'; import { DateField } from '../../fields/DateField'; import { ScriptField } from '../../fields/ScriptField'; -import { getEffectiveAcl } from '../../fields/util'; +import { GetEffectiveAcl } from '../../fields/util'; /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) @@ -138,7 +138,7 @@ export function ViewBoxAnnotatableComponent

!docList.includes(d)); - const effectiveAcl = getEffectiveAcl(this.dataDoc); + const effectiveAcl = GetEffectiveAcl(this.dataDoc); if (added.length) { if (effectiveAcl === AclReadonly) { return false; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 032012b2d..7448ae002 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -17,7 +17,7 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; -import { TraceMobx, getEffectiveAcl } from '../../../fields/util'; +import { TraceMobx, GetEffectiveAcl } from '../../../fields/util'; import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -132,7 +132,7 @@ export class CollectionView extends Touchable !docList.includes(d)); - const effectiveAcl = getEffectiveAcl(this.props.Document); + const effectiveAcl = GetEffectiveAcl(this.props.Document); if (added.length) { if (effectiveAcl === AclReadonly) { return false; @@ -166,7 +166,7 @@ export class CollectionView extends Touchable { - if (getEffectiveAcl(this.props.Document) === AclEdit) { + if (GetEffectiveAcl(this.props.Document) === AclEdit) { const docs = doc instanceof Doc ? [doc] : doc as Doc[]; const targetDataDoc = this.props.Document[DataSym]; const value = DocListCast(targetDataDoc[this.props.fieldKey]); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b47236bea..971c501ca 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,6 +1,7 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, Opt, DocListCast, DataSym } from "../../../../fields/Doc"; +import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly } from "../../../../fields/Doc"; +import { GetEffectiveAcl } from "../../../../fields/util"; import { InkData, InkField, InkTool } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { RichTextField } from "../../../../fields/RichTextField"; @@ -276,7 +277,8 @@ export class MarqueeView extends React.Component 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns - return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || getEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) : + return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) : (Docu render() { if (!(this.props.Document instanceof Doc)) return (null); - if (getEffectiveAcl(this.props.Document) === AclPrivate) return (null); + if (GetEffectiveAcl(this.props.Document) === AclPrivate) return (null); if (this.props.Document.hidden) return (null); const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document); const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null))); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index aa89b4a10..0583c7f39 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -24,7 +24,7 @@ import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { createSchema, makeInterface } from "../../../../fields/Schema"; import { Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../fields/Types"; -import { TraceMobx, OVERRIDE_ACL, getEffectiveAcl } from '../../../../fields/util'; +import { TraceMobx, OVERRIDE_ACL, GetEffectiveAcl } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../../DocServer"; @@ -226,7 +226,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const curProto = Cast(Cast(this.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()); - if (getEffectiveAcl(this.dataDoc) === AclEdit) { + if (GetEffectiveAcl(this.dataDoc) === AclEdit) { if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) { this._applyingChange = true; (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 27eabf451..e8dca5fb6 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -17,7 +17,7 @@ import { RichTextField } from "./RichTextField"; import { listSpec } from "./Schema"; import { ComputedField } from "./ScriptField"; import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; -import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, getEffectiveAcl } from "./util"; +import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, GetEffectiveAcl } from "./util"; import { LinkManager } from "../client/util/LinkManager"; import { SharingPermissions } from "../client/util/SharingManager"; @@ -136,10 +136,10 @@ export class Doc extends RefField { set: setter, get: getter, // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter - has: (target, key) => getEffectiveAcl(target) !== AclPrivate && key in target.__fields, + has: (target, key) => GetEffectiveAcl(target) !== AclPrivate && key in target.__fields, ownKeys: target => { const obj = {} as any; - if (getEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fields); + if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fields); runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__); return Object.keys(obj); }, @@ -197,7 +197,7 @@ export class Doc extends RefField { public [WidthSym] = () => NumCast(this[SelfProxy]._width); public [HeightSym] = () => NumCast(this[SelfProxy]._height); public [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; } - public [ToString]() { return `Doc(${getEffectiveAcl(this) === AclPrivate ? "-inaccessible-" : this.title})`; } + public [ToString]() { return `Doc(${GetEffectiveAcl(this) === AclPrivate ? "-inaccessible-" : this.title})`; } public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; } public get [DataSym]() { const self = this[SelfProxy]; @@ -825,7 +825,7 @@ 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) return 0; + if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0; return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0; } export function IsBrushedDegree(doc: Doc) { @@ -834,14 +834,14 @@ export namespace Doc { })(doc); } export function BrushDoc(doc: Doc) { - if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc; + if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc; brushManager.BrushedDoc.set(doc, true); brushManager.BrushedDoc.set(Doc.GetProto(doc), true); return doc; } export function UnBrushDoc(doc: Doc) { - if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc; + if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc; brushManager.BrushedDoc.delete(doc); brushManager.BrushedDoc.delete(Doc.GetProto(doc)); return doc; @@ -871,7 +871,7 @@ export namespace Doc { } const highlightManager = new HighlightBrush(); export function IsHighlighted(doc: Doc) { - if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return false; + if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return false; return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc)); } export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) { diff --git a/src/fields/util.ts b/src/fields/util.ts index a1af1d3c5..f82ea26e0 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -108,15 +108,13 @@ export function OVERRIDE_ACL(val: boolean) { } let currentUserGroups: string[] = []; -let currentUserEmail: string;// = Doc.CurrentUserEmail; export function setGroups(groups: string[]) { currentUserGroups = groups; - currentUserEmail = Doc.CurrentUserEmail; } -export function getEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol { +export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol { const HierarchyMapping = new Map([ [AclPrivate, 0], @@ -131,7 +129,7 @@ export function getEffectiveAcl(target: any, in_prop?: string | symbol | number) if (target[AclSym] && Object.keys(target[AclSym]).length) { - if (target.author === currentUserEmail) return AclEdit; + if (target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclEdit; if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit; @@ -141,13 +139,11 @@ export function getEffectiveAcl(target: any, in_prop?: string | symbol | number) let aclPresent = false; for (const [key, value] of Object.entries(target[AclSym])) { - if (key.startsWith("ACL-")) { - if (currentUserGroups.includes(key.substring(4)) || currentUserEmail === key.substring(4).replace("_", ".")) { - if (HierarchyMapping.get(value as symbol)! >= HierarchyMapping.get(effectiveAcl)!) { - aclPresent = true; - effectiveAcl = value as symbol; - if (effectiveAcl === AclEdit) break; - } + if (currentUserGroups.includes(key.substring(4)) || Doc.CurrentUserEmail === key.substring(4).replace("_", ".")) { + if (HierarchyMapping.get(value as symbol)! >= HierarchyMapping.get(effectiveAcl)!) { + aclPresent = true; + effectiveAcl = value as symbol; + if (effectiveAcl === AclEdit) break; } } } @@ -163,7 +159,7 @@ const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHe "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"]; export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { let prop = in_prop; - if (getEffectiveAcl(target, in_prop) !== AclEdit) { + if (GetEffectiveAcl(target, in_prop) !== AclEdit) { return true; } if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { @@ -185,7 +181,7 @@ export function setter(target: any, in_prop: string | symbol | number, value: an export function getter(target: any, in_prop: string | symbol | number, receiver: any): any { let prop = in_prop; if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym]; - if (getEffectiveAcl(target) === AclPrivate && !_overrideAcl) return undefined; + if (GetEffectiveAcl(target) === AclPrivate && !_overrideAcl) return undefined; if (prop === LayoutSym) { return target.__LAYOUT__; } @@ -222,7 +218,7 @@ function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreP } if (field === undefined && !ignoreProto && prop !== "proto") { const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters - if (proto instanceof Doc && getEffectiveAcl(proto) !== AclPrivate) { + if (proto instanceof Doc && GetEffectiveAcl(proto) !== AclPrivate) { return getFieldImpl(proto[Self], prop, receiver, ignoreProto); } return undefined; -- cgit v1.2.3-70-g09d2 From db15b1d27a639af7a65f72dd5e4b6ea298412315 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 13 Jul 2020 11:46:35 -0400 Subject: fixed issues with ACLs and writing to playground fields when you have Edit permission. Also fixed text editing by fixing fetchProto to use untracked references to fields --- src/client/util/SharingManager.tsx | 3 ++- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/fields/Doc.ts | 12 +++++------- src/fields/util.ts | 8 +++++--- 4 files changed, 13 insertions(+), 12 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index af68edab6..050ff0c4e 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -146,6 +146,7 @@ export default class SharingManager extends React.Component<{}> { const ACL = `ACL-${StrCast(group.groupName)}`; target[ACL] = permission; + Doc.GetProto(target)[ACL] = permission; group.docsShared ? DocListCastAsync(group.docsShared).then(resolved => Doc.IndexOf(target, resolved!) === -1 && (group.docsShared as List).push(target)) : group.docsShared = new List([target]); // group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List).push(target) : group.docsShared = new List([target]); @@ -215,7 +216,7 @@ export default class SharingManager extends React.Component<{}> { // const permissions: { [key: string]: number } = target[ACL] ? JSON.parse(StrCast(target[ACL])) : {}; target[ACL] = permission; - + Doc.GetProto(target)[ACL] = permission; if (permission !== SharingPermissions.None) { console.log(target); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 0af941182..01fbcb020 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -237,7 +237,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._applyingChange = true; (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) - if (json !== curLayout?.Data) { + if (json.replace(/"selection":.*/, "") !== curLayout?.Data.replace(/"selection":.*/, "")) { !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize)); !curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily)); this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index e8dca5fb6..8ab4735a7 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1,4 +1,4 @@ -import { action, computed, observable, ObservableMap, runInAction } from "mobx"; +import { action, computed, observable, ObservableMap, runInAction, untracked } from "mobx"; import { computedFn } from "mobx-utils"; import { alias, map, serializable } from "serializr"; import { DocServer } from "../client/DocServer"; @@ -110,15 +110,13 @@ const AclMap = new Map([ export function fetchProto(doc: Doc) { if (doc.author !== Doc.CurrentUserEmail) { // storing acls for groups needs to be extended here - AclSym should store a datastructure that stores information about permissions + untracked(() => { + const permissions: { [key: string]: symbol } = {}; - const permissions: { [key: string]: symbol } = {}; + Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!); - Object.keys(doc).forEach(key => { - if (key.startsWith("ACL")) permissions[key] = AclMap.get(StrCast(doc[key]))!; + if (Object.keys(permissions).length) doc[AclSym] = permissions; }); - - - if (Object.keys(permissions).length) doc[AclSym] = permissions; } if (doc.proto instanceof Promise) { diff --git a/src/fields/util.ts b/src/fields/util.ts index ebfc3933a..be7736413 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -70,8 +70,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 || (writeMode !== DocServer.WriteMode.LiveReadonly); - const writeToServer = (sameAuthor || (writeMode === DocServer.WriteMode.Default)) && !playgroundMode; + const writeToDoc = sameAuthor || GetEffectiveAcl(target) === AclEdit || (writeMode !== DocServer.WriteMode.LiveReadonly); + const writeToServer = (sameAuthor || GetEffectiveAcl(target) === AclEdit || writeMode === DocServer.WriteMode.Default) && !playgroundMode; if (writeToDoc) { if (value === undefined) { @@ -91,8 +91,9 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number redo: () => receiver[prop] = value, undo: () => receiver[prop] = curValue }); + return true; } - return true; + return false; }); let _setter: (target: any, prop: string | symbol | number, value: any, receiver: any) => boolean = _setterImpl; @@ -126,6 +127,7 @@ export function setGroups(groups: string[]) { } export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol { + if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclEdit; const HierarchyMapping = new Map([ [AclPrivate, 0], -- cgit v1.2.3-70-g09d2 From 49e1dccee78af835ef48723bb708f1c2b47c9228 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 22 Jul 2020 21:09:20 -0400 Subject: fixed highlighting of text based on author. made download /import document novice menu items. --- .../collectionFreeForm/CollectionFreeFormView.tsx | 69 +++++++++++----------- src/client/views/nodes/DocumentView.tsx | 20 +++---- .../views/nodes/formattedText/FormattedTextBox.tsx | 15 ++--- src/client/views/nodes/formattedText/marks_rts.ts | 10 ++-- 4 files changed, 58 insertions(+), 56 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index bee9e7009..dc32ecb07 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1258,41 +1258,44 @@ export class CollectionFreeFormView extends CollectionSubView this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" }); - optionItems.push({ - description: "Import document", icon: "upload", event: ({ x, y }) => { - const input = document.createElement("input"); - input.type = "file"; - input.accept = ".zip"; - input.onchange = async _e => { - const upload = Utils.prepend("/uploadDoc"); - const formData = new FormData(); - const file = input.files && input.files[0]; - if (file) { - formData.append('file', file); - formData.append('remap', "true"); - const response = await fetch(upload, { method: "POST", body: formData }); - const json = await response.json(); - if (json !== "error") { - const doc = await DocServer.GetRefField(json); - if (doc instanceof Doc) { - const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y); - doc.x = xx, doc.y = yy; - this.props.addDocument?.(doc); - setTimeout(() => { - SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => { - docs.docs.forEach(d => LinkManager.Instance.addLink(d)); - }) - }, 2000); // need to give solr some time to update so that this query will find any link docs we've added. - } - } - } - }; - input.click(); - } - }); + } !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); - + const mores = ContextMenu.Instance.findByDescription("More..."); + const moreItems = mores && "subitems" in mores ? mores.subitems : []; + moreItems.push({ + description: "Import document", icon: "upload", event: ({ x, y }) => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".zip"; + input.onchange = async _e => { + const upload = Utils.prepend("/uploadDoc"); + const formData = new FormData(); + const file = input.files && input.files[0]; + if (file) { + formData.append('file', file); + formData.append('remap', "true"); + const response = await fetch(upload, { method: "POST", body: formData }); + const json = await response.json(); + if (json !== "error") { + const doc = await DocServer.GetRefField(json); + if (doc instanceof Doc) { + const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y); + doc.x = xx, doc.y = yy; + this.props.addDocument?.(doc); + setTimeout(() => { + SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => { + docs.docs.forEach(d => LinkManager.Instance.addLink(d)); + }) + }, 2000); // need to give solr some time to update so that this query will find any link docs we've added. + } + } + } + }; + input.click(); + } + }); + !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" }); } @observable showTimeline = false; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3b46b70ea..748938699 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -782,6 +782,16 @@ export class DocumentView extends DocComponent(Docu const more = cm.findByDescription("More..."); const moreItems = more && "subitems" in more ? more.subitems : []; + moreItems.push({ + description: "Download document", icon: "download", event: async () => { + Doc.Zip(this.props.Document); + // const a = document.createElement("a"); + // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); + // a.href = url; + // a.download = `DocExport-${this.props.Document[Id]}.zip`; + // a.click(); + } + }); if (!Doc.UserDoc().noviceMode) { moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" }); moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); @@ -791,16 +801,6 @@ export class DocumentView extends DocComponent(Docu moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" }); moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" }); } - moreItems.push({ - description: "Download document", icon: "download", event: async () => { - Doc.Zip(this.props.Document); - // const a = document.createElement("a"); - // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); - // a.href = url; - // a.download = `DocExport-${this.props.Document[Id]}.zip`; - // a.click(); - } - }); moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" }); } GetEffectiveAcl(this.props.Document) === AclEdit && moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 7906e2533..e703a81e2 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -425,16 +425,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" }); } if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "todo", { outline: "black solid 1px" }); } if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "important", { "font-size": "larger" }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "important", { "font-size": "larger" }); } if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "disagree", { "text-decoration": "line-through" }); } if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "ignore", { "font-size": "1" }); } if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) { addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); @@ -1303,9 +1303,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); } - const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); - this._lastTimedMark = mark; - // this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); + if (e.key === " " || this._lastTimedMark?.attrs.userid !== Doc.CurrentUserEmail) { + const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); + } if (!this._undoTyping) { this.startUndoTypingBatch(); diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 3d7d71b14..f95f46104 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -258,9 +258,7 @@ export const marks: { [index: string]: MarkSpec } = { }, parseDOM: [{ style: 'background: yellow' }], toDOM(node: any) { - return ['span', { - style: `background: ${node.attrs.selected ? "orange" : "yellow"}` - }]; + return ['span', { style: `background: ${node.attrs.selected ? "orange" : "yellow"}` }]; } }, @@ -277,8 +275,8 @@ export const marks: { [index: string]: MarkSpec } = { const min = Math.round(node.attrs.modified / 12); const hr = Math.round(min / 60); const day = Math.round(hr / 60 / 24); - const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : ""; - return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0]; + const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " UM-remote" : ""; + return ['span', { class: "UM-" + uid + remote + " UM-min-" + min + " UM-hr-" + hr + " UM-day-" + day }, 0]; } }, // the id of the user who entered the text @@ -292,7 +290,7 @@ export const marks: { [index: string]: MarkSpec } = { inclusive: false, toDOM(node: any) { const uid = node.attrs.userid.replace(".", "").replace("@", ""); - return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0]; + return ['span', { class: "UT-" + uid + " UT-" + node.attrs.tag }, 0]; } }, -- cgit v1.2.3-70-g09d2