aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2021-08-06 18:03:38 -0400
committerbobzel <zzzman@gmail.com>2021-08-06 18:03:38 -0400
commite7bbdd3b489fea1c508af53345cd0d1f31685cb9 (patch)
tree339fdd62e8caabba31bfde2de524cfddc7526836 /src
parent3a04312c198618d8093585e7dc209ffda9a80ba8 (diff)
collabortion fixes: added new acl for allowing people to edit their own text within the same note, fixed playground fields to write to the server without updating other clients.
Diffstat (limited to 'src')
-rw-r--r--src/client/DocServer.ts5
-rw-r--r--src/client/documents/Documents.ts10
-rw-r--r--src/client/util/CurrentUserUtils.ts6
-rw-r--r--src/client/util/SharingManager.tsx13
-rw-r--r--src/client/views/DocComponent.tsx6
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/MarqueeAnnotator.tsx4
-rw-r--r--src/client/views/PropertiesView.tsx14
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx4
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx12
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts70
-rw-r--r--src/fields/Doc.ts6
-rw-r--r--src/fields/util.ts22
13 files changed, 105 insertions, 69 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 59278d2af..d9ae7d64c 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -59,7 +59,10 @@ export namespace DocServer {
export var PlaygroundFields: string[];
export function setPlaygroundFields(livePlaygroundFields: string[]) {
DocServer.PlaygroundFields = livePlaygroundFields;
- livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.Playground));
+ livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground));
+ }
+ export function IsPlaygroundField(field: string) {
+ return DocServer.PlaygroundFields?.includes(field.replace(/^_/, ""));
}
export function setFieldWriteMode(field: string, writeMode: WriteMode) {
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 24f777e88..93f0880a4 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -577,7 +577,7 @@ export namespace Docs {
dataProps.creationDate = new DateField;
dataProps[`${fieldKey}-lastModified`] = new DateField;
dataProps["acl-Override"] = "None";
- dataProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add;
+ dataProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
dataProps[fieldKey] = data;
@@ -588,7 +588,7 @@ export namespace Docs {
viewProps.author = Doc.CurrentUserEmail;
viewProps["acl-Override"] = "None";
- viewProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add;
+ viewProps["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewProps, true, true);
![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc);
@@ -699,7 +699,7 @@ export namespace Docs {
I.author = Doc.CurrentUserEmail;
I.rotation = 0;
I.data = new InkField(points);
- I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add;
+ I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment;
I["acl-Override"] = "None";
I[Initializing] = false;
return I;
@@ -1038,8 +1038,8 @@ export namespace DocUtils {
title: ComputedField.MakeFunction("generateLinkTitle(self)") as any,
"anchor1-useLinkSmallAnchor": source.doc.useLinkSmallAnchor ? true : undefined,
"anchor2-useLinkSmallAnchor": target.doc.useLinkSmallAnchor ? true : undefined,
- "acl-Public": SharingPermissions.Add,
- "_acl-Public": SharingPermissions.Add,
+ "acl-Public": SharingPermissions.Augment,
+ "_acl-Public": SharingPermissions.Augment,
layout_linkView: Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null),
linkDisplay: true, hidden: true,
linkRelationship,
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 1af6607a7..14c43fb1c 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -917,7 +917,7 @@ export class CurrentUserUtils {
linkDocs = new Doc(linkDatabaseId, true);
(linkDocs as Doc).author = Doc.CurrentUserEmail;
(linkDocs as Doc).data = new List<Doc>([]);
- (linkDocs as Doc)["acl-Public"] = SharingPermissions.Add;
+ (linkDocs as Doc)["acl-Public"] = SharingPermissions.Augment;
}
doc.myLinkDatabase = new PrefetchProxy(linkDocs);
}
@@ -926,10 +926,10 @@ export class CurrentUserUtils {
if (!sharedDocs) {
sharedDocs = Docs.Create.TreeDocument([], {
title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15,
- _showTitle: "title", ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Add, "_acl-Public": SharingPermissions.Add,
+ _showTitle: "title", ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment,
_chromeHidden: true, boxShadow: "0 0",
}, sharingDocumentId + "outer", sharingDocumentId);
- (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Add;
+ (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment;
}
if (sharedDocs instanceof Doc) {
Doc.GetProto(sharedDocs).userColor = sharedDocs.userColor || "rgb(202, 202, 202)";
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index d283510b7..a8972b988 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import Select from "react-select";
import * as RequestPromise from "request-promise";
-import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
+import { AclAugment, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, Opt, AclSelfEdit } from "../../fields/Doc";
import { List } from "../../fields/List";
import { Cast, NumCast, StrCast } from "../../fields/Types";
import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from "../../fields/util";
@@ -85,7 +85,8 @@ export class SharingManager extends React.Component<{}> {
private AclMap = new Map<symbol, string>([
[AclPrivate, SharingPermissions.None],
[AclReadonly, SharingPermissions.View],
- [AclAddonly, SharingPermissions.Add],
+ [AclAugment, SharingPermissions.Augment],
+ [AclSelfEdit, SharingPermissions.SelfEdit],
[AclEdit, SharingPermissions.Edit],
[AclAdmin, SharingPermissions.Admin]
]);
@@ -101,7 +102,7 @@ export class SharingManager extends React.Component<{}> {
this.targetDoc = target_doc || target?.props.Document;
DictationOverlay.Instance.hasActiveModal = true;
this.isOpen = this.targetDoc !== undefined;
- this.permissions = SharingPermissions.Add;
+ this.permissions = SharingPermissions.Augment;
});
}
@@ -366,10 +367,10 @@ export class SharingManager extends React.Component<{}> {
const dropdownValues: string[] = Object.values(SharingPermissions);
if (!uniform) dropdownValues.unshift("-multiple-");
if (override) dropdownValues.unshift("None");
- return dropdownValues.filter(permission => permission !== SharingPermissions.View).map(permission =>
+ return dropdownValues.filter(permission => !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission =>
(
<option key={permission} value={permission}>
- {permission === SharingPermissions.Add ? "Can Augment" : permission}
+ {permission}
</option>
)
);
@@ -546,7 +547,7 @@ export class SharingManager extends React.Component<{}> {
</select>
) : (
<div className={"permissions-dropdown"}>
- {permissions === SharingPermissions.Add ? "Can Augment" : permissions}
+ {permissions}
</div>
)}
</div>
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index fc36c7e43..99c695a4a 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,4 +1,4 @@
-import { Doc, Opt, DataSym, AclReadonly, AclAddonly, AclPrivate, AclEdit, AclSym, DocListCastAsync, DocListCast, AclAdmin } from '../../fields/Doc';
+import { Doc, Opt, DataSym, AclReadonly, AclAugment, AclPrivate, AclEdit, AclSym, DocListCast, AclAdmin, AclSelfEdit } from '../../fields/Doc';
import { Touchable } from './Touchable';
import { computed, action, observable } from 'mobx';
import { Cast, BoolCast, ScriptCast } from '../../fields/Types';
@@ -131,7 +131,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
removeDocument(doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean): boolean {
const effectiveAcl = GetEffectiveAcl(this.dataDoc);
const indocs = doc instanceof Doc ? [doc] : doc;
- const docs = indocs.filter(doc => effectiveAcl === AclEdit || effectiveAcl === AclAdmin || GetEffectiveAcl(doc) === AclAdmin);
+ const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin);
if (docs.length) {
setTimeout(() => docs.map(doc => { // this allows 'addDocument' to see the annotationOn field in order to create a pushin
Doc.SetInPlace(doc, "isPushpin", undefined, true);
@@ -199,7 +199,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
});
}
- if (effectiveAcl === AclAddonly) {
+ if (effectiveAcl === AclAugment) {
added.map(doc => {
if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc);
doc.context = this.props.Document;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 005e46836..8f37172a0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -107,7 +107,7 @@ export class MainView extends React.Component {
new InkStrokeProperties();
this._sidebarContent.proto = undefined;
if (!MainView.Live) {
- DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_autoHeight", "_showSidebar", "showSidebar", "_sidebarWidthPercent", "_width", "_height", "width", "height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeHidden", "nativeWidth", "_nativeWidth"]); // can play with these fields on someone else's
+ DocServer.setPlaygroundFields(["dataTransition", "autoHeight", "showSidebar", "sidebarWidthPercent", "viewTransition", "panX", "panY", "viewScale", "scrollTop", "hidden", "curPage", "viewType", "chromeHidden", "nativeWidth"]); // can play with these fields on someone else's
}
DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg)));
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 805cda95c..a3a3bce56 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -1,6 +1,6 @@
import { action, observable, ObservableMap, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../fields/Doc";
+import { AclAugment, AclAdmin, AclEdit, DataSym, Doc, Opt, AclSelfEdit } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { List } from "../../fields/List";
import { NumCast } from "../../fields/Types";
@@ -156,7 +156,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => {
// creates annotation documents for current highlights
const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]);
- const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
+ const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
!savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc);
return annotationDoc as Doc ?? undefined;
}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 8136edf04..de437e1df 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -5,7 +5,7 @@ import { intersection } from "lodash";
import { action, autorun, computed, Lambda, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { ColorState, SketchPicker } from "react-color";
-import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc";
+import { AclAugment, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, Field, HeightSym, Opt, WidthSym, AclSelfEdit } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { InkField } from "../../fields/InkField";
import { ComputedField } from "../../fields/ScriptField";
@@ -342,12 +342,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
return <select className="permissions-select"
value={permission}
onChange={e => this.changePermissions(e, user)}>
- {dropdownValues.filter(permission => permission !== SharingPermissions.View).map(permission => {
- return (
- <option key={permission} value={permission}>
- {permission === SharingPermissions.Add ? "Can Augment" : permission}
- </option>);
- })}
+ {dropdownValues.filter(permission =>
+ !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission =>
+ <option key={permission} value={permission}> {permission} </option>)}
</select>;
}
@@ -402,7 +399,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
[AclUnset, "None"],
[AclPrivate, SharingPermissions.None],
[AclReadonly, SharingPermissions.View],
- [AclAddonly, SharingPermissions.Add],
+ [AclAugment, SharingPermissions.Augment],
+ [AclSelfEdit, SharingPermissions.SelfEdit],
[AclEdit, SharingPermissions.Edit],
[AclAdmin, SharingPermissions.Admin]
]);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 846d28214..19da7ea00 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,6 +1,6 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../../../fields/Doc";
+import { AclAugment, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../../../fields/Doc";
import { Id } from "../../../../fields/FieldSymbols";
import { InkData, InkField, InkTool } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
@@ -298,7 +298,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._downX = x;
this._downY = y;
const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
- if ([AclAdmin, AclEdit, AclAddonly].includes(effectiveAcl)) {
+ if ([AclAdmin, AclEdit, AclAugment].includes(effectiveAcl)) {
PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
}
this.clearSelection();
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index edd7177de..f7bed2fa1 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -11,7 +11,7 @@ import { ReplaceStep } from 'prosemirror-transform';
import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from "../../../../fields/Doc";
+import { AclAdmin, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym, AclAugment } from "../../../../fields/Doc";
import { documentSchema } from '../../../../fields/documentSchemas';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
@@ -253,7 +253,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const removeSelection = (json: string | undefined) => json?.indexOf("\"storedMarks\"") === -1 ?
json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\"");
- if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) {
+ if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || effectiveAcl === AclSelfEdit) {
const accumTags = [] as string[];
state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => {
if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith("#")) {
@@ -1401,6 +1401,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._rules!.EnteringStyle = false;
}
e.stopPropagation();
+ for (var i = state.selection.from; i < state.selection.to; i++) {
+ const node = state.doc.resolve(i);
+ if (node?.marks?.().some(mark => mark.type === schema.marks.user_mark &&
+ mark.attrs.userid !== Doc.CurrentUserEmail) &&
+ [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
+ e.preventDefault();
+ }
+ }
switch (e.key) {
case "Escape":
this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index d5c77786c..1f78b2204 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -7,13 +7,14 @@ import { splitListItem, wrapInList, } from "prosemirror-schema-list";
import { EditorState, Transaction, TextSelection } from "prosemirror-state";
import { SelectionManager } from "../../../util/SelectionManager";
import { NumCast, BoolCast, Cast, StrCast } from "../../../../fields/Types";
-import { Doc, DataSym, DocListCast } from "../../../../fields/Doc";
+import { Doc, DataSym, DocListCast, AclAugment } from "../../../../fields/Doc";
import { FormattedTextBox } from "./FormattedTextBox";
import { Id } from "../../../../fields/FieldSymbols";
import { Docs } from "../../../documents/Documents";
import { Utils } from "../../../../Utils";
import { listSpec } from "../../../../fields/Schema";
import { List } from "../../../../fields/List";
+import { GetEffectiveAcl } from "../../../../fields/util";
const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
@@ -70,25 +71,39 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
return false;
};
+ const canEdit = (state: any) => {
+ for (var i = state.selection.from; i < state.selection.to; i++) {
+ const node = state.doc.resolve(i);
+ if (node?.marks?.().some((mark: any) => mark.type === schema.marks.user_mark &&
+ mark.attrs.userid !== Doc.CurrentUserEmail) &&
+ GetEffectiveAcl(props.Document) === AclAugment) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ const toggleEditableMark = (mark: any) => (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
+
//History commands
bind("Mod-z", undo);
bind("Shift-Mod-z", redo);
!mac && bind("Mod-y", redo);
//Commands to modify Mark
- bind("Mod-b", toggleMark(schema.marks.strong));
- bind("Mod-B", toggleMark(schema.marks.strong));
+ bind("Mod-b", toggleEditableMark(schema.marks.strong));
+ bind("Mod-B", toggleEditableMark(schema.marks.strong));
- bind("Mod-e", toggleMark(schema.marks.em));
- bind("Mod-E", toggleMark(schema.marks.em));
+ bind("Mod-e", toggleEditableMark(schema.marks.em));
+ bind("Mod-E", toggleEditableMark(schema.marks.em));
- bind("Mod-*", toggleMark(schema.marks.code));
+ bind("Mod-*", toggleEditableMark(schema.marks.code));
- bind("Mod-u", toggleMark(schema.marks.underline));
- bind("Mod-U", toggleMark(schema.marks.underline));
+ bind("Mod-u", toggleEditableMark(schema.marks.underline));
+ bind("Mod-U", toggleEditableMark(schema.marks.underline));
//Commands for lists
- bind("Ctrl-i", wrapInList(schema.nodes.ordered_list));
+ bind("Ctrl-i", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state, dispatch as any));
bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
/// bcz; Argh!! replace layotuTEmpalteString with a onTab prop conditionally handles Tab);
@@ -96,6 +111,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
if (!props.LayoutTemplateString) return addTextBox(false, true);
return true;
}
+ if (!canEdit(state)) return true;
const ref = state.selection;
const range = ref.$from.blockRange(ref.$to);
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
@@ -121,6 +137,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
/// bcz; Argh!! replace with a onShiftTab prop conditionally handles Tab);
if (props.Document._singleLine) return true;
+ if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
@@ -140,24 +157,19 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
//Commands to modify BlockType
- bind("Ctrl->", wrapIn(schema.nodes.blockquote));
- bind("Alt-\\", setBlockType(schema.nodes.paragraph));
- bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block));
+ bind("Ctrl->", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit((state) && wrapIn(schema.nodes.blockquote)(state, dispatch as any)));
+ bind("Alt-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state, dispatch as any));
+ bind("Shift-Ctrl-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state, dispatch as any));
- bind("Ctrl-m", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() })));
- });
+ bind("Ctrl-m", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() }))));
for (let i = 1; i <= 6; i++) {
- bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i }));
+ bind("Shift-Ctrl-" + i, (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state, dispatch as any));
}
//Command to create a horizontal break line
const hr = schema.nodes.horizontal_rule;
- bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
- return true;
- });
+ bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
//Command to unselect all
bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
@@ -173,13 +185,15 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
};
//Command to create a text document to the right of the selected textbox
- bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => addTextBox(false, true));
+ bind("Alt-Enter", () => addTextBox(false, true));
//Command to create a text document to the bottom of the selected textbox
- bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => addTextBox(true, true));
+ bind("Ctrl-Enter", () => addTextBox(true, true));
// backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
bind("Backspace", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
+ if (!canEdit(state)) return true;
+
if (!deleteSelection(state, (tx: Transaction<Schema<any, any>>) => {
dispatch(updateBullets(tx, schema));
})) {
@@ -200,6 +214,9 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
//command to break line
bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
if (addTextBox(true, false)) return true;
+
+ if (!canEdit(state)) return true;
+
const trange = state.selection.$from.blockRange(state.selection.$to);
const path = (state.selection.$from as any).path;
const depth = trange ? liftTarget(trange) : undefined;
@@ -238,18 +255,19 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
//Command to create a blank space
bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
dispatch(splitMetadata(marks, state.tr));
return false;
});
- bind("Alt-ArrowUp", joinUp);
- bind("Alt-ArrowDown", joinDown);
- bind("Mod-BracketLeft", lift);
+ bind("Alt-ArrowUp", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && joinUp(state, dispatch as any));
+ bind("Alt-ArrowDown", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && joinDown(state, dispatch as any));
+ bind("Mod-BracketLeft", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && lift(state, dispatch as any));
const cmd = chainCommands(exitCode, (state, dispatch) => {
if (dispatch) {
- dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
+ canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
return true;
}
return false;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 0cbfaf067..85ea3cfa9 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -90,7 +90,8 @@ export const DirectLinksSym = Symbol("DirectLinks");
export const AclUnset = Symbol("AclUnset");
export const AclPrivate = Symbol("AclOwnerOnly");
export const AclReadonly = Symbol("AclReadOnly");
-export const AclAddonly = Symbol("AclAddonly");
+export const AclAugment = Symbol("AclAugment");
+export const AclSelfEdit = Symbol("AclSelfEdit");
export const AclEdit = Symbol("AclEdit");
export const AclAdmin = Symbol("AclAdmin");
export const UpdatingFromServer = Symbol("UpdatingFromServer");
@@ -102,7 +103,8 @@ const AclMap = new Map<string, symbol>([
["None", AclUnset],
[SharingPermissions.None, AclPrivate],
[SharingPermissions.View, AclReadonly],
- [SharingPermissions.Add, AclAddonly],
+ [SharingPermissions.Augment, AclAugment],
+ [SharingPermissions.SelfEdit, AclSelfEdit],
[SharingPermissions.Edit, AclEdit],
[SharingPermissions.Admin, AclAdmin]
]);
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 526e5af72..2bb6b45c2 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,5 +1,5 @@
import { UndoManager } from "../client/util/UndoManager";
-import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync, ForceServerWrite, Initializing } from "./Doc";
+import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAugment, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync, ForceServerWrite, Initializing, AclSelfEdit } from "./Doc";
import { SerializationHelper } from "../client/util/SerializationHelper";
import { ProxyField, PrefetchProxy } from "./Proxy";
import { RefField } from "./RefField";
@@ -14,6 +14,7 @@ import CursorField from "./CursorField";
import { List } from "./List";
import { SnappingManager } from "../client/util/SnappingManager";
import { computedFn } from "mobx-utils";
+import { RichTextField } from "./RichTextField";
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
@@ -77,7 +78,9 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
const fromServer = target[UpdatingFromServer];
const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail);
const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (writeMode !== DocServer.WriteMode.LiveReadonly);
- const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Default) && !DocServer.Control.isReadOnly();// && !playgroundMode;
+ const writeToServer =
+ (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclSelfEdit && (value instanceof RichTextField))) &&
+ !DocServer.Control.isReadOnly();
if (writeToDoc) {
if (value === undefined) {
@@ -157,9 +160,10 @@ export function inheritParentAcls(parent: Doc, child: Doc) {
*/
export enum SharingPermissions {
Admin = "Admin",
- Edit = "Can Edit",
- Add = "Can Augment",
- View = "Can View",
+ Edit = "Edit",
+ SelfEdit = "Self Edit",
+ Augment = "Augment",
+ View = "View",
None = "Not Shared"
}
@@ -176,7 +180,7 @@ export function GetEffectiveAcl(target: any, user?: string): symbol {
function getPropAcl(target: any, prop: string | symbol | number) {
if (prop === UpdatingFromServer || prop === Initializing || target[UpdatingFromServer] || prop === AclSym) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent
- if (prop && DocServer.PlaygroundFields?.includes(prop.toString())) return AclEdit; // playground props are always editable
+ if (prop && DocServer.IsPlaygroundField(prop.toString())) return AclEdit; // playground props are always editable
return GetEffectiveAcl(target);
}
@@ -192,7 +196,8 @@ function getEffectiveAcl(target: any, user?: string): symbol {
HierarchyMapping = HierarchyMapping || new Map<symbol, number>([
[AclPrivate, 0],
[AclReadonly, 1],
- [AclAddonly, 2],
+ [AclAugment, 2],
+ [AclSelfEdit, 2.5],
[AclEdit, 3],
[AclAdmin, 4]
]);
@@ -235,6 +240,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
["Not Shared", 0],
["Can View", 1],
["Can Augment", 2],
+ ["Self Edit", 2.5],
["Can Edit", 3],
["Admin", 4]
]);
@@ -294,7 +300,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
let prop = in_prop;
const effectiveAcl = getPropAcl(target, prop);
- if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) return true;
+ if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) return true;
// if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't
if (typeof prop === "string" && prop.startsWith("acl") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined, "None"].includes(value))) return true;
// if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Augment", "Can View", "Not Shared", undefined].includes(value)) return true;