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.tsx121
1 files changed, 51 insertions, 70 deletions
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 63435eea8..b895043de 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,7 +1,7 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { isEqual } from 'lodash';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
import { observer } from 'mobx-react';
import { baseKeymap, selectAll } from 'prosemirror-commands';
import { history } from 'prosemirror-history';
@@ -45,7 +45,7 @@ import { LightboxView } from '../../LightboxView';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { SidebarAnnos } from '../../SidebarAnnos';
import { StyleProp } from '../../StyleProvider';
-import { DocumentViewInternal } from '../DocumentView';
+import { DocFocusOptions, DocumentViewInternal, OpenWhere } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { LinkDocPreview } from '../LinkDocPreview';
import { DashDocCommentView } from './DashDocCommentView';
@@ -87,7 +87,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
private _editorView: Opt<EditorView>;
- private _applyingChange: string = '';
+ public _applyingChange: string = '';
private _searchIndex = 0;
private _lastTimedMark: Mark | undefined = undefined;
private _cachedLinks: Doc[] = [];
@@ -231,7 +231,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
}
- getAnchor = () => this.makeLinkAnchor(undefined, 'add:right', undefined, 'Anchored Selection');
+ getAnchor = () => this.makeLinkAnchor(undefined, 'add:right', undefined, 'Anchored Selection', false);
@action
setupAnchorMenu = () => {
@@ -239,7 +239,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
AnchorMenu.Instance.OnClick = (e: PointerEvent) => {
!this.layoutDoc.showSidebar && this.toggleSidebar();
- this._sidebarRef.current?.anchorMenuClick(this.getAnchor());
+ setTimeout(() => this._sidebarRef.current?.anchorMenuClick(this.makeLinkAnchor(undefined, 'add:right', undefined, 'Anchored Selection', true))); // give time for sidebarRef to be created
};
AnchorMenu.Instance.OnAudio = (e: PointerEvent) => {
!this.layoutDoc.showSidebar && this.toggleSidebar();
@@ -499,12 +499,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const end = this._editorView.state.doc.nodeSize - 2;
this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
}
- if (FormattedTextBox.PasteOnLoad) {
- const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfOrigin');
- const pdfRegionId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfRegion');
- FormattedTextBox.PasteOnLoad = undefined;
- setTimeout(() => pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, undefined), 10);
- }
};
adoptAnnotation = (start: number, end: number, mark: Mark) => {
const view = this._editorView!;
@@ -692,12 +686,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
@undoBatch
- pinToPres = (anchor: Doc) => this.props.pinToPres(anchor);
+ pinToPres = (anchor: Doc) => this.props.pinToPres(anchor, {});
@undoBatch
- makePushpin = (anchor: Doc) => (anchor.isPushpin = !anchor.isPushpin);
+ makePushpin = (anchor: Doc) => (anchor.followLinkToggle = !anchor.followLinkToggle);
- isPushpin = (anchor: Doc) => BoolCast(anchor.isPushpin);
+ isPushpin = (anchor: Doc) => BoolCast(anchor.followLinkToggle);
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
@@ -897,9 +891,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
// TODO: nda -- Look at how link anchors are added
- makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string) {
+ makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string, noPreview?: boolean) {
const state = this._editorView?.state;
if (state) {
+ let selectedText = '';
const sel = state.selection;
const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
let tr = state.tr.addMark(sel.from, sel.to, splitter);
@@ -911,13 +906,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
const allAnchors = [{ href, title, anchorId: anchor[Id] }];
allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allAnchors ?? []));
- const link = state.schema.marks.linkAnchor.create({ allAnchors, title, location });
+ const link = state.schema.marks.linkAnchor.create({ allAnchors, title, location, noPreview });
tr = tr.addMark(pos, pos + node.nodeSize, link);
+ selectedText += (node as Node).textContent;
}
});
this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
+ anchor.text = selectedText;
return anchor;
}
return anchorDoc ?? this.rootDoc;
@@ -925,10 +922,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return anchorDoc ?? this.rootDoc;
}
- scrollFocus = (textAnchor: Doc, smooth: boolean) => {
+ scrollFocus = (textAnchor: Doc, options: DocFocusOptions) => {
let didToggle = false;
if (DocListCast(this.Document[this.fieldKey + '-sidebar']).includes(textAnchor) && !this.SidebarShown) {
- this.toggleSidebar(!smooth);
+ this.toggleSidebar(options.instant);
didToggle = true;
}
const textAnchorId = textAnchor[Id];
@@ -969,7 +966,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const content = (ret.frag as any)?.content;
if ((ret.frag.size > 2 || (content?.length && content[0].type === this._editorView.state.schema.nodes.audiotag)) && ret.start >= 0) {
- smooth && (this._focusSpeed = 500);
+ !options.instant && (this._focusSpeed = 500);
let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
if (ret.frag.firstChild) {
selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
@@ -1123,7 +1120,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const durationSecStr = viewTrans.match(/([0-9.]*)s/);
const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
if (duration) {
- smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0));
+ this._scrollStopper = smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0), 'ease', this._scrollStopper);
} else {
this._scrollRef.current.scrollTo({ top: pos });
}
@@ -1235,61 +1232,38 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
- const cbe = event as ClipboardEvent;
- const pdfDocId = cbe.clipboardData?.getData('dash/pdfOrigin');
- const pdfRegionId = cbe.clipboardData?.getData('dash/pdfRegion');
- return pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, slice) ? true : false;
+ const pdfAnchorId = (event as ClipboardEvent).clipboardData?.getData('dash/pdfAnchor');
+ return pdfAnchorId && this.addPdfReference(pdfAnchorId) ? true : false;
};
- addPdfReference = (pdfDocId: string, pdfRegionId: string, slice?: Slice) => {
+ addPdfReference = (pdfAnchorId: string) => {
const view = this._editorView!;
- if (pdfDocId && pdfRegionId) {
- DocServer.GetRefField(pdfDocId).then(pdfDoc => {
- DocServer.GetRefField(pdfRegionId).then(pdfRegion => {
- if (pdfDoc instanceof Doc && pdfRegion instanceof Doc) {
- setTimeout(async () => {
- const targetField = Doc.LayoutFieldKey(pdfDoc);
- const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + '-annotations']); // bcz: better to have the PDF's view handle updating its own annotations
- if (targetAnnotations) targetAnnotations.push(pdfRegion);
- else Doc.AddDocToList(pdfDoc[DataSym], targetField + '-annotations', pdfRegion);
- });
-
- const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, 'PDF pasted');
- if (link) {
- const linkId = link[Id];
- const quote = view.state.schema.nodes.blockquote.create({ content: addMarkToFrag(slice?.content || view.state.doc.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)) });
- const newSlice = new Slice(Fragment.from(quote), slice?.openStart || 0, slice?.openEnd || 0);
- if (slice) {
- view.dispatch(view.state.tr.replaceSelection(newSlice).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste'));
- } else {
- selectAll(view.state, (tx: Transaction) => view.dispatch(tx.replaceSelection(newSlice).scrollIntoView()));
- }
- }
+ if (pdfAnchorId) {
+ 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.marks.linkAnchor.create({
+ allAnchors: [{ href: `/doc/${this.rootDoc[Id]}`, title: this.rootDoc.title, anchorId: `${this.rootDoc[Id]}` }],
+ location: 'add:right',
+ title: `from: ${DocCast(pdfAnchor.context).title}`,
+ noPreview: true,
+ docref: false,
+ }),
+ view.state.schema.marks.pFontSize.create({ fontSize: '8px' }),
+ view.state.schema.marks.em.create({}),
+ ]),
+ ]);
+
+ const link = DocUtils.MakeLink({ doc: pdfAnchor }, { doc: this.rootDoc }, 'PDF pasted');
+ if (link) {
+ view.dispatch(view.state.tr.replaceSelectionWith(dashField, false).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste'));
}
- });
+ }
});
return true;
}
return false;
-
- function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) {
- const nodes: Node[] = [];
- frag.forEach(node => nodes.push(marker(node)));
- return Fragment.fromArray(nodes);
- }
-
- function addLinkMark(node: Node, title: string, linkId: string) {
- if (!node.isText) {
- const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));
- return node.copy(content);
- }
- const marks = [...node.marks];
- const linkIndex = marks.findIndex(mark => mark.type.name === 'link');
- 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);
- }
};
isActiveTab(el: Element | null | undefined) {
@@ -1310,6 +1284,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
});
}
_didScroll = false;
+ _scrollStopper: undefined | (() => void);
setupEditor(config: any, fieldKey: string) {
const curText = Cast(this.dataDoc[this.fieldKey], RichTextField, null) || StrCast(this.dataDoc[this.fieldKey]);
const rtfField = Cast((!curText && this.layoutDoc[this.fieldKey]) || this.dataDoc[fieldKey], RichTextField);
@@ -1328,7 +1303,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
const scrollPos = scrollRef.scrollTop + shift * self.props.ScreenToLocalTransform().Scale;
if (this._focusSpeed !== undefined) {
- scrollPos && smoothScroll(this._focusSpeed, scrollRef, scrollPos);
+ scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed, scrollRef, scrollPos, 'ease', this._scrollStopper));
} else {
scrollRef.scrollTo({ top: scrollPos });
}
@@ -1417,6 +1392,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []),
...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []),
];
+ if (FormattedTextBox.PasteOnLoad) {
+ const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
+ FormattedTextBox.PasteOnLoad = undefined;
+ pdfAnchorId && this.addPdfReference(pdfAnchorId);
+ }
}
FormattedTextBox.DontSelectInitialText = false;
}
@@ -1449,7 +1429,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const func = () => {
const docView = DocumentManager.Instance.getDocumentView(audiodoc);
if (!docView) {
- this.props.addDocTab(audiodoc, 'add:bottom');
+ this.props.addDocTab(audiodoc, OpenWhere.addBottom);
setTimeout(func);
} else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, 'number', null)); // bcz: would be nice to find the next audio tag in the doc and play until that
};
@@ -1495,7 +1475,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
!this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0))));
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);
+ FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
}
if (e.button === 0 && this.props.isSelected(true) && !e.altKey) {
@@ -1530,6 +1510,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
//applyDevTools.applyDevTools(this._editorView);
FormattedTextBox.Focused = this;
this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
+ this.startUndoTypingBatch();
};
@observable public static Focused: FormattedTextBox | undefined;