From e7bbdd3b489fea1c508af53345cd0d1f31685cb9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 6 Aug 2021 18:03:38 -0400 Subject: 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. --- .../formattedText/ProsemirrorExampleTransfer.ts | 70 ++++++++++++++-------- 1 file changed, 44 insertions(+), 26 deletions(-) (limited to 'src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts') 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>(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, dispatch: (tx: Transaction) => 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, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state, dispatch as any)); bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { /// bcz; Argh!! replace layotuTEmpalteString with a onTab prop conditionally handles Tab); @@ -96,6 +111,7 @@ export function buildKeymap>(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>(schema: S, props: any, mapKey bind("Shift-Tab", (state: EditorState, dispatch: (tx: Transaction) => 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>(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, dispatch: (tx: Transaction) => void) => canEdit((state) && wrapIn(schema.nodes.blockquote)(state, dispatch as any))); + bind("Alt-\\", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state, dispatch as any)); + bind("Shift-Ctrl-\\", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state, dispatch as any)); - bind("Ctrl-m", (state: EditorState, dispatch: (tx: Transaction) => void) => { - dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() }))); - }); + bind("Ctrl-m", (state: EditorState, dispatch: (tx: Transaction) => 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, dispatch: (tx: Transaction) => 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, dispatch: (tx: Transaction) => void) => { - dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()); - return true; - }); + bind("Mod-_", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView())); //Command to unselect all bind("Escape", (state: EditorState, dispatch: (tx: Transaction) => void) => { @@ -173,13 +185,15 @@ export function buildKeymap>(schema: S, props: any, mapKey }; //Command to create a text document to the right of the selected textbox - bind("Alt-Enter", (state: EditorState, dispatch: (tx: Transaction>) => 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, dispatch: (tx: Transaction) => void) => addTextBox(true, true)); + bind("Ctrl-Enter", () => addTextBox(true, true)); // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward); bind("Backspace", (state: EditorState, dispatch: (tx: Transaction>) => void) => { + if (!canEdit(state)) return true; + if (!deleteSelection(state, (tx: Transaction>) => { dispatch(updateBullets(tx, schema)); })) { @@ -200,6 +214,9 @@ export function buildKeymap>(schema: S, props: any, mapKey //command to break line bind("Enter", (state: EditorState, dispatch: (tx: Transaction>) => 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>(schema: S, props: any, mapKey //Command to create a blank space bind("Space", (state: EditorState, dispatch: (tx: Transaction) => 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, dispatch: (tx: Transaction) => void) => canEdit(state) && joinUp(state, dispatch as any)); + bind("Alt-ArrowDown", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinDown(state, dispatch as any)); + bind("Mod-BracketLeft", (state: EditorState, dispatch: (tx: Transaction) => 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; -- cgit v1.2.3-70-g09d2 From bd41980b6d8849c5ae875fbe5325f88373b3640b Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 17 Aug 2021 11:58:21 -0400 Subject: fixed canEdit func in prosemirrorExampleTransfer to prevent error when hitting enter at end of uneditable doc. fixed scrollheight setting error in formattedtextbox caused by assigning null --- .../views/nodes/formattedText/FormattedTextBox.tsx | 6 +++--- .../nodes/formattedText/ProsemirrorExampleTransfer.ts | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) (limited to 'src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 1058070f8..b0b8ce75d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -535,7 +535,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); const min = Math.round(Date.now() / 1000 / 60); numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() })); - setTimeout(() => this.updateHighlights()); + setTimeout(this.updateHighlights); } if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) { addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); @@ -882,7 +882,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } }, ); - if (this._recording) setTimeout(() => this.recordDictation()); + if (this._recording) setTimeout(this.recordDictation); } var quickScroll: string | undefined = ""; this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop), @@ -1453,7 +1453,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp setTimeout(() => { proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins); scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); - setScrollHeight(); + scrollHeight && setScrollHeight(); }, 10); } else setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived... } diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 1f78b2204..eff400a98 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -7,7 +7,7 @@ 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, AclAugment } from "../../../../fields/Doc"; +import { Doc, DataSym, DocListCast, AclAugment, AclSelfEdit } from "../../../../fields/Doc"; import { FormattedTextBox } from "./FormattedTextBox"; import { Id } from "../../../../fields/FieldSymbols"; import { Docs } from "../../../documents/Documents"; @@ -72,13 +72,16 @@ export function buildKeymap>(schema: S, props: any, mapKey }; 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; - } + switch (GetEffectiveAcl(props.Document)) { + case AclAugment: return false; + case AclSelfEdit: + for (var i = state.selection.from; i < state.selection.to; i++) { + const marks = state.doc.resolve(i)?.marks?.(); + if (marks?.some((mark: any) => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail)) { + return false; + } + } + break; } return true; } -- cgit v1.2.3-70-g09d2 From 3051d9a16dff8efbf4d32465812093cae7508c74 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 19 Aug 2021 18:42:46 -0400 Subject: fixed errors and warnings --- src/client/util/CurrentUserUtils.ts | 8 ++++---- src/client/util/LinkManager.ts | 4 ++-- src/client/util/SharingManager.tsx | 2 +- src/client/views/nodes/FilterBox.tsx | 4 ++-- src/client/views/nodes/ScreenshotBox.tsx | 2 +- .../views/nodes/formattedText/ProsemirrorExampleTransfer.ts | 2 +- src/fields/Doc.ts | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) (limited to 'src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index e446e3752..1f37163d7 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -567,7 +567,7 @@ export class CurrentUserUtils { ); menuBtns.forEach(menuBtn => { - if (menuBtn.title == "Search") { + if (menuBtn.title === "Search") { this.searchBtn = menuBtn; } }); @@ -575,10 +575,10 @@ export class CurrentUserUtils { menuBtns[menuBtns.length - 1].hidden = ComputedField.MakeFunction("IsNoviceMode()"); menuBtns.forEach(menuBtn => { - if (menuBtn.title == "Search") { - doc.searchBtn = menuBtn + if (menuBtn.title === "Search") { + doc.searchBtn = menuBtn; } - }) + }); doc.menuStack = new PrefetchProxy(Docs.Create.StackingDocument(menuBtns, { title: "menuItemPanel", diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index c33dff8d1..3579083e4 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -47,7 +47,7 @@ export class LinkManager { Doc.GetProto(link)[DirectLinksSym].add(link); } })); - } + }; const remLinkFromDoc = (link: Doc) => { const a1 = link?.anchor1; const a2 = link?.anchor2; @@ -58,7 +58,7 @@ export class LinkManager { Doc.GetProto(link)[DirectLinksSym].delete(link); } })); - } + }; const watchUserLinkDB = (userLinkDBDoc: Doc) => { LinkManager.links.push(...DocListCast(userLinkDBDoc.data)); const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field; // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 6c4556250..6d7f7e8df 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -513,7 +513,7 @@ export class SharingManager extends React.Component<{}> { if (this.myDocAcls) { const newDocs: Doc[] = []; - SearchBox.foreachRecursiveDoc(docs, doc => newDocs.push(doc)); + SearchBox.foreachRecursiveDoc(docs, (depth, doc) => newDocs.push(doc)); docs = newDocs.filter(doc => GetEffectiveAcl(doc) === AclAdmin); } diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index c892a9f6c..beefc4a82 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -101,7 +101,7 @@ export class FilterBox extends ViewBoxBaseComponent(); const activeTabs = DocListCast(targetDoc.data); - SearchBox.foreachRecursiveDoc(activeTabs, (doc: Doc) => allDocs.add(doc)); + SearchBox.foreachRecursiveDoc(activeTabs, (depth, doc) => allDocs.add(doc)); setTimeout(action(() => this._allDocs = Array.from(allDocs))); } return this._allDocs; @@ -482,7 +482,7 @@ Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: Scripting.addGlobal(function readFacetData(layoutDoc: Doc, facetHeader: string) { const allCollectionDocs = new Set(); const activeTabs = DocListCast(layoutDoc.data); - SearchBox.foreachRecursiveDoc(activeTabs, (doc: Doc) => allCollectionDocs.add(doc)); + SearchBox.foreachRecursiveDoc(activeTabs, (depth: number, doc: Doc) => allCollectionDocs.add(doc)); const set = new Set(); if (facetHeader === "tags") allCollectionDocs.forEach(child => Field.toString(child[facetHeader] as Field).split(":").forEach(key => set.add(key))); else allCollectionDocs.forEach(child => set.add(Field.toString(child[facetHeader] as Field))); diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 68ab3193b..f0db0b594 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -265,7 +265,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent { if (this.dataDoc[this.fieldKey + "-dictation"]) return; diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index eff400a98..76a5675de 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -84,7 +84,7 @@ export function buildKeymap>(schema: S, props: any, mapKey break; } return true; - } + }; const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 1eeadeedc..e4087cf43 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -257,7 +257,7 @@ export class Doc extends RefField { DocServer.GetRefField(this[Id], true); } }; - const writeMode = DocServer.getFieldWriteMode(fKey as string); + const writeMode = DocServer.getFieldWriteMode(fKey); if (fKey.startsWith("acl") || writeMode !== DocServer.WriteMode.Playground) { delete this[CachedUpdates][fKey]; await fn(); -- cgit v1.2.3-70-g09d2