diff options
Diffstat (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 194 |
1 files changed, 122 insertions, 72 deletions
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 52e47a4ee..bb910737b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -21,7 +21,7 @@ import { List } from '../../../../fields/List'; import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from '../../../../fields/RichTextField'; import { ComputedField } from '../../../../fields/ScriptField'; -import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT'; @@ -68,8 +68,13 @@ import { RichTextRules } from './RichTextRules'; import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; // import * as applyDevTools from 'prosemirror-dev-tools'; + +interface FormattedTextBoxProps extends FieldViewProps { + onBlur?: () => void; // callback when text loses focus + autoFocus?: boolean; // whether text should get input focus when created +} @observer -export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface { +export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextBoxProps>() implements ViewBoxInterface { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } @@ -99,7 +104,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps private _dropDisposer?: DragManager.DragDropDisposer; private _recordingStart: number = 0; private _ignoreScroll = false; - private _hadDownFocus = false; private _focusSpeed: Opt<number>; private _keymap: any = undefined; private _rules: RichTextRules | undefined; @@ -200,7 +204,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId } - constructor(props: FieldViewProps) { + constructor(props: FormattedTextBoxProps) { super(props); makeObservable(this); FormattedTextBox.Instance = this; @@ -242,8 +246,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { - if (!pinProps && this._editorView?.state.selection.empty) return this.Document; - const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.Document.title), annotationOn: this.Document }); + const rootDoc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document); + if (!pinProps && this._editorView?.state.selection.empty) return rootDoc; + const anchor = Docs.Create.ConfigDocument({ title: StrCast(rootDoc.title), annotationOn: rootDoc }); this.addDocument(anchor); this._finishingLink = true; this.makeLinkAnchor(anchor, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation); @@ -321,8 +326,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps leafText = (node: Node) => { if (node.type === this._editorView?.state.schema.nodes.dashField) { - const refDoc = !node.attrs.docId ? this.Document : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc); - return Field.toJavascriptString(refDoc[node.attrs.fieldKey as string] as Field); + const refDoc = !node.attrs.docId ? DocCast(this.Document.rootDocument, this.Document) : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc); + const fieldKey = StrCast(node.attrs.fieldKey); + return ( + (node.attrs.hideKey ? '' : fieldKey + ':') + // + (node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as Field)) + ); } return ''; }; @@ -337,12 +346,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps tryUpdateDoc = (force: boolean) => { if (this._editorView && (this._editorView as any).docView) { const state = this._editorView.state; - const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc; + const dataDoc = this.dataDoc; const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText); const newJson = JSON.stringify(state.toJSON()); const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box const templateData = this.Document !== this.layoutDoc ? prevData : undefined; // the default text stored in a layout template const protoData = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype + const layoutData = this.layoutDoc.isTemplateDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text inherited from a prototype const effectiveAcl = GetEffectiveAcl(dataDoc); const removeSelection = (json: string | undefined) => json?.replace(/"selection":.*/, ''); @@ -359,25 +369,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } let unchanged = true; - if (this._applyingChange !== this.fieldKey && (force || removeSelection(newJson) !== removeSelection(prevData?.Data))) { + const textChange = newText !== prevData?.Text; // the Text string can change even if the RichText doesn't because dashFieldViews may return new strings as the data they reference changes + if (this._applyingChange !== this.fieldKey && (force || textChange || removeSelection(newJson) !== removeSelection(prevData?.Data))) { this._applyingChange = this.fieldKey; - const textChange = newText !== prevData?.Text; textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now()))); - if ((!prevData && !protoData) || newText || (!newText && !protoData)) { + if ((!prevData && !protoData && !layoutData) || newText || (!newText && !protoData && !layoutData)) { // 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 (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && removeSelection(newJson) !== removeSelection(prevData?.Data))) { + if (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && (textChange || removeSelection(newJson) !== removeSelection(prevData?.Data)))) { const numstring = NumCast(dataDoc[this.fieldKey], null); - dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : newText ? new RichTextField(newJson, newText) : undefined; + dataDoc[this.fieldKey] = + numstring !== undefined ? Number(newText) : newText || (DocCast(dataDoc.proto)?.[this.fieldKey] === undefined && this.layoutDoc[this.fieldKey] === undefined) ? new RichTextField(newJson, newText) : undefined; textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.Document, text: newText }); this._applyingChange = ''; // turning this off here allows a Doc to retrieve data from template if noTemplate below is changed to false - dataDoc[this.fieldKey + '_noTemplate'] = newText ? true : false; // mark the data field as being split from the template if it has been edited unchanged = false; } } else { // if we've deleted all the text in a note driven by a template, then restore the template data dataDoc[this.fieldKey] = undefined; - this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((protoData || prevData).Data))); - dataDoc[this.fieldKey + '_noTemplate'] = undefined; // mark the data field as not being split from any template it might have + this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(((layoutData !== prevData ? layoutData : undefined) ?? protoData).Data))); ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, text: newText }); unchanged = false; } @@ -443,16 +452,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps autoLink = () => { const newAutoLinks = new Set<Doc>(); - const oldAutoLinks = LinkManager.Links(this.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords); + const oldAutoLinks = LinkManager.Links(this.Document).filter( + link => + ((!Doc.isTemplateForField(this.Document) && + (!Doc.isTemplateForField(DocCast(link.link_anchor_1)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) && + (!Doc.isTemplateForField(DocCast(link.link_anchor_2)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_2), this.Document))) || + (Doc.isTemplateForField(this.Document) && (link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document))) && + link.link_relationship === LinkManager.AutoKeywords + ); // prettier-ignore if (this._editorView?.state.doc.textContent) { - const isNodeSel = this._editorView.state.selection instanceof NodeSelection; const f = this._editorView.state.selection.from; + const t = this._editorView.state.selection.to; var tr = this._editorView.state.tr as any; const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor; tr = tr.removeMark(0, tr.doc.content.size, autoAnch); - Doc.MyPublishedDocs.forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks))); - tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); + Doc.MyPublishedDocs.filter(term => term.title).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks))); + tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); this._editorView?.dispatch(tr); } oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(LinkManager.Instance.deleteLink); @@ -617,7 +633,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps docId: draggedDoc[Id], float: 'unset', }); - if (![dropActionType.embed, dropActionType.copy].includes(dropAction ?? dropActionType.move)) { + if (!de.embedKey && ![dropActionType.embed, dropActionType.copy].includes(dropAction ?? dropActionType.move)) { added = dragData.removeDocument?.(draggedDoc) ? true : false; } else { added = true; @@ -944,6 +960,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const options = cm.findByDescription('Options...'); const optionItems = options && 'subitems' in options ? options.subitems : []; + optionItems.push({ description: `Toggle auto update from template`, event: () => (this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate']), icon: 'star' }); optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' }); optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' }); this._props.renderDepth && @@ -958,8 +975,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), icon: this.Document._layout_autoHeight ? 'lock' : 'unlock', }); - optionItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: <BsMarkdownFill /> }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); + const help = cm.findByDescription('Help...'); + const helpItems = help && 'subitems' in help ? help.subitems : []; + helpItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: <BsMarkdownFill /> }); + !help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' }); this._downX = this._downY = Number.NaN; }; @@ -1011,7 +1031,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const state = this._editorView.state; const to = state.selection.to; const updated = TextSelection.create(state.doc, to, to); - this._editorView.dispatch(state.tr.setSelection(updated).insertText('\n', to)); + this._editorView.dispatch(state.tr.setSelection(updated).insert(to, state.schema.nodes.paragraph.create({}))); if (this._recordingDictation) { this.recordDictation(); } @@ -1235,9 +1255,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps ); this._disposers.editorState = reaction( () => { - const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc?.proto), this.fieldKey) ? DocCast(this.layoutDoc?.proto) : this?.dataDoc; - const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : dataDoc?.[this.fieldKey + '_noTemplate'] || !this.layoutDoc[this.fieldKey] ? dataDoc : this.layoutDoc; - return !whichDoc ? undefined : { data: Cast(whichDoc[this.fieldKey], RichTextField, null), str: Field.toString(DocCast(whichDoc[this.fieldKey]) ?? StrCast(whichDoc[this.fieldKey])) }; + const protoData = DocCast(this.dataDoc.proto)?.[this.fieldKey]; + const dataData = this.dataDoc[this.fieldKey]; + const layoutData = Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? undefined : this.layoutDoc[this.fieldKey]; + const dataTime = dataData ? DateCast(this.dataDoc[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0; + const layoutTime = layoutData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? DateCast(DocCast(this.layoutDoc)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0; + const protoTime = protoData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? DateCast(DocCast(this.dataDoc.proto)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0; + const recentData = dataTime >= layoutTime ? (protoTime >= dataTime ? protoData : dataData) : layoutTime >= protoTime ? layoutData : protoData; + const whichData = recentData ?? (this.layoutDoc.isTemplateDoc ? layoutData : protoData) ?? protoData; + return !whichData ? undefined : { data: RTFCast(whichData), str: Field.toString(DocCast(whichData) ?? StrCast(whichData)) }; }, incomingValue => { if (this._editorView && this._applyingChange !== this.fieldKey) { @@ -1247,11 +1273,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); this.tryUpdateScrollHeight(); } - } else if (incomingValue?.str) { - selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str))); + } else if (this._editorView.state.doc.textContent !== incomingValue?.str) { + selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue?.str ?? ''))); } } - } + }, + { fireImmediately: true } ); this._disposers.search = reaction( @@ -1347,15 +1374,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps DocServer.GetRefField(pdfAnchorId).then(pdfAnchor => { if (pdfAnchor instanceof Doc) { const dashField = view.state.schema.nodes.paragraph.create({}, [ - view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false }, undefined, [ + view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, hideValue: false, editable: false }, undefined, [ view.state.schema.marks.linkAnchor.create({ allAnchors: [{ href: `/doc/${this.Document[Id]}`, title: this.Document.title, anchorId: `${this.Document[Id]}` }], - title: `from: ${DocCast(pdfAnchor.embedContainer).title}`, + title: StrCast(pdfAnchor.title), noPreview: true, - docref: false, + docref: true, + fontSize: '8px', }), - view.state.schema.marks.pFontSize.create({ fontSize: '8px' }), - view.state.schema.marks.em.create({}), ]), ]); @@ -1489,6 +1515,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps FormattedTextBox.PasteOnLoad = undefined; pdfAnchorId && this.addPdfReference(pdfAnchorId); } + if (this._props.autoFocus) setTimeout(() => this._editorView!.focus()); // not sure why setTimeout is needed but editing dashFieldView's doesn't work without it. } // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. @@ -1561,7 +1588,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps (this.ProseRef?.children?.[0] as any).focus(); } } - this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false; if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { e.preventDefault(); } @@ -1572,9 +1598,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps onPointerUp = (e: React.PointerEvent): void => { const editor = this._editorView!; const state = editor?.state; - if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime) && !this._hadDownFocus) { - (this.ProseRef?.children[0] as HTMLElement)?.blur?.(); - } if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return; if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu(); else if (this._props.isContentActive() && !e.button) { @@ -1583,10 +1606,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) { xpos = xpos - 1; } - editor.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(xpos)))); - let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span> - while (target && !target.dataset?.targethrefs) target = target.parentElement; - FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true'); + let node: any; + try { + node = state.doc.nodeAt(xpos); + } catch (e) {} + if (node?.type !== schema.nodes.dashFieldView) { + editor.dispatch(state.tr.setSelection(TextSelection.near(state.doc.resolve(xpos)))); + let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span> + while (target && !target.dataset?.targethrefs) target = target.parentElement; + FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true'); + } else if (node) { + try { + editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos)))); + } catch (e) { + editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos - 1)))); + } + } } }; @action @@ -1607,10 +1642,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps e.stopPropagation(); } }; - setFocus = () => { - const pos = this._editorView?.state.selection.$from.pos || 1; - (this.ProseRef?.children?.[0] as any).focus(); - setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); + setFocus = (ipos?: number) => { + const pos = ipos ?? (this._editorView?.state.selection.$from.pos || 1); + setTimeout(() => this._editorView?.dispatch(this._editorView.state.tr.setSelection(TextSelection.near(this._editorView.state.doc.resolve(pos)))), 100); + setTimeout(() => (this.ProseRef?.children?.[0] as any).focus(), 200); }; @action onFocused = (e: React.FocusEvent): void => { @@ -1662,10 +1697,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this._forceUncollapse = false; clearStyleSheetRules(FormattedTextBox._bulletStyleSheet); const clickPos = this._editorView!.posAtCoords({ left: x, top: y }); - let olistPos = clickPos?.pos; + const clickPosVal = clickPos?.pos || 1; + let olistPos = clickPosVal; if (clickPos && olistPos && this._props.rootSelected?.()) { - const clickNode = this._editorView?.state.doc.nodeAt(olistPos); - const nodeBef = this._editorView?.state.doc.nodeAt(Math.max(0, olistPos - 1)); + const clickNode = this._editorView?.state.doc.resolve(olistPos).node(); + const nodeBef = this._editorView?.state.doc.resolve(Math.max(0, olistPos - 1)).node(); olistPos = nodeBef?.type === this._editorView?.state.schema.nodes.ordered_list ? olistPos - 1 : olistPos; let $olistPos = this._editorView?.state.doc.resolve(olistPos); let olistNode = (nodeBef !== null || clickNode?.type === this._editorView?.state.schema.nodes.list_item) && olistPos === clickPos?.pos ? clickNode : nodeBef; @@ -1675,18 +1711,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps $olistPos = this._editorView?.state.doc.resolve(($olistPos as any).path[($olistPos as any).path.length - 4]); } } - const listPos = this._editorView?.state.doc.resolve(clickPos.pos); - const listNode = this._editorView?.state.doc.nodeAt(clickPos.pos); + const maxSize = this._editorView?.state.doc.content.size ?? 0; + const listPos = this._editorView?.state.doc.resolve(Math.min(maxSize, clickPosVal === olistPos ? clickPosVal + 1 : clickPosVal)); + const listNode = listPos?.node(); if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list && listNode) { if (!highlightOnly) { if (selectOrderedList) { this._editorView.dispatch(this._editorView.state.tr.setSelection(new NodeSelection(selectOrderedList ? $olistPos! : listPos!))); } else { - const tr = this._editorView.state.tr.setNodeMarkup(clickPos.pos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility }); - this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, clickPos.pos))); + const nodePos = clickPosVal - (olistPos === clickPosVal ? 0 : 1); + if (this._editorView.state.doc.nodeAt(nodePos)) { + const tr = this._editorView.state.tr.setNodeMarkup(nodePos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility }); + this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, nodePos))); + } } } - addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ':hover:before', { background: 'lightgray' }); + addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ':hover:before', { background: 'gray' }); } } } @@ -1703,13 +1743,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps if (this.ProseRef?.children[0] !== e.nativeEvent.target) return; if (!(this.EditorView?.state.selection instanceof NodeSelection) || this.EditorView.state.selection.node.type !== this.EditorView.state.schema.nodes.footnote) { const stordMarks = this._editorView?.state.storedMarks?.slice(); - this.autoLink(); - if (this._editorView?.state.tr) { - const tr = stordMarks?.reduce((tr, m) => { - tr.addStoredMark(m); - return tr; - }, this._editorView.state.tr); - tr && this._editorView.dispatch(tr); + if (!(this.EditorView?.state.selection instanceof NodeSelection)) { + this.autoLink(); + if (this._editorView?.state.tr) { + const tr = stordMarks?.reduce((tr, m) => { + tr.addStoredMark(m); + return tr; + }, this._editorView.state.tr); + tr && this._editorView.dispatch(tr); + } } } if (RichTextMenu.Instance?.view === this._editorView && !this._props.rootSelected?.()) { @@ -1730,6 +1772,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.Document.title).length + 2))).deleteSelection()); }, 'titler'); } + // if the text box blurs and none of its contents are focused(), then pass the blur along + setTimeout(() => !this.ProseRef?.contains(document.activeElement) && this._props.onBlur?.()); }; onKeyDown = (e: React.KeyboardEvent) => { @@ -1775,7 +1819,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps default: if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break; case ' ': - if (e.code !== 'Space') { + if (e.code !== 'Space' && e.code !== 'Backspace') { [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.Document)) && this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); } @@ -1810,7 +1854,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps }; const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins); const scrollHeight = this.ProseRef && proseHeight; - if (this._props.setHeight && scrollHeight && !this._props.dontRegisterView) { + if (this._props.setHeight && !this._props.suppressSetHeight && scrollHeight && !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.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight); @@ -1934,11 +1978,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps </div> ); } - cycleAlternateText = () => { - if (this.layoutDoc._layout_enableAltContentUI) { - const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; - this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined; - } + cycleAlternateText = (skipHover?: boolean) => { + this.layoutDoc._layout_enableAltContentUI = true; + const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; + this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' && !skipHover ? 'alternate:hover' : undefined; }; @computed get overlayAlternateIcon() { const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; @@ -1973,7 +2016,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps ); } get fieldKey() { - const usePath = StrCast(this.layoutDoc[`${this._props.fieldKey}_usePath`]); + return this._fieldKey; + } + @computed get _fieldKey() { + const usePath = this._props.ignoreUsePath ? '' : StrCast(this.layoutDoc[`${this._props.fieldKey}_usePath`]); return this._props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering || this._props.isContentActive()) ? `_${usePath.replace(':hover', '')}` : ''); } @observable _isHovering = false; @@ -1994,6 +2040,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const height = Number(styleFromLayoutString.height?.replace('px', '')); // prevent default if selected || child is active but this doc isn't scrollable if ( + !Number.isNaN(height) && (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && // (this._props.rootSelected?.() || this.isAnyChildContentActive()) ) { @@ -2026,8 +2073,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps return styleFromLayoutString?.height === '0px' ? null : ( <div className="formattedTextBox" - onPointerEnter={action(() => (this._isHovering = true))} - onPointerLeave={action(() => (this._isHovering = false))} + onPointerEnter={action(() => { + this._isHovering = true; + this.layoutDoc[`_${this._props.fieldKey}_usePath`] && (this.Document.isHovering = true); + })} + onPointerLeave={action(() => (this.Document.isHovering = this._isHovering = false))} ref={r => { this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); this._oldWheel = r; |