diff options
Diffstat (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 112 |
1 files changed, 70 insertions, 42 deletions
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 911ec1560..4b1d76d00 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'; @@ -71,7 +71,8 @@ export interface FormattedTextBoxProps { xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView yPadding?: number; noSidebar?: boolean; - dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded + dontScale?: boolean; + dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded (and mark as not being associated with scrollTop document field) } export const GoogleRef = "googleDocId"; @@ -119,13 +120,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp public get EditorView() { return this._editorView; } public get SidebarKey() { return this.fieldKey + "-sidebar"; } - @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } + @computed get sidebarWidthPercent() { return this._showSidebar ? "20%" : StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); } @computed get autoHeight() { return this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight; } @computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); } @computed get scrollHeight() { return NumCast(this.rootDoc[this.fieldKey + "-scrollHeight"]); } - @computed get sidebarHeight() { return NumCast(this.rootDoc[this.SidebarKey + "-height"]); } + @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + "-height"]); } @computed get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; } + @computed get autoHeightMargins() { return this.titleHeight + NumCast(this.layoutDoc._autoHeightMargins); } @computed get _recording() { return this.dataDoc?.mediaState === "recording"; } set _recording(value) { !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? "recording" : undefined); @@ -215,6 +217,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView?.state && RichTextMenu.Instance.insertHighlight(color, this._editorView.state, this._editorView?.dispatch); return undefined; }); + AnchorMenu.Instance.onMakeAnchor = this.getAnchor; /** * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation. * It also initiates a Drag/Drop interaction to place the text annotation. @@ -251,7 +254,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("#")) { @@ -368,7 +371,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; const anchor = Docs.Create.TextanchorDocument(); const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!; - const allAnchors = [{ href: Utils.prepend("/doc/" + anchor[Id]), title: "a link", anchorId: anchor[Id] }]; + const allAnchors = [{ href: Doc.localServerPath(anchor), title: "a link", anchorId: anchor[Id] }]; const link = this._editorView!.state.schema.marks.linkAnchor.create({ allAnchors, title: "auto link", location }); tr = tr.addMark(flattened[i].from, flattened[i].to, link); }); @@ -429,6 +432,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.ProseRef = ele; this._dropDisposer?.(); ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc)); + // if (this.autoHeight) this.tryUpdateScrollHeight(); } @undoBatch @@ -532,7 +536,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" }); @@ -541,11 +545,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } + @observable _showSidebar = false; + @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; } + @action - toggleSidebar = () => { + toggleSidebar = (preview: boolean = false) => { const prevWidth = this.sidebarWidth(); - this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%"; - this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth); + if (preview) this._showSidebar = true; + else this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%"; + + this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth); } sidebarDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false); @@ -702,7 +711,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp let tr = state.tr.addMark(sel.from, sel.to, splitter); if (sel.from !== sel.to) { const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: this._editorView?.state.doc.textBetween(sel.from, sel.to) }); - const href = targetHref ?? Utils.prepend("/doc/" + anchor[Id]); + const href = targetHref ?? Doc.localServerPath(anchor); if (anchor !== anchorDoc) this.addDocument(anchor); tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { @@ -723,6 +732,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } scrollFocus = (textAnchor: Doc, smooth: boolean) => { + if (DocListCast(this.Document[this.fieldKey + "-sidebar"]).includes(textAnchor) && !this.SidebarShown) { + this.toggleSidebar(!smooth); + } const textAnchorId = textAnchor[Id]; const findAnchorFrag = (frag: Fragment, editor: EditorView) => { const nodes: Node[] = []; @@ -780,12 +792,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // Since we also monitor all component height changes, this will update the document's height. resetNativeHeight = (scrollHeight: number) => { const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight); - this.rootDoc[this.fieldKey + "-height"] = scrollHeight + this.titleHeight; + this.rootDoc[this.fieldKey + "-height"] = scrollHeight; if (nh) this.layoutDoc._nativeHeight = scrollHeight; } componentDidMount() { - this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. + !this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. this._cachedLinks = DocListCast(this.Document.links); this._disposers.breakupDictation = reaction(() => DocumentManager.Instance.RecordingEvent, this.breakupDictation); this._disposers.autoHeight = reaction(() => this.autoHeight, autoHeight => autoHeight && this.tryUpdateScrollHeight()); @@ -793,8 +805,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp ({ width, scrollHeight, autoHeight }) => width && autoHeight && this.resetNativeHeight(scrollHeight) ); this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on - () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight }), - ({ sidebarHeight, textHeight, autoHeight }) => autoHeight && this.props.setHeight(Math.max(sidebarHeight, textHeight))); + () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }), + ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => autoHeight && this.props.setHeight(marginsHeight + Math.max(sidebarHeight, textHeight))); this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks newLinks => { this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l)); @@ -871,12 +883,12 @@ 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), pos => { - if (!this._ignoreScroll && this._scrollRef.current) { + if (!this._ignoreScroll && this._scrollRef.current && !this.props.dontSelectOnLoad) { const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition); const durationMiliStr = viewTrans.match(/([0-9]*)ms/); const durationSecStr = viewTrans.match(/([0-9.]*)s/); @@ -1039,7 +1051,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } const marks = [...node.marks]; const linkIndex = marks.findIndex(mark => mark.type.name === "link"); - const allLinks = [{ href: Utils.prepend(`/doc/${linkId}`), title, linkId }]; + const allLinks = [{ href: Doc.globalServerPath(linkId), title, linkId }]; const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "add:right", title, docref: true }); marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link); return node.mark(marks); @@ -1199,7 +1211,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if ((e.nativeEvent as any).formattedHandled) { console.log("handled"); } - if (!(e.nativeEvent as any).formattedHandled && this.isContentActive(true)) { + if (!(e.nativeEvent as any).formattedHandled && this.props.isContentActive(true)) { const editor = this._editorView!; const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0)))); @@ -1391,6 +1403,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))); @@ -1413,23 +1433,32 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } onScroll = (e: React.UIEvent) => { if (!LinkDocPreview.LinkInfo && this._scrollRef.current) { - this._ignoreScroll = true; - this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop; - this._ignoreScroll = false; + if (!this.props.dontSelectOnLoad) { + this._ignoreScroll = true; + this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop; + this._ignoreScroll = false; + } } } - tryUpdateScrollHeight() { + tryUpdateScrollHeight = () => { if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) { - setTimeout(() => { // bcz: don't know why this is needed, but without it, the size of the textbox is too big as it includes the size of the title header. after the timeout, the size seems to get computed correctly. - const proseHeight = this.ProseRef?.scrollHeight || 0; - const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); + const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); + const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined; + if (children) { + var proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins); + var scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight; if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) { setScrollHeight(); + 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); + 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... } - }); + } } } fitToBox = () => this.props.Document._fitToBox; @@ -1452,7 +1481,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @computed get sidebarHandle() { TraceMobx(); const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length; - return (!annotated && !this.isContentActive()) ? (null) : <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} + return (!annotated && !this.props.isContentActive()) ? (null) : <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : "")) @@ -1464,15 +1493,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return ComponentTag === CollectionStackingView ? <SidebarAnnos ref={this._sidebarRef} {...this.props} - fieldKey={this.annotationKey} + fieldKey={this.fieldKey} rootDoc={this.rootDoc} layoutDoc={this.layoutDoc} dataDoc={this.dataDoc} + nativeWidth={NumCast(this.layoutDoc._nativeWidth)} + showSidebar={this.SidebarShown} PanelWidth={this.sidebarWidth} + setHeight={this.setSidebarHeight} sidebarAddDocument={this.sidebarAddDocument} moveDocument={this.moveDocument} removeDocument={this.removeDocument} - isContentActive={this.isContentActive} /> : <ComponentTag {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} @@ -1485,7 +1516,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp scaleField={this.SidebarKey + "-scale"} isAnnotationOverlay={false} select={emptyFunction} - isContentActive={this.isContentActive} scaling={this.sidebarContentScaling} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} removeDocument={this.sidebarRemDocument} @@ -1507,7 +1537,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp render() { TraceMobx(); const selected = this.props.isSelected(); - const active = this.isContentActive(); + const active = this.props.isContentActive(); const scale = this.props.hideOnLeave ? 1 : (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false); @@ -1517,16 +1547,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const selPad = Math.min(margins, 10); const padding = Math.max(margins + ((selected && !this.layoutDoc._singleLine) || minimal ? -selPad : 0), 0); const selPaddingClass = selected && !this.layoutDoc._singleLine && margins >= 10 ? "-selected" : ""; - const col = this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color); - const back = this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor); return ( <div className="formattedTextBox-cont" - onWheel={e => this.isContentActive() && e.stopPropagation()} + onWheel={e => this.props.isContentActive() && e.stopPropagation()} style={{ - transform: `scale(${scale})`, - transformOrigin: "top left", - width: `${100 / scale}%`, - height: `${100 / scale}%`, + transform: this.props.dontScale ? undefined : `scale(${scale})`, + transformOrigin: this.props.dontScale ? undefined : "top left", + width: this.props.dontScale ? undefined : `${100 / scale}%`, + height: this.props.dontScale ? undefined : `${100 / scale}%`, // overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined, ...this.styleFromLayoutString(scale) // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' > }}> @@ -1554,7 +1582,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp > <div className={`formattedTextBox-outer${selected ? "-selected" : ""}`} ref={this._scrollRef} style={{ - width: `calc(100% - ${this.sidebarWidthPercent})`, + width: this.props.dontSelectOnLoad ? "100%" : `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !active && !SnappingManager.GetIsDragging() ? "none" : undefined, overflow: this.layoutDoc._singleLine ? "hidden" : undefined, }} @@ -1566,8 +1594,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }} /> </div> - {(this.props.noSidebar || this.Document._noSidebar) || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection} - {(this.props.noSidebar || this.Document._noSidebar) || this.Document._singleLine ? (null) : this.sidebarHandle} + {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection} + {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || this.Document._singleLine ? (null) : this.sidebarHandle} {!this.layoutDoc._showAudio ? (null) : this.audioHandle} </div> </div > |