aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/FormattedTextBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/FormattedTextBox.tsx')
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx149
1 files changed, 110 insertions, 39 deletions
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index eb4718581..63a16f90c 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -80,6 +80,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
private _nodeClicked: any;
private _undoTyping?: UndoManager.Batch;
private _searchReactionDisposer?: Lambda;
+ private _scrollToRegionReactionDisposer: Opt<IReactionDisposer>;
private _reactionDisposer: Opt<IReactionDisposer>;
private _textReactionDisposer: Opt<IReactionDisposer>;
private _heightReactionDisposer: Opt<IReactionDisposer>;
@@ -138,6 +139,52 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (this.props.isOverlay) {
DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined);
}
+
+ this._scrollToRegionReactionDisposer = reaction(
+ () => StrCast(this.props.Document.scrollToLinkID),
+ async (scrollToLinkID) => {
+ let findLinkFrag = (frag: Fragment, editor: EditorView) => {
+ const nodes: Node[] = [];
+ frag.forEach((node, index) => {
+ let examinedNode = findLinkNode(node, editor);
+ if (examinedNode && examinedNode.textContent) {
+ nodes.push(examinedNode);
+ start += index;
+ }
+ });
+ return { frag: Fragment.fromArray(nodes), start: start };
+ };
+ let findLinkNode = (node: Node, editor: EditorView) => {
+ if (!node.isText) {
+ const content = findLinkFrag(node.content, editor);
+ return node.copy(content.frag);
+ }
+ const marks = [...node.marks];
+ const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
+ return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined;
+ };
+
+ let start = -1;
+ if (this._editorView && scrollToLinkID) {
+ let editor = this._editorView;
+ let ret = findLinkFrag(editor.state.doc.content, editor);
+
+ if (ret.frag.size > 2 && ((!this.props.isOverlay && !this.props.isSelected()) || (this.props.isSelected() && this.props.isOverlay))) {
+ 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 + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
+ }
+ editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
+ const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
+ setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0);
+ setTimeout(() => this.unhighlightSearchTerms(), 2000);
+ }
+ this.props.Document.scrollToLinkID = undefined;
+ }
+
+ },
+ { fireImmediately: true }
+ );
}
public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
@@ -696,6 +743,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
this._editorView && this._editorView.destroy();
this._editorView = new EditorView(this._proseRef, {
state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
+ handleScrollToSelection: (editorView) => {
+ let ref = editorView.domAtPos(editorView.state.selection.from);
+ let refNode = ref.node as any;
+ while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement;
+ let r1 = refNode && refNode.getBoundingClientRect();
+ let r3 = self._ref.current!.getBoundingClientRect();
+ r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale);
+ return true;
+ },
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); },
@@ -736,6 +792,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
componentWillUnmount() {
+ this._scrollToRegionReactionDisposer && this._scrollToRegionReactionDisposer();
this._rulesReactionDisposer && this._rulesReactionDisposer();
this._reactionDisposer && this._reactionDisposer();
this._proxyReactionDisposer && this._proxyReactionDisposer();
@@ -758,6 +815,40 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
e.stopPropagation();
}
let ctrlKey = e.ctrlKey;
+ if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
+ e.preventDefault();
+ }
+ }
+
+ onPointerUp = (e: React.PointerEvent): void => {
+ FormattedTextBoxComment.textBox = this;
+ if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
+ e.stopPropagation();
+ }
+ }
+
+ @action
+ onFocused = (e: React.FocusEvent): void => {
+ document.removeEventListener("keypress", this.recordKeyHandler);
+ document.addEventListener("keypress", this.recordKeyHandler);
+ this.tryUpdateHeight();
+ if (!this.props.isOverlay) {
+ FormattedTextBox.InputBoxOverlay = this;
+ } else {
+ if (this._ref.current) {
+ this._ref.current.scrollTop = FormattedTextBox.InputBoxOverlayScroll;
+ }
+ }
+ }
+ onPointerWheel = (e: React.WheelEvent): void => {
+ // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
+ if (this.props.isSelected() || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
+ e.stopPropagation();
+ }
+ }
+
+ onClick = (e: React.MouseEvent): void => {
+ let ctrlKey = e.ctrlKey;
if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) {
let href = (e.target as any).href;
let location: string;
@@ -771,8 +862,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos);
if (node) {
let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link);
- href = link && link.attrs.href;
- location = link && link.attrs.location;
+ if (link && !(link.attrs.docref && link.attrs.title)) { // bcz: getting hacky. this indicates that we clicked on a PDF excerpt quotation. In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above).
+ href = link && link.attrs.href;
+ location = link && link.attrs.location;
+ }
}
if (href) {
if (href.indexOf(Utils.prepend("/doc/")) === 0) {
@@ -783,14 +876,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
let proto = Doc.GetProto(linkDoc);
let targetContext = await Cast(proto.targetContext, Doc);
let jumpToDoc = await Cast(linkDoc.anchor2, Doc);
+
if (jumpToDoc) {
if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
-
DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page)));
return;
}
}
- if (targetContext) {
+ if (targetContext && (!jumpToDoc || targetContext !== await jumpToDoc.annotationOn)) {
DocumentManager.Instance.jumpToDocument(targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
} else if (jumpToDoc) {
DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
@@ -812,39 +905,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
- if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
- e.preventDefault();
- }
- }
-
- onPointerUp = (e: React.PointerEvent): void => {
- FormattedTextBoxComment.textBox = this;
- if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
- e.stopPropagation();
- }
- }
-
- @action
- onFocused = (e: React.FocusEvent): void => {
- document.removeEventListener("keypress", this.recordKeyHandler);
- document.addEventListener("keypress", this.recordKeyHandler);
- this.tryUpdateHeight();
- if (!this.props.isOverlay) {
- FormattedTextBox.InputBoxOverlay = this;
- } else {
- if (this._ref.current) {
- this._ref.current.scrollTop = FormattedTextBox.InputBoxOverlayScroll;
- }
- }
- }
- onPointerWheel = (e: React.WheelEvent): void => {
- // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
- if (this.props.isSelected() || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
- e.stopPropagation();
- }
- }
-
- onClick = (e: React.MouseEvent): void => {
// this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them.
if (this.props.isSelected() && e.nativeEvent.offsetX < 40) {
let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
@@ -852,7 +912,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
let node = this._editorView!.state.doc.nodeAt(pos.pos);
let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined;
if (node === this._nodeClicked && node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) {
- this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility }));
+ let hit = this._editorView!.domAtPos(pos.pos).node as any;
+ let beforeEle = document.querySelector("." + hit.className) as Element;
+ let before = beforeEle ? window.getComputedStyle(beforeEle, ':before') : undefined;
+ let beforeWidth = before ? Number(before.getPropertyValue('width').replace("px", "")) : undefined;
+ if (beforeWidth && e.nativeEvent.offsetX < beforeWidth) {
+ let ol = this._editorView!.state.doc.nodeAt(pos.pos - 2) ? this._editorView!.state.doc.nodeAt(pos.pos - 2) : undefined;
+ if (ol && ol.type === schema.nodes.ordered_list && !e.shiftKey) {
+ this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection(this._editorView!.state.doc.resolve(pos.pos - 2))));
+ } else {
+ this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility }));
+ }
+ }
}
}
}
@@ -903,7 +974,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (e.key === "Tab" || e.key === "Enter") {
e.preventDefault();
}
- this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }));
+ this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })));
if (!this._undoTyping) {
this._undoTyping = UndoManager.StartBatch("undoTyping");