aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/util/RichTextSchema.tsx5
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx50
-rw-r--r--src/client/views/nodes/FormattedTextBoxComment.scss34
-rw-r--r--src/client/views/nodes/FormattedTextBoxComment.tsx78
4 files changed, 132 insertions, 35 deletions
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index f567d803e..ee0c0870a 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -343,7 +343,10 @@ export const marks: { [index: string]: MarkSpec } = {
let hideUsers = node.attrs.hide_users;
let hidden = hideUsers.indexOf(node.attrs.userid) !== -1 || (hideUsers.length === 0 && node.attrs.userid !== Doc.CurrentUserEmail);
return hidden ?
- ['span', { class: node.attrs.opened ? "userMarkOpen" : "userMark" }, 0] :
+ (node.attrs.opened ?
+ ['span', { class: "userMarkOpen" }, 0] :
+ ['span', { class: "userMark" }, ['span', { style: "font-size:2" }, 0]]
+ ) :
['span', 0];
}
},
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 146281f2b..2485760df 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -37,7 +37,7 @@ import { DocumentDecorations } from '../DocumentDecorations';
import { DictationManager } from '../../util/DictationManager';
import { ReplaceStep } from 'prosemirror-transform';
import { DocumentType } from '../../documents/DocumentTypes';
-import { number } from 'prop-types';
+import { selectionSizePlugin, findStartOfMark, findUserMark, findEndOfMark, findOtherUserMark, SelectionSizeTooltip } from './FormattedTextBoxComment';
library.add(faEdit);
library.add(faSmile, faTextHeight, faUpload);
@@ -325,7 +325,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
props: {
attributes: { class: "ProseMirror-example-setup-style" }
}
- })
+ }),
+ selectionSizePlugin
] : [
history(),
keymap(buildKeymap(schema)),
@@ -643,6 +644,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
this._heightReactionDisposer && this._heightReactionDisposer();
this._searchReactionDisposer && this._searchReactionDisposer();
document.removeEventListener("paste", this.paste);
+ this._editorView && this._editorView.destroy();
}
onPointerDown = (e: React.PointerEvent): void => {
@@ -707,45 +709,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
- findUserMark(marks: Mark[]) {
- return marks.find(m => m.attrs && m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
- }
- findStartOfMark(rpos: ResolvedPos) {
- let before = 0;
- let nbef = rpos.nodeBefore;
- while (nbef && this.findUserMark(nbef.marks)) {
- before += nbef.nodeSize;
- rpos = this._editorView!.state.doc.resolve(rpos.pos - nbef.nodeSize);
- rpos && (nbef = rpos.nodeBefore);
- }
- return before;
- }
- findEndOfMark(rpos: ResolvedPos) {
- let after = 0;
- let naft = rpos.nodeAfter;
- while (naft && this.findUserMark(naft.marks)) {
- after += naft.nodeSize;
- rpos = this._editorView!.state.doc.resolve(rpos.pos + naft.nodeSize);
- rpos && (naft = rpos.nodeAfter);
- }
- return after;
- }
-
onPointerUp = (e: React.PointerEvent): void => {
let view = this._editorView!;
const pos = view.posAtCoords({ left: e.clientX, top: e.clientY });
const rpos = pos && view.state.doc.resolve(pos.pos);
- if (pos && rpos && view.state.selection.$from === view.state.selection.$to) {
- let nbef = this.findStartOfMark(rpos);
- let naft = this.findEndOfMark(rpos);
+ let noselection = view.state.selection.$from === view.state.selection.$to;
+ if (pos && rpos) {
+ let nbef = findStartOfMark(rpos, view, findOtherUserMark);
+ let naft = findEndOfMark(rpos, view, findOtherUserMark);
const spos = view.state.doc.resolve(pos.pos - nbef);
const epos = view.state.doc.resolve(pos.pos + naft);
let ts = new TextSelection(spos, epos);
- let child = rpos.nodeBefore;
- let mark = child && this.findUserMark(child.marks);
- if (mark && child && nbef && naft) {
- let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: e.button === 2 ? Doc.CurrentUserEmail : mark.attrs.userid, opened: e.button === 2 ? false : !mark.attrs.opened });
- view.dispatch(view.state.tr.setSelection(ts).removeMark(ts.from, ts.to, nmark).addMark(ts.from, ts.to, nmark).setSelection(new TextSelection(epos, epos)));
+ let child = rpos.nodeBefore || rpos.nodeAfter;
+ let mark = child && findOtherUserMark(child.marks);
+ if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
+ let opened = e.button === 2 ? false : !mark.attrs.opened;
+ SelectionSizeTooltip.tooltip.style.display = opened ? "" : "none";
+ let mid = opened ? epos : view.state.doc.resolve((spos.pos + epos.pos) / 2);
+ let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: e.button === 2 ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened });
+ view.dispatch(view.state.tr.addMark(ts.from, ts.to, nmark).setSelection(new TextSelection(mid, mid)));
}
}
if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
diff --git a/src/client/views/nodes/FormattedTextBoxComment.scss b/src/client/views/nodes/FormattedTextBoxComment.scss
new file mode 100644
index 000000000..792cee182
--- /dev/null
+++ b/src/client/views/nodes/FormattedTextBoxComment.scss
@@ -0,0 +1,34 @@
+.FormattedTextBox-tooltip {
+ position: absolute;
+ pointer-events: none;
+ z-index: 20;
+ background: white;
+ border: 1px solid silver;
+ border-radius: 2px;
+ padding: 2px 10px;
+ margin-bottom: 7px;
+ -webkit-transform: translateX(-50%);
+ transform: translateX(-50%);
+ }
+ .FormattedTextBox-tooltip:before {
+ content: "";
+ height: 0; width: 0;
+ position: absolute;
+ left: 50%;
+ margin-left: -5px;
+ bottom: -6px;
+ border: 5px solid transparent;
+ border-bottom-width: 0;
+ border-top-color: silver;
+ }
+ .FormattedTextBox-tooltip:after {
+ content: "";
+ height: 0; width: 0;
+ position: absolute;
+ left: 50%;
+ margin-left: -5px;
+ bottom: -4.5px;
+ border: 5px solid transparent;
+ border-bottom-width: 0;
+ border-top-color: white;
+ } \ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
new file mode 100644
index 000000000..e88c85a86
--- /dev/null
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -0,0 +1,78 @@
+import { Plugin, EditorState, TextSelection } from "prosemirror-state"
+import './FormattedTextBoxComment.scss'
+import { DragManager } from "../../util/DragManager";
+import { ResolvedPos, Mark } from "prosemirror-model";
+import { EditorView } from "prosemirror-view";
+import { Doc } from "../../../new_fields/Doc";
+
+export let selectionSizePlugin = new Plugin({
+ view(editorView) { return new SelectionSizeTooltip(editorView); }
+})
+export function findOtherUserMark(marks: Mark[]): Mark | undefined {
+ return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
+}
+export function findUserMark(marks: Mark[]): Mark | undefined {
+ return marks.find(m => m.attrs.userid);
+}
+export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
+ let before = 0;
+ let nbef = rpos.nodeBefore;
+ while (nbef && finder(nbef.marks)) {
+ before += nbef.nodeSize;
+ rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
+ rpos && (nbef = rpos.nodeBefore);
+ }
+ return before;
+}
+export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
+ let after = 0;
+ let naft = rpos.nodeAfter;
+ while (naft && finder(naft.marks)) {
+ after += naft.nodeSize;
+ rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
+ rpos && (naft = rpos.nodeAfter);
+ }
+ return after;
+}
+
+export class SelectionSizeTooltip {
+ static tooltip: any;
+ constructor(view: any) {
+ if (!SelectionSizeTooltip.tooltip) {
+ SelectionSizeTooltip.tooltip = document.createElement("div");
+ SelectionSizeTooltip.tooltip.className = "FormattedTextBox-tooltip";
+ DragManager.Root().appendChild(SelectionSizeTooltip.tooltip);
+ }
+
+ this.update(view, undefined);
+ }
+
+
+ update(view: EditorView, lastState?: EditorState) {
+ let state = view.state;
+ // Don't do anything if the document/selection didn't change
+ if (lastState && lastState.doc.eq(state.doc) &&
+ lastState.selection.eq(state.selection)) return;
+
+ if (state.selection.$from) {
+ let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
+ let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
+ let child = state.selection.$from.nodeBefore;
+ let mark = child && findOtherUserMark(child.marks);
+ if (mark && child && nbef && naft && mark.attrs.opened && SelectionSizeTooltip.tooltip.offsetParent) {
+ SelectionSizeTooltip.tooltip.textContent = mark.attrs.userid;
+ // These are in screen coordinates
+ let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
+ // The box in which the tooltip is positioned, to use as base
+ let box = SelectionSizeTooltip.tooltip.offsetParent.getBoundingClientRect();
+ // Find a center-ish x position from the selection endpoints (when
+ // crossing lines, end may be more to the left)
+ let left = Math.max((start.left + end.left) / 2, start.left + 3);
+ SelectionSizeTooltip.tooltip.style.left = (left - box.left) + "px";
+ SelectionSizeTooltip.tooltip.style.bottom = (box.bottom - start.top) + "px";
+ }
+ }
+ }
+
+ destroy() { SelectionSizeTooltip.tooltip.style.display = "none" }
+}