aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/formattedText
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/formattedText')
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss9
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx57
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.scss3
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx2
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx49
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts6
6 files changed, 69 insertions, 57 deletions
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index dbf98a5e9..0d92d7062 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -12,7 +12,6 @@
.formattedTextBox-cont {
touch-action: none;
- cursor: text;
background: inherit;
padding: 0;
border-width: 0px;
@@ -38,6 +37,7 @@
}
}
+.formattedTextBox-outer-selected,
.formattedTextBox-outer {
position: relative;
overflow: auto;
@@ -45,6 +45,9 @@
width: 100%;
height: unset;
}
+.formattedTextBox-outer-selected {
+ cursor: text;
+}
.formattedTextBox-sidebar-handle {
position: absolute;
@@ -357,7 +360,6 @@ footnote::after {
.formattedTextBox-cont {
touch-action: none;
- cursor: text;
background: inherit;
padding: 0;
border-width: 0px;
@@ -383,6 +385,7 @@ footnote::after {
}
}
+ .formattedTextBox-outer-selected,
.formattedTextBox-outer {
position: relative;
overflow: auto;
@@ -399,7 +402,7 @@ footnote::after {
height: 35px;
background: lightgray;
border-radius: 20px;
- cursor:grabbing;
+ cursor: grabbing;
}
.formattedTextBox-sidebar,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a998386d8..4f8f46111 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -11,7 +11,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
import { ReplaceStep } from 'prosemirror-transform';
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../../fields/DateField';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit, AclAdmin } from "../../../../fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit, AclAdmin, UpdatingFromServer } from "../../../../fields/Doc";
import { documentSchema } from '../../../../fields/documentSchemas';
import applyDevTools = require("prosemirror-dev-tools");
import { removeMarkWithAttrs } from "./prosemirrorPatches";
@@ -22,7 +22,7 @@ import { RichTextField } from "../../../../fields/RichTextField";
import { RichTextUtils } from '../../../../fields/RichTextUtils';
import { makeInterface } from "../../../../fields/Schema";
import { Cast, DateCast, NumCast, StrCast, ScriptCast, BoolCast } from "../../../../fields/Types";
-import { TraceMobx, OVERRIDE_acl, GetEffectiveAcl } from '../../../../fields/util';
+import { TraceMobx, GetEffectiveAcl } from '../../../../fields/util';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents, OmitKeys } from '../../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
import { DocServer } from "../../../DocServer";
@@ -289,10 +289,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
} else {
-
- const json = JSON.parse(Cast(this.dataDoc[this.fieldKey], RichTextField)?.Data!);
- json.selection = state.toJSON().selection;
- this._editorView.updateState(EditorState.fromJSON(this.config, json));
+ const jsonstring = Cast(this.dataDoc[this.fieldKey], RichTextField)?.Data!;
+ if (jsonstring) {
+ const json = JSON.parse(jsonstring);
+ json.selection = state.toJSON().selection;
+ this._editorView.updateState(EditorState.fromJSON(this.config, json));
+ }
}
}
}
@@ -803,9 +805,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
tr = tr.addMark(pos, pos + node.nodeSize, link);
}
});
- OVERRIDE_acl(true);
+ 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));
- OVERRIDE_acl(false);
+ this.dataDoc[UpdatingFromServer] = false;
}
}
componentDidMount() {
@@ -883,7 +885,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
({ width, autoHeight }) => width !== undefined && setTimeout(() => this.tryUpdateHeight(), 0)
);
this._disposers.height = reaction(
- () => NumCast(this.layoutDoc._height),
+ () => Cast(this.layoutDoc._height, "number", null),
action(height => {
if (height !== undefined && height <= 20 && height < NumCast(this.layoutDoc._delayAutoHeight, 20)) {
this.layoutDoc._delayAutoHeight = height;
@@ -916,11 +918,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
async (scrollToLinkID) => {
const findLinkFrag = (frag: Fragment, editor: EditorView) => {
const nodes: Node[] = [];
+ let hadStart = start !== 0;
frag.forEach((node, index) => {
const examinedNode = findLinkNode(node, editor);
- if (examinedNode?.textContent) {
- nodes.push(examinedNode);
- !start && (start = index);
+ if (examinedNode?.node.textContent) {
+ nodes.push(examinedNode.node);
+ !hadStart && (start = index + examinedNode.start);
+ hadStart = true;
}
});
return { frag: Fragment.fromArray(nodes), start };
@@ -928,11 +932,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const findLinkNode = (node: Node, editor: EditorView) => {
if (!node.isText) {
const content = findLinkFrag(node.content, editor);
- return node.copy(content.frag);
+ return { node: node.copy(content.frag), start: content.start };
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor);
- return linkIndex !== -1 && marks[linkIndex].attrs.allLinks.find((item: { href: string }) => scrollToLinkID === item.href.replace(/.*\/doc\//, "")) ? node : undefined;
+ return linkIndex !== -1 && marks[linkIndex].attrs.allLinks.find((item: { href: string }) => scrollToLinkID === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined;
};
let start = 0;
@@ -1123,6 +1127,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
+ private isActiveTab(el: Element | null | undefined) {
+ while (el && el !== document.body) {
+ if (getComputedStyle(el).display === "none") return false;
+ el = el.parentNode as any;
+ }
+ return true;
+ }
+
private setupEditor(config: any, fieldKey: string) {
const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null);
const rtfField = Cast((!curText?.Text && this.layoutDoc[this.props.fieldKey]) || this.dataDoc[fieldKey], RichTextField);
@@ -1161,7 +1173,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad;
- if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad) {
+ if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
FormattedTextBox.SelectOnLoad = "";
this.props.select(false);
if (FormattedTextBox.SelectOnLoadChar && this._editorView) {
@@ -1311,6 +1323,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) {
y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height);
}
+ this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
setTimeout(() => window.document.activeElement === this.ProseRef?.children[0] && RichTextMenu.Instance.jumpTo(x, y), 250);
}
}
@@ -1418,7 +1431,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const self = this;
return new Plugin({
view(newView) {
- self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView);
+ runInAction(() => self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
return self.menuPlugin = new RichTextMenuPlugin({ editorProps: this.props });
}
});
@@ -1439,6 +1452,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
public static LiveTextUndo: UndoManager.Batch | undefined;
public static HadSelection: boolean = false;
onBlur = (e: any) => {
+ RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
this.endUndoTypingBatch();
this.doLinkOnDeselect();
@@ -1500,7 +1514,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
@action
tryUpdateHeight(limitHeight?: number) {
let scrollHeight = this.ProseRef?.scrollHeight || 0;
- this.rootDoc[this.fieldKey + "-height"] = 0;
if (this.props.renderDepth && this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight && 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
scrollHeight = scrollHeight * NumCast(this.layoutDoc._viewScale, 1);
if (limitHeight && scrollHeight > limitHeight) {
@@ -1531,7 +1544,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.rootDoc[this.fieldKey + "-height"] = finalHeight;
} catch (e) { console.log("Error in tryUpdateHeight"); }
}
- }
+ } else this.rootDoc[this.fieldKey + "-height"] = 0;
}
@computed get audioHandle() {
@@ -1542,14 +1555,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
@computed get sidebarHandle() {
- const annotated = DocListCast(this.dataDoc[this.annotationKey]).filter(d => d?.title).length;
+ const annotated = DocListCast(this.dataDoc[this.annotationKey]).filter(d => d?.author).length;
return !this.props.isSelected() && !(annotated && !this.sidebarWidth()) ? (null) :
<div className="formattedTextBox-sidebar-handle"
style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: annotated ? "lightBlue" : undefined }}
onPointerDown={this.sidebarDown}
- onClick={e => {
- console.log(e);
- }}
/>;
}
@@ -1598,7 +1608,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const scale = this.props.hideOnLeave ? 1 : this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1);
const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
const interactive = (Doc.GetSelectedTool() === InkTool.None || SnappingManager.GetIsDragging()) && !this.layoutDoc._isBackground;
- selected && setTimeout(() => this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props)); // need to make sure that we update a text box that is selected after updating the one that was deselected
if (!selected && FormattedTextBoxComment.textBox === this) { FormattedTextBoxComment.Hide(); }
const minimal = this.props.ignoreAutoHeight;
const margins = NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0);
@@ -1646,7 +1655,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
})}
onDoubleClick={this.onDoubleClick}
>
- <div className={`formattedTextBox-outer`} ref={this._scrollRef}
+ <div className={`formattedTextBox-outer${selclass}`} ref={this._scrollRef}
style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !active && !SnappingManager.GetIsDragging() ? "none" : undefined }}
onScroll={this.onscrolled} onDrop={this.ondrop} >
<div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
index 582ada6ce..81afba4d7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
@@ -29,9 +29,6 @@
font-style: italic;
color: rgb(95, 97, 102);
font-size: 10px;
- padding-bottom: 4px;
- margin-bottom: 5px;
-
}
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 5183e193b..f96fda861 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -260,7 +260,7 @@ export class FormattedTextBoxComment {
const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
if (anchor !== target && anchor && target) {
- target._scrollY = NumCast(anchor?.y);
+ target._scrollPY = NumCast(anchor?.y);
}
if (target?.author) {
FormattedTextBoxComment.showCommentbox("", view, nbef);
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 68239a8f1..2700c508b 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -32,7 +32,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
static Instance: RichTextMenu;
public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
- public view?: EditorView;
+ @observable public view?: EditorView;
public editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
public _brushMap: Map<string, Set<Mark>> = new Map();
@@ -153,11 +153,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
public delayHide = () => this._delayHide = true;
@action
- public updateMenu(view: EditorView, lastState: EditorState | undefined, props: any) {
- if (!view || !(view as any).TextView?.props.isSelected(true) || !view.hasFocus()) {
+ public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any) {
+ this.view = view;
+ if (!view || !view.hasFocus()) {
return;
}
- this.view = view;
props && (this.editorProps = props);
// Don't do anything if the document/selection didn't change
@@ -366,7 +366,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
);
}
- createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => {}): JSX.Element {
+ createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => void): JSX.Element {
const items = options.map(({ title, label, hidden, style }) => {
if (hidden) {
return <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
@@ -378,19 +378,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
function onChange(e: React.ChangeEvent<HTMLSelectElement>) {
e.stopPropagation();
e.preventDefault();
- self.TextView.endUndoTypingBatch();
- options.forEach(({ label, mark, command }) => {
- if (e.target.value === label && mark) {
- if (!self.TextView.props.isSelected(true)) {
- switch (mark.type) {
- case schema.marks.pFontFamily: setter(Doc.UserDoc().fontFamily = mark.attrs.family); break;
- case schema.marks.pFontSize: setter(Doc.UserDoc().fontSize = mark.attrs.fontSize.toString() + "px"); break;
+ self.TextView?.endUndoTypingBatch();
+ UndoManager.RunInBatch(() => {
+ options.forEach(({ label, mark, command }) => {
+ if (e.target.value === label && mark) {
+ if (!self.TextView?.props.isSelected(true)) {
+ switch (mark.type) {
+ case schema.marks.pFontFamily: setter(Doc.UserDoc().fontFamily = mark.attrs.family); break;
+ case schema.marks.pFontSize: setter(Doc.UserDoc().fontSize = mark.attrs.fontSize.toString() + "px"); break;
+ }
}
+ else self.view && mark && command(mark, self.view);
}
- else UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown");
- }
- });
+ });
+ }, "text mark dropdown");
}
+
return <Tooltip key={key} title={<div className="dash-tooltip">{key}</div>} placement="bottom">
<select onChange={onChange} value={activeOption}>{items}</select>
</Tooltip>;
@@ -424,9 +427,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
changeFontSize = (mark: Mark, view: EditorView) => {
- if ((this.view?.state.selection.$from.pos || 0) < 2) {
- this.TextView.layoutDoc._fontSize = mark.attrs.fontSize === Number(mark.attrs.fontSize) ? `${mark.attrs.fontSize}pt` : mark.attrs.fontSize;
- }
const fmark = view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize });
this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true);
view.focus();
@@ -434,9 +434,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
changeFontFamily = (mark: Mark, view: EditorView) => {
- if ((this.view?.state.selection.$from.pos || 0) < 2) {
- this.TextView.layoutDoc._fontFamily = mark.attrs.family;
- }
const fmark = view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family });
this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true);
view.focus();
@@ -984,8 +981,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
{this.collapsed ? this.getDragger() : (null)}
<div key="row 2" style={{ display: this.collapsed ? "none" : undefined }}>
<div className="richTextMenu-divider" key="divider 3" />,
- {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => this.activeFontSize = val)),
- this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => this.activeFontFamily = val)),
+ {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => {
+ this.activeFontSize = val;
+ SelectionManager.SelectedDocuments().map(dv => dv.props.Document._fontSize = val);
+ })),
+ this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => {
+ this.activeFontFamily = val;
+ SelectionManager.SelectedDocuments().map(dv => dv.props.Document._fontFamily = val);
+ })),
<div className="richTextMenu-divider" key="divider 4" />,
this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})),
this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 72b91c955..99334b6db 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -267,11 +267,11 @@ export class RichTextRules {
// [[fieldKey]] => show field
// [[fieldKey:Doc]] => show field of doc
new InputRule(
- new RegExp(/\[\[([a-zA-Z_@\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@\.\? \-0-9]+)?\]\]$/),
+ new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/),
(state, match, start, end) => {
const fieldKey = match[1];
- const rawdocid = match[3]?.substring(1);
- const docid = rawdocid ? (!rawdocid.includes("@") ? normalizeEmail(Doc.CurrentUserEmail) + "@" + rawdocid : rawdocid) : undefined;
+ const rawdocid = match[3];
+ const docid = rawdocid ? normalizeEmail((!rawdocid.includes("@") ? Doc.CurrentUserEmail + rawdocid : rawdocid.substring(1))) : undefined;
const value = match[2]?.substring(1);
if (!fieldKey) {
const linkId = Utils.GenerateGuid();