aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/formattedText/FormattedTextBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx126
1 files changed, 49 insertions, 77 deletions
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a46b19a85..43010b2ed 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -91,7 +91,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
private _sidebarRef = React.createRef<SidebarAnnos>();
private _sidebarTagRef = React.createRef<React.Component>();
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
- private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _scrollRef: HTMLDivElement | null = null;
private _editorView: Opt<EditorView>;
public _applyingChange: string = '';
private _inDrop = false;
@@ -291,7 +291,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
});
};
AnchorMenu.Instance.Highlight = undoable((color: string) => {
- this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
+ this._editorView?.state && RichTextMenu.Instance?.setHighlight(color);
return undefined;
}, 'highlght text');
AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
@@ -478,7 +478,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text);
if (
!this._props.dontRegisterView && // (this.Document.isTemplateForField === "text" || !this.Document.isTemplateForField) && // only update the title if the data document's data field is changing
- (title.startsWith('-') || title.startsWith('@')) &&
+ title.startsWith('-') &&
this._editorView &&
!this.dataDoc.title_custom &&
(Doc.LayoutFieldKey(this.Document) === this.fieldKey || this.fieldKey === 'text')
@@ -486,14 +486,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
let node = this._editorView.state.doc;
while (node.firstChild && node.firstChild.type.name !== 'text') node = node.firstChild;
const str = node.textContent;
- const prefix = str.startsWith('@') ? '' : '-';
+ const prefix = '-';
const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title));
if (!(cfield instanceof ComputedField)) {
this.dataDoc.title = (prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '')).trim();
- if (str.startsWith('@') && str.length > 1) {
- Doc.AddToMyPublished(this.Document);
- }
}
}
};
@@ -510,7 +507,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
const editorView = this._editorView;
if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) {
- const autoLinkTerm = StrCast(target.title).replace(/^@/, '');
+ const autoLinkTerm = Field.toString(target.title as Field).replace(/^@/, '');
var alink: Doc | undefined;
this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
if (
@@ -887,7 +884,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
event: undoBatch(() => {
this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
this.Document.layout_fieldKey = 'layout_meta';
- setTimeout(() => (this.layoutDoc._headerHeight = this.layoutDoc._layout_autoHeightMargins = 50), 50);
+ setTimeout(() => (this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50), 50);
}),
icon: 'eye',
});
@@ -1309,25 +1306,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
);
if (this._recordingDictation) setTimeout(this.recordDictation);
}
- var quickScroll: string | undefined = '';
this._disposers.scroll = reaction(
() => NumCast(this.layoutDoc._layout_scrollTop),
pos => {
- 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/);
- const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
- if (duration) {
- this._scrollStopper = smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0), 'ease', this._scrollStopper);
- } else {
- this._scrollRef.current.scrollTo({ top: pos });
- }
+ if (!this._ignoreScroll && this._scrollRef) {
+ const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]+)(m?)s/);
+ const duration = Number(durationStr?.[1]) * (durationStr?.[2] ? 1 : 1000);
+ this._scrollStopper = smoothScroll(duration || 0, this._scrollRef, Math.abs(pos || 0), 'ease', this._scrollStopper);
}
},
{ fireImmediately: true }
);
- quickScroll = undefined;
this.tryUpdateScrollHeight();
setTimeout(this.tryUpdateScrollHeight, 250);
}
@@ -1419,14 +1408,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
handleScrollToSelection: editorView => {
const docPos = editorView.coordsAtPos(editorView.state.selection.to);
const viewRect = self._ref.current!.getBoundingClientRect();
- const scrollRef = self._scrollRef.current;
+ const scrollRef = self._scrollRef;
const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined;
const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
const scrollPos = scrollRef.scrollTop + shift * self.ScreenToLocalBoxXf().Scale;
if (this._focusSpeed !== undefined) {
- scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed, scrollRef, scrollPos, 'ease', this._scrollStopper));
+ setTimeout(() => scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper)));
} else {
scrollRef.scrollTo({ top: scrollPos });
}
@@ -1496,6 +1485,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
} else if (curText && !FormattedTextBox.DontSelectInitialText) {
selectAll(this._editorView.state, this._editorView?.dispatch);
+ this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
}
}
if (selectOnLoad) {
@@ -1589,32 +1579,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
document.removeEventListener('pointerup', this.onSelectEnd);
};
onPointerUp = (e: React.PointerEvent): void => {
- const editor = this._editorView!;
- const state = editor?.state;
- 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) {
- const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
- let xpos = pcords?.pos || 0;
- while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
- xpos = xpos - 1;
- }
- 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))));
- }
- }
+ const state = this.EditorView?.state;
+ if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) {
+ if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
+ let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
+ for (let target = e.target as any; target && !target.dataset?.targethrefs; target = target.parentElement);
+ while (target && !target.dataset?.targethrefs) target = target.parentElement;
+ FormattedTextBoxComment.update(this, this.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
}
};
@action
@@ -1751,20 +1722,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
}
FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
+
+ // this is the markdown for @<published name> document publishing to Doc.myPublishedDocs
+ const match = RTFCast(this.Document[this.fieldKey])?.Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
+ if (match) {
+ this.dataDoc.title_custom = true;
+ this.dataDoc.title = match[1]; // this triggers the collectionDockingView to publish this Doc
+ this.EditorView?.dispatch(this.EditorView?.state.tr.deleteRange(0, match[1].length + 1));
+ }
+
this.endUndoTypingBatch();
FormattedTextBox.LiveTextUndo?.end();
FormattedTextBox.LiveTextUndo = undefined;
const state = this._editorView!.state;
- if (StrCast(this.Document.title).startsWith('@') && !this.dataDoc.title_custom) {
- UndoManager.RunInBatch(() => {
- this.dataDoc.title_custom = true;
- this.dataDoc.layout_showTitle = 'title';
- const tr = this._editorView!.state.tr;
- 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?.());
};
@@ -1799,7 +1771,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
(document.activeElement as any).blur?.();
SelectionManager.DeselectAll();
- RichTextMenu.Instance.updateMenu(undefined, undefined, undefined, undefined);
+ RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
return;
case 'Enter':
this.insertTime();
@@ -1826,26 +1798,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash.
};
onScroll = (e: React.UIEvent) => {
- if (!LinkInfo.Instance?.LinkInfo && this._scrollRef.current) {
- if (!this._props.dontSelectOnLoad) {
- this._ignoreScroll = true;
- this.layoutDoc._layout_scrollTop = this._scrollRef.current.scrollTop;
- this._ignoreScroll = false;
- e.stopPropagation();
- e.preventDefault();
- }
+ if (!LinkInfo.Instance?.LinkInfo && this._scrollRef) {
+ this._ignoreScroll = true;
+ this.layoutDoc._layout_scrollTop = this._scrollRef.scrollTop;
+ this._ignoreScroll = false;
+ e.stopPropagation();
+ e.preventDefault();
}
};
tryUpdateScrollHeight = () => {
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 && !SnappingManager.IsDragging) {
- const toNum = (val: string) => Number(val.replace('px', '').replace('auto', '0'));
- const toHgt = (node: Element) => {
+ const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0;
+ const toNum = (val: string) => Number(val.replace('px', ''));
+ const toHgt = (node: Element): number => {
const { height, marginTop, marginBottom } = getComputedStyle(node);
- return toNum(height) + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom));
+ const childHeight = height === 'auto' ? getChildrenHeights(Array.from(node.children)) : toNum(height);
+ return childHeight + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom));
};
- const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins);
+ const proseHeight = !this.ProseRef ? 0 : getChildrenHeights(children);
const scrollHeight = this.ProseRef && proseHeight;
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
@@ -2029,12 +2001,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
if (this._props.isContentActive()) {
const scale = this._props.NativeDimScaling?.() || 1;
- const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
+ const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
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._scrollRef?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
(this._props.rootSelected?.() || this.isAnyChildContentActive())
) {
e.preventDefault();
@@ -2062,7 +2034,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
const paddingX = NumCast(this.layoutDoc._xMargin, this._props.xPadding || 0);
const paddingY = NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
- const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
+ const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
return styleFromLayoutString?.height === '0px' ? null : (
<div
className="formattedTextBox"
@@ -2112,9 +2084,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
onDoubleClick={this.onDoubleClick}>
<div
className="formattedTextBox-outer"
- ref={this._scrollRef}
+ ref={r => (this._scrollRef = r)}
style={{
- width: this._props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
+ width: this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
}}
onScroll={this.onScroll}
@@ -2131,8 +2103,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}}
/>
</div>
- {this.noSidebar || this._props.dontSelectOnLoad || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
- {this.noSidebar || this.Document._layout_noSidebar || this._props.dontSelectOnLoad || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
+ {this.noSidebar || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
+ {this.noSidebar || this.Document._layout_noSidebar || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
{this.audioHandle}
{this.layoutDoc._layout_enableAltContentUI && !this.layoutDoc._chromeHidden ? this.overlayAlternateIcon : null}
</div>