aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/formattedText/FormattedTextBox.tsx
diff options
context:
space:
mode:
authorsrichman333 <sarah_n_richman@brown.edu>2023-10-10 16:21:41 -0400
committersrichman333 <sarah_n_richman@brown.edu>2023-10-10 16:21:41 -0400
commit9e91e6065333f03d3f3bf2c0d43b822d85344c78 (patch)
tree4c923fc8257b597d69700bee4c1a4e69d3cbe21a /src/client/views/nodes/formattedText/FormattedTextBox.tsx
parent368e33c076085b1b73f522ac88f548a2ad081c80 (diff)
parentd929c0511cae863412a398f426d9e5b7ca64e6d9 (diff)
merge?
Diffstat (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx147
1 files changed, 95 insertions, 52 deletions
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index bd969b527..90ebf5206 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -38,7 +38,7 @@ import { LinkManager } from '../../../util/LinkManager';
import { RTFMarkup } from '../../../util/RTFMarkup';
import { SelectionManager } from '../../../util/SelectionManager';
import { SnappingManager } from '../../../util/SnappingManager';
-import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionStackingView } from '../../collections/CollectionStackingView';
import { CollectionTreeView } from '../../collections/CollectionTreeView';
@@ -71,13 +71,14 @@ import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
import applyDevTools = require('prosemirror-dev-tools');
import React = require('react');
-const translateGoogleApi = require('translate-google-api');
+import { media_state } from '../AudioBox';
+import { setCORS } from 'google-translate-api-browser';
+// setting up cors-anywhere server address
+const translate = setCORS('http://cors-anywhere.herokuapp.com/');
export const GoogleRef = 'googleDocId';
type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
-export interface FormattedTextBoxProps {
- allowScroll?: boolean;
-}
+export interface FormattedTextBoxProps {}
@observer
export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps & FormattedTextBoxProps>() {
public static LayoutString(fieldStr: string) {
@@ -93,6 +94,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
static _userStyleSheet: any = addStyleSheet();
static _hadSelection: boolean = false;
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 _editorView: Opt<EditorView>;
@@ -153,11 +155,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@computed get layout_autoHeightMargins() {
return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins);
}
- @computed get _recording() {
- return this.dataDoc?.mediaState === 'recording';
+ @computed get _recordingDictation() {
+ return this.dataDoc?.mediaState === media_state.Recording;
}
- set _recording(value) {
- !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? 'recording' : undefined);
+ set _recordingDictation(value) {
+ !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? media_state.Recording : undefined);
}
@computed get config() {
this._keymap = buildKeymap(schema, this.props);
@@ -265,19 +267,36 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
AnchorMenu.Instance.OnAudio = (e: PointerEvent) => {
!this.layoutDoc.layout_showSidebar && this.toggleSidebar();
const anchor = this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true, true);
+
setTimeout(() => {
const target = this._sidebarRef.current?.anchorMenuClick(anchor);
if (target) {
anchor.followLinkAudio = true;
- DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target));
+ let stopFunc: any;
+ Doc.GetProto(target).mediaState = media_state.Recording;
+ Doc.GetProto(target).audioAnnoState = 'recording';
+ DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
+ let reactionDisposer = reaction(
+ () => target.mediaState,
+ action(dictation => {
+ if (!dictation) {
+ Doc.GetProto(target).audioAnnoState = 'stopped';
+ stopFunc();
+ reactionDisposer();
+ }
+ })
+ );
target.title = ComputedField.MakeFunction(`self["text_audioAnnotations_text"].lastElement()`);
}
});
};
- AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => {
- this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
- return undefined;
- });
+ AnchorMenu.Instance.Highlight = undoable(
+ action((color: string, isLinkButton: boolean) => {
+ this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
+ return undefined;
+ }),
+ 'highlght text'
+ );
AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
AnchorMenu.Instance.StartCropDrag = unimplementedFunction;
/**
@@ -372,7 +391,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
let link;
LinkManager.Links(this.dataDoc).forEach((l, i) => {
const anchor = (l.link_anchor_1 as Doc).annotationOn ? (l.link_anchor_1 as Doc) : (l.link_anchor_2 as Doc).annotationOn ? (l.link_anchor_2 as Doc) : undefined;
- if (anchor && (anchor.annotationOn as Doc).mediaState === 'recording') {
+ if (anchor && (anchor.annotationOn as Doc).mediaState === media_state.Recording) {
linkTime = NumCast(anchor._timecodeToShow /* audioStart */);
linkAnchor = anchor;
link = l;
@@ -439,7 +458,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
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 ? '...' : '');
+ this.dataDoc.title = (prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '')).trim();
if (str.startsWith('@') && str.length > 1) {
Doc.AddDocToList(Doc.MyPublishedDocs, undefined, this.rootDoc);
}
@@ -541,7 +560,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.annoDragData) {
de.complete.annoDragData.dropDocCreator = () => this.getAnchor(true);
- e.stopPropagation();
return true;
}
const dragData = de.complete.docDragData;
@@ -648,7 +666,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' });
}
if (highlights.includes('My Text')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { background: 'moccasin' });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace(/\./g, '').replace(/@/g, ''), { background: 'moccasin' });
}
if (highlights.includes('Todo Items')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-todo', { outline: 'black solid 1px' });
@@ -688,7 +706,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
toggleSidebar = (preview: boolean = false) => {
const prevWidth = 1 - this.sidebarWidth() / Number(getComputedStyle(this._ref.current!).width.replace('px', ''));
if (preview) this._showSidebar = true;
- else this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%';
+ else {
+ this.layoutDoc[this.SidebarKey + '_freeform_scale_max'] = 1;
+ this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_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);
};
@@ -844,7 +865,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
event: () => (this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI),
icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
});
- uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
+ !Doc.noviceMode && uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
!Doc.noviceMode &&
uicontrols.push({
description: 'Broadcast Message',
@@ -945,14 +966,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
breakupDictation = () => {
- if (this._editorView && this._recording) {
+ if (this._editorView && this._recordingDictation) {
this.stopDictation(true);
this._break = true;
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));
- if (this._recording) {
+ if (this._recordingDictation) {
this.recordDictation();
}
}
@@ -1242,13 +1263,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (!this.props.dontRegisterView) {
this._disposers.record = reaction(
- () => this._recording,
+ () => this._recordingDictation,
() => {
this.stopDictation(true);
- this._recording && this.recordDictation();
- }
+ this._recordingDictation && this.recordDictation();
+ },
+ { fireImmediately: true }
);
- if (this._recording) setTimeout(this.recordDictation);
+ if (this._recordingDictation) setTimeout(this.recordDictation);
}
var quickScroll: string | undefined = '';
this._disposers.scroll = reaction(
@@ -1546,8 +1568,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@action
componentWillUnmount() {
- if (this._recording) {
- this._recording = !this._recording;
+ if (this._recordingDictation) {
+ this._recordingDictation = !this._recordingDictation;
}
Object.values(this._disposers).forEach(disposer => disposer?.());
this.endUndoTypingBatch();
@@ -1561,7 +1583,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
onPointerDown = (e: React.PointerEvent): void => {
if ((e.nativeEvent as any).handledByInnerReactInstance) {
- return e.stopPropagation();
+ return; //e.stopPropagation();
} else (e.nativeEvent as any).handledByInnerReactInstance = true;
if (this.Document.forceActive) e.stopPropagation();
@@ -1585,7 +1607,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
});
}
- if (this._recording && !e.ctrlKey && e.button === 0) {
+ if (this._recordingDictation && !e.ctrlKey && e.button === 0) {
this.breakupDictation();
}
this._downX = e.clientX;
@@ -1593,10 +1615,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
FormattedTextBoxComment.textBox = this;
if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
- // stop propagation if not in sidebar
- // bcz: Change. drag selecting requires that preventDefault is NOT called. This used to happen in DocumentView,
- // but that's changed, so this shouldn't be needed.
- //e.stopPropagation(); // if the text box is selected, then it consumes all down events
+ // stop propagation if not in sidebar, otherwise nested boxes will lose focus to outer boxes.
+ e.stopPropagation(); // if the text box's content is active, then it consumes all down events
document.addEventListener('pointerup', this.onSelectEnd);
}
}
@@ -1614,8 +1634,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
else if (this.props.isContentActive(true)) {
const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
- // !this.props.isSelected(true) &&
- editor.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(pcords?.pos || 0))));
+ let xpos = pcords?.pos || 0;
+ 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');
@@ -1758,13 +1781,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const state = this._editorView!.state;
const curText = state.doc.textBetween(0, state.doc.content.size, ' \n');
- if (this.layoutDoc.sidebar_collectionType === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) {
+ if (this.layoutDoc[this.SidebarKey + '_type_collection'] === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) {
try {
- translateGoogleApi(curText, { from: 'en', to: 'es' }).then((result1: any) => {
+ translate(curText, { from: 'en', to: 'es' }).then((result1: any) => {
setTimeout(
() =>
- translateGoogleApi(result1[0], { from: 'es', to: 'en' }).then((result: any) => {
- this.dataDoc[this.fieldKey + '_translation'] = result1 + '\r\n\r\n' + result[0];
+ translate(result1.text, { from: 'es', to: 'en' }).then((result: any) => {
+ const tb = this._sidebarTagRef.current as FormattedTextBox;
+ tb._editorView?.dispatch(tb._editorView!.state.tr.insertText(result1.text + '\r\n\r\n' + result.text));
}),
1000
);
@@ -1802,7 +1826,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (state.selection.empty || !this._rules!.EnteringStyle) {
this._rules!.EnteringStyle = false;
}
- e.stopPropagation();
+ let stopPropagation = true;
for (var i = state.selection.from; i <= state.selection.to; i++) {
const node = state.doc.resolve(i);
if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
@@ -1821,6 +1845,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
case 'Tab':
e.preventDefault();
break;
+ case 'c':
+ this._editorView?.state.selection.empty && (stopPropagation = false);
+ break;
default:
if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
case ' ':
@@ -1830,6 +1857,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
break;
}
+ if (stopPropagation) e.stopPropagation();
this.startUndoTypingBatch();
};
ondrop = (e: React.DragEvent) => {
@@ -1887,7 +1915,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
.scale(1 / NumCast(this.layoutDoc._freeform_scale, 1) / (this.props.NativeDimScaling?.() || 1));
@computed get audioHandle() {
- return !this._recording ? null : (
+ return !this._recordingDictation ? null : (
<div
className="formattedTextBox-dictation"
onPointerDown={e =>
@@ -1896,7 +1924,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
e,
returnFalse,
emptyFunction,
- action(e => (this._recording = !this._recording))
+ action(e => (this._recordingDictation = !this._recordingDictation))
)
}>
<FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: 'red' }} icon={'microphone'} size="sm" />
@@ -1948,6 +1976,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
<div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
<ComponentTag
{...this.props}
+ ref={this._sidebarTagRef as any}
setContentView={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
@@ -1970,14 +1999,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
fitContentsToBox={this.fitContentsToBox}
noSidebar={true}
treeViewHideTitle={true}
- fieldKey={this.layoutDoc.sidebar_collectionType === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
+ fieldKey={this.layoutDoc[this.SidebarKey + '_type_collection'] === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
/>
</div>
);
};
return (
<div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
- {renderComponent(StrCast(this.layoutDoc.sidebar_collectionType))}
+ {renderComponent(StrCast(this.layoutDoc[this.SidebarKey + '_type_collection']))}
</div>
);
}
@@ -2025,9 +2054,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
@observable _isHovering = false;
onPassiveWheel = (e: WheelEvent) => {
+ if (e.clientX > this.ProseRef!.getBoundingClientRect().right) {
+ if (this.rootDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform) {
+ // if the scrolled freeform is a child of the sidebar component, we need to let the event go through
+ // so react can let the freeform view handle it. We prevent default to stop any containing views from scrolling
+ e.preventDefault();
+ }
+ return;
+ }
+
// 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() && !this.props.allowScroll) {
- if (!NumCast(this.layoutDoc._layout_scrollTop) && e.deltaY <= 0) e.preventDefault();
+ if (this.props.isContentActive()) {
+ // prevent default if selected || child is active but this doc isn't scrollable
+ if (
+ (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil(this.props.PanelHeight()) && //
+ (this.props.isSelected() || this.isAnyChildContentActive())
+ ) {
+ e.preventDefault();
+ }
e.stopPropagation();
}
};
@@ -2084,7 +2128,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
ref={this._ref}
style={{
cursor: this.props.isContentActive() ? 'text' : undefined,
- overflow: this.layout_autoHeight && this.props.CollectionFreeFormDocumentView?.() ? 'hidden' : undefined, //x this breaks viewing an layout_autoHeight doc in its own tab, or in the lightbox
height: this.props.height || (this.layout_autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),
pointerEvents: Doc.ActiveTool === InkTool.None && !this.props.onBrowseClick?.() ? undefined : 'none',
}}
@@ -2098,10 +2141,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
onPointerDown={this.onPointerDown}
onDoubleClick={this.onDoubleClick}>
<div
- className={`formattedTextBox-outer`}
+ className="formattedTextBox-outer"
ref={this._scrollRef}
style={{
- width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
+ width: this.props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
}}
onScroll={this.onScroll}
@@ -2119,9 +2162,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
/>
</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 ? null : this.sidebarHandle}
+ {this.noSidebar || this.Document._layout_noSidebar || this.props.dontSelectOnLoad || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
{this.audioHandle}
- {this.layoutDoc._layout_enableAltContentUI ? this.overlayAlternateIcon : null}
+ {this.layoutDoc._layout_enableAltContentUI && !this.layoutDoc._chromeHidden ? this.overlayAlternateIcon : null}
</div>
</div>
);