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.tsx304
1 files changed, 222 insertions, 82 deletions
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a76f9623a..b5aa34a29 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,7 +1,8 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
import { isEqual } from 'lodash';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { baseKeymap, selectAll } from 'prosemirror-commands';
import { history } from 'prosemirror-history';
@@ -11,7 +12,7 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, Doc, DocListCast, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, CssSym, Doc, DocListCast, Field, ForceServerWrite, HeightSym, Opt, StrListCast, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { PrefetchProxy } from '../../../../fields/Proxy';
@@ -20,15 +21,18 @@ import { RichTextUtils } from '../../../../fields/RichTextUtils';
import { ComputedField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
+import { gptAPICall, GPTCallType, gptImageCall } from '../../../apis/gpt/GPT';
import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
+import { Networking } from '../../../Network';
import { DictationManager } from '../../../util/DictationManager';
import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager } from '../../../util/DragManager';
import { MakeTemplate } from '../../../util/DropConverter';
+import { IsFollowLinkScript } from '../../../util/LinkFollower';
import { LinkManager } from '../../../util/LinkManager';
import { SelectionManager } from '../../../util/SelectionManager';
import { SnappingManager } from '../../../util/SnappingManager';
@@ -64,10 +68,10 @@ import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
import applyDevTools = require('prosemirror-dev-tools');
import React = require('react');
+import { RTFMarkup } from '../../../util/RTFMarkup';
+import { List } from '../../../../fields/List';
const translateGoogleApi = require('translate-google-api');
-
export const GoogleRef = 'googleDocId';
-
type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@observer
@@ -78,7 +82,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
public static Instance: FormattedTextBox;
public static LiveTextUndo: UndoManager.Batch | undefined;
- static _globalHighlights: string[] = ['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items'];
+ static _globalHighlightsCache: string = '';
+ static _globalHighlights = new ObservableSet<string>(['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items']);
static _highlightStyleSheet: any = addStyleSheet();
static _bulletStyleSheet: any = addStyleSheet();
static _userStyleSheet: any = addStyleSheet();
@@ -151,7 +156,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
@computed get config() {
this._keymap = buildKeymap(schema, this.props);
- this._rules = new RichTextRules(this.props.Document, this);
+ this._rules = new RichTextRules(this.rootDoc, this);
return {
schema,
plugins: [
@@ -170,6 +175,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
}
+ // State for GPT
+ @observable
+ private gptRes: string = '';
+
public static PasteOnLoad: ClipboardEvent | undefined;
public static SelectOnLoad = '';
public static DontSelectInitialText = false; // whether initial text should be selected or not
@@ -186,13 +195,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return '';
}
public static GetDocFromUrl(url: string) {
- return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docid
+ return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
}
constructor(props: any) {
super(props);
FormattedTextBox.Instance = this;
- this.updateHighlights();
this._recordingStart = Date.now();
}
@@ -306,11 +314,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
accumTags.push(node.attrs.fieldKey);
}
});
- const curTags = Object.keys(dataDoc).filter(key => key.startsWith('#'));
- const added = accumTags.filter(tag => !curTags.includes(tag));
- const removed = curTags.filter(tag => !accumTags.includes(tag));
- removed.forEach(r => (dataDoc[r] = undefined));
- added.forEach(a => (dataDoc[a] = a));
+ dataDoc.tags = accumTags.length ? new List<string>(Array.from(new Set<string>(accumTags))) : undefined;
let unchanged = true;
if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) {
@@ -454,7 +458,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
alink =
alink ??
(LinkManager.Links(this.Document).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) ||
- DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!);
+ DocUtils.MakeLink(this.props.Document, target, { linkRelationship: LinkManager.AutoKeywords })!);
newAutoLinks.add(alink);
const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
@@ -540,17 +544,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
// embed document when dragg marked as embed
} else if (de.embedKey) {
const target = dragData.droppedDocuments[0];
- target._fitContentsToBox = true;
const node = schema.nodes.dashDoc.create({
width: target[WidthSym](),
height: target[HeightSym](),
title: 'dashDoc',
- docid: target[Id],
+ docId: target[Id],
float: 'unset',
});
if (!['alias', 'copy'].includes((dragData.dropAction ?? '') as any)) {
dragData.removeDocument?.(dragData.draggedDocuments[0]);
}
+ target._fitContentsToBox = true;
+ target.context = this.rootDoc;
const view = this._editorView!;
view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
e.stopPropagation();
@@ -603,45 +608,46 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return ret;
}
- updateHighlights = () => {
- const highlights = FormattedTextBox._globalHighlights;
+ updateHighlights = (highlights: string[]) => {
+ if (Array.from(highlights).join('') === FormattedTextBox._globalHighlightsCache) return;
+ setTimeout(() => (FormattedTextBox._globalHighlightsCache = Array.from(highlights).join('')));
clearStyleSheetRules(FormattedTextBox._userStyleSheet);
- if (highlights.indexOf('Audio Tags') === -1) {
+ if (highlights.includes('Audio Tags')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, '');
}
- if (highlights.indexOf('Text from Others') !== -1) {
+ if (highlights.includes('Text from Others')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' });
}
- if (highlights.indexOf('My Text') !== -1) {
+ if (highlights.includes('My Text')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { background: 'moccasin' });
}
- if (highlights.indexOf('Todo Items') !== -1) {
+ if (highlights.includes('Todo Items')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-todo', { outline: 'black solid 1px' });
}
- if (highlights.indexOf('Important Items') !== -1) {
+ if (highlights.includes('Important Items')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-important', { 'font-size': 'larger' });
}
- if (highlights.indexOf('Bold Text') !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror strong > span', { 'font-size': 'large' }, '');
- addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror :not(strong > span)', { 'font-size': '0px' }, '');
+ if (highlights.includes('Bold Text')) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner .ProseMirror strong > span', { 'font-size': 'large' }, '');
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner .ProseMirror :not(strong > span)', { 'font-size': '0px' }, '');
}
- if (highlights.indexOf('Disagree Items') !== -1) {
+ if (highlights.includes('Disagree Items')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-disagree', { 'text-decoration': 'line-through' });
}
- if (highlights.indexOf('Ignore Items') !== -1) {
+ if (highlights.includes('Ignore Items')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-ignore', { 'font-size': '1' });
}
- if (highlights.indexOf('By Recent Minute') !== -1) {
+ if (highlights.includes('By Recent Minute')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
const min = Math.round(Date.now() / 1000 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
- setTimeout(this.updateHighlights);
}
- if (highlights.indexOf('By Recent Hour') !== -1) {
+ if (highlights.includes('By Recent Hour')) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
const hr = Math.round(Date.now() / 1000 / 60 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
}
+ this.layoutDoc[CssSym] = this.layoutDoc[CssSym] + 1; // css changes happen outside of react/mobx. so we need to set a flag that will notify anyone intereted in layout changes triggered by css changes (eg., CollectionLinkView)
};
@observable _showSidebar = false;
@@ -779,23 +785,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const expertHighlighting = [...noviceHighlighting, 'Important Items', 'Ignore Items', 'Disagree Items', 'By Recent Minute', 'By Recent Hour'];
(Doc.noviceMode ? noviceHighlighting : expertHighlighting).forEach(option =>
highlighting.push({
- description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? 'Highlight ' : 'Unhighlight ') + option,
- event: () => {
+ description: (!FormattedTextBox._globalHighlights.has(option) ? 'Highlight ' : 'Unhighlight ') + option,
+ event: action(() => {
e.stopPropagation();
- if (FormattedTextBox._globalHighlights.indexOf(option) === -1) {
- FormattedTextBox._globalHighlights.push(option);
+ if (!FormattedTextBox._globalHighlights.has(option)) {
+ FormattedTextBox._globalHighlights.add(option);
} else {
- FormattedTextBox._globalHighlights.splice(FormattedTextBox._globalHighlights.indexOf(option), 1);
+ FormattedTextBox._globalHighlights.delete(option);
}
- runInAction(() => (this.layoutDoc._highlights = FormattedTextBox._globalHighlights.join('')));
- this.updateHighlights();
- },
- icon: FormattedTextBox._globalHighlights.indexOf(option) === -1 ? 'highlighter' : 'remove-format',
+ }),
+ icon: !FormattedTextBox._globalHighlights.has(option) ? 'highlighter' : 'remove-format',
})
);
const uicontrols: ContextMenuProps[] = [];
uicontrols.push({ description: !this.Document._noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar), icon: !this.Document._noSidebar ? 'eye-slash' : 'eye' });
+ uicontrols.push({
+ description: (this.Document._showAltContentUI ? 'Hide' : 'Show') + ' Alt Content UI',
+ event: () => (this.layoutDoc._showAltContentUI = !this.layoutDoc._showAltContentUI),
+ icon: !this.Document._showAltContentUI ? 'eye-slash' : 'eye',
+ });
uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
!Doc.noviceMode &&
uicontrols.push({
@@ -844,12 +853,72 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const options = cm.findByDescription('Options...');
const optionItems = options && 'subitems' in options ? options.subitems : [];
- optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: !this.Document._singleLine ? 'grip-lines' : 'bars' });
- optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: this.Document._autoHeight ? 'lock' : 'unlock' });
+ optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
+ optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
+ optionItems.push({
+ description: !this.Document._singleLine ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns',
+ event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine),
+ icon: !this.Document._singleLine ? 'grip-lines' : 'bars',
+ });
+ !Doc.noviceMode && optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: this.Document._autoHeight ? 'lock' : 'unlock' });
+ optionItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: 'text' });
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
this._downX = this._downY = Number.NaN;
};
+ animateRes = (resIndex: number) => {
+ if (resIndex < this.gptRes.length) {
+ this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes[resIndex];
+ setTimeout(() => {
+ this.animateRes(resIndex + 1);
+ }, 20);
+ }
+ };
+
+ askGPT = action(async () => {
+ try {
+ let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
+ if (res) {
+ this.gptRes = res;
+ this.animateRes(0);
+ }
+ } catch (err) {
+ console.log(err);
+ this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + 'Something went wrong';
+ }
+ });
+
+ generateImage = async () => {
+ console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text);
+ try {
+ let image_url = await gptImageCall((this.dataDoc.text as RichTextField)?.Text);
+ if (image_url) {
+ const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_url] });
+ const source = Utils.prepend(result.accessPaths.agnostic.client);
+ const newDoc = Docs.Create.ImageDocument(source, {
+ x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10,
+ y: NumCast(this.rootDoc.y),
+ _height: 200,
+ _width: 200,
+ 'data-nativeWidth': result.nativeWidth,
+ 'data-nativeHeight': result.nativeHeight,
+ });
+ if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc)) {
+ newDoc.overlayX = this.rootDoc.x;
+ newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
+ Doc.AddDocToList(Doc.MyOverlayDocs, undefined, newDoc);
+ } else {
+ this.props.addDocument?.(newDoc);
+ }
+ // Create link between prompt and image
+ DocUtils.MakeLink(this.rootDoc, newDoc, { linkRelationship: 'Image Prompt' });
+ }
+ } catch (err) {
+ console.log(err);
+ return '';
+ }
+ };
+
breakupDictation = () => {
if (this._editorView && this._recording) {
this.stopDictation(true);
@@ -879,7 +948,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (this._editorView && this._recordingStart) {
if (this._break) {
const textanchorFunc = () => {
- const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor' });
+ const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor', unrendered: true });
return this.addDocument(tanch) ? tanch : undefined;
};
const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement();
@@ -955,7 +1024,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
let hadStart = start !== 0;
frag.forEach((node, index) => {
const examinedNode = findAnchorNode(node, editor);
- if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) {
+ if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) {
nodes.push(examinedNode.node);
!hadStart && (start = index + examinedNode.start);
hadStart = true;
@@ -970,9 +1039,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
return undefined;
}
+ if (node.type === this._editorView?.state.schema.nodes.dashDoc) {
+ if (node.attrs.docId === textAnchorId) {
+ return { node, start: 0 };
+ }
+ return undefined;
+ }
if (!node.isText) {
const content = findAnchorFrag(node.content, editor);
- return { node: node.copy(content.frag), start: content.start };
+ if (content.frag.childCount) return { node: content.frag.childCount ? content.frag.child(0) : node, start: content.start };
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor);
@@ -986,7 +1061,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const ret = findAnchorFrag(editor.state.doc.content, editor);
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) {
+ if ((ret.frag.size || (content?.length && content[0].type === this._editorView.state.schema.nodes.dashDoc) || (content?.length && content[0].type === this._editorView.state.schema.nodes.audiotag)) && ret.start >= 0) {
!options.instant && (this._focusSpeed = focusSpeed);
let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
if (ret.frag.firstChild) {
@@ -997,6 +1072,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' });
setTimeout(() => (this._focusSpeed = undefined), this._focusSpeed);
setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000));
+ return focusSpeed;
+ } else {
+ return this.props.focus(this.rootDoc, options);
}
}
};
@@ -1020,6 +1098,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
() => this.autoHeight,
autoHeight => autoHeight && this.tryUpdateScrollHeight()
);
+ this._disposers.highlights = reaction(
+ () => Array.from(FormattedTextBox._globalHighlights).slice(),
+ highlights => this.updateHighlights(highlights),
+ { fireImmediately: true }
+ );
this._disposers.width = reaction(
() => this.props.PanelWidth(),
width => this.tryUpdateScrollHeight()
@@ -1104,7 +1187,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._disposers.selected = reaction(
() => this.props.isSelected(),
action(selected => {
- this.layoutDoc._highlights = selected ? FormattedTextBox._globalHighlights.join('') : '';
+ if (FormattedTextBox._globalHighlights.has('Bold Text')) {
+ this.layoutDoc[CssSym] = this.layoutDoc[CssSym] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed
+ }
if (RichTextMenu.Instance?.view === this._editorView && !selected) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
}
@@ -1112,6 +1197,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
this.autoLink();
}
+ // Accessing editor and text doc for gpt assisted text edits
+ if (this._editorView && selected) {
+ AnchorMenu.Instance?.setEditorView(this._editorView);
+ AnchorMenu.Instance?.setTextDoc(this.dataDoc);
+ }
}),
{ fireImmediately: true }
);
@@ -1260,7 +1350,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
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.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',
@@ -1273,7 +1363,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
]),
]);
- const link = DocUtils.MakeLink({ doc: pdfAnchor }, { doc: this.rootDoc }, 'PDF pasted');
+ const link = DocUtils.MakeLink(pdfAnchor, this.rootDoc, { linkRelationship: 'PDF pasted' });
if (link) {
view.dispatch(view.state.tr.replaceSelectionWith(dashField, false).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste'));
}
@@ -1420,7 +1510,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
componentWillUnmount() {
- FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined);
Object.values(this._disposers).forEach(disposer => disposer?.());
this.endUndoTypingBatch();
this.unhighlightSearchTerms();
@@ -1470,22 +1559,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
// but that's changed, so this shouldn't be needed.
//e.stopPropagation(); // if the text box is selected, then it consumes all down events
document.addEventListener('pointerup', this.onSelectEnd);
- document.addEventListener('pointermove', this.onSelectMove);
}
}
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
e.preventDefault();
}
};
- onSelectMove = (e: PointerEvent) => e.stopPropagation();
onSelectEnd = (e: PointerEvent) => {
document.removeEventListener('pointerup', this.onSelectEnd);
- document.removeEventListener('pointermove', this.onSelectMove);
};
onPointerUp = (e: React.PointerEvent): void => {
const editor = this._editorView!;
const state = editor?.state;
- if (!state || !editor) return;
+ if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return;
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 });
@@ -1497,10 +1583,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return;
}
}
-
- if (e.button === 0 && this.props.isSelected(true) && !e.altKey) {
- e.stopPropagation();
- }
};
@action
onDoubleClick = (e: React.MouseEvent): void => {
@@ -1528,13 +1610,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@action
onFocused = (e: React.FocusEvent): void => {
//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;
onClick = (e: React.MouseEvent): void => {
+ if (!this.props.isContentActive()) return;
if ((e.nativeEvent as any).handledByInnerReactInstance) {
e.stopPropagation();
return;
@@ -1566,8 +1647,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (this.props.isSelected(true)) {
// if text box is selected, then it consumes all click events
(e.nativeEvent as any).handledByInnerReactInstance = true;
- if (this.ProseRef?.children[0] !== e.nativeEvent.target) e.stopPropagation(); // if you double click on text, then it will be selected instead of sending a double click to DocumentView & opening a lightbox. Also,if a text box has isLinkButton, this will prevent link following if you've selected the document to edit it.
- // e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks (see above comment)
this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey);
}
this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed;
@@ -1630,7 +1709,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
tr && this._editorView.dispatch(tr);
}
}
- FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined);
if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
}
@@ -1669,8 +1747,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
onKeyDown = (e: React.KeyboardEvent) => {
- if (e.altKey) {
+ if ((e.altKey || e.ctrlKey) && e.key === 't') {
e.preventDefault();
+ e.stopPropagation();
+ this.props.setTitleFocus?.();
return;
}
const state = this._editorView!.state;
@@ -1721,6 +1801,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._ignoreScroll = true;
this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop;
this._ignoreScroll = false;
+ e.stopPropagation();
+ e.preventDefault();
}
}
};
@@ -1743,12 +1825,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
}
};
- fitContentsToBox = () => this.props.Document._fitContentsToBox;
+ fitContentsToBox = () => BoolCast(this.props.Document._fitContentsToBox);
sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
if (!this.layoutDoc._showSidebar) this.toggleSidebar();
- // console.log("printting allSideBarDocs");
- // console.log(this.allSidebarDocs);
return this.addDocument(doc, sidebarKey);
};
sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
@@ -1789,8 +1869,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
className="formattedTextBox-sidebar-handle"
onPointerDown={this.sidebarDown}
style={{
- backgroundColor: backgroundColor,
- color: color,
+ backgroundColor,
+ color,
opacity: annotated ? 1 : undefined,
}}>
<FontAwesomeIcon icon={'comment-alt'} />
@@ -1804,39 +1884,41 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
<SidebarAnnos
ref={this._sidebarRef}
{...this.props}
- fieldKey={this.fieldKey}
rootDoc={this.rootDoc}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
- ScreenToLocalTransform={this.sidebarScreenToLocal}
+ usePanelWidth={true}
nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
showSidebar={this.SidebarShown}
- PanelWidth={this.sidebarWidth}
- setHeight={this.setSidebarHeight}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
sidebarAddDocument={this.sidebarAddDocument}
moveDocument={this.moveDocument}
removeDocument={this.removeDocument}
+ ScreenToLocalTransform={this.sidebarScreenToLocal}
+ fieldKey={this.fieldKey}
+ PanelWidth={this.sidebarWidth}
+ setHeight={this.setSidebarHeight}
/>
) : (
<div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
<ComponentTag
- {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+ {...this.props}
+ setContentView={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.sidebarWidth}
xPadding={0}
yPadding={0}
- scaleField={this.SidebarKey + '-scale'}
+ viewField={this.SidebarKey}
isAnnotationOverlay={false}
select={emptyFunction}
+ isAnyChildContentActive={returnFalse}
NativeDimScaling={this.sidebarContentScaling}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
removeDocument={this.sidebarRemDocument}
moveDocument={this.sidebarMoveDocument}
addDocument={this.sidebarAddDocument}
- CollectionView={undefined}
ScreenToLocalTransform={this.sidebarScreenToLocal}
renderDepth={this.props.renderDepth + 1}
setHeight={this.setSidebarHeight}
@@ -1854,6 +1936,49 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
</div>
);
}
+ cycleAlternateText = () => {
+ if (this.layoutDoc._showAltContentUI) {
+ const usePath = this.rootDoc[`${this.props.fieldKey}-usePath`];
+ this.rootDoc[`_${this.props.fieldKey}-usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
+ }
+ };
+ @computed get overlayAlternateIcon() {
+ const usePath = this.rootDoc[`${this.props.fieldKey}-usePath`];
+ return (
+ <Tooltip
+ title={
+ <div className="dash-tooltip">
+ toggle (%/) between
+ <span style={{ color: usePath === undefined ? 'black' : undefined }}>
+ <em> primary, </em>
+ </span>
+ <span style={{ color: usePath === 'alternate' ? 'black' : undefined }}>
+ <em>alternate, </em>
+ </span>
+ and show
+ <span style={{ color: usePath === 'alternate:hover' ? 'black' : undefined }}>
+ <em> alternate on hover</em>
+ </span>
+ </div>
+ }>
+ <div
+ className="formattedTextBox-alternateButton"
+ onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => this.cycleAlternateText())}
+ style={{
+ display: this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'flex' : 'none',
+ background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
+ color: usePath === undefined ? 'black' : 'white',
+ }}>
+ <FontAwesomeIcon icon="turn-up" size="sm" />
+ </div>
+ </Tooltip>
+ );
+ }
+ @computed get fieldKey() {
+ const usePath = StrCast(this.rootDoc[`${this.props.fieldKey}-usePath`]);
+ return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering) ? `-${usePath.replace(':hover', '')}` : '');
+ }
+ @observable _isHovering = false;
render() {
TraceMobx();
const active = this.props.isContentActive() || this.props.isSelected();
@@ -1870,8 +1995,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
return styleFromLayoutString?.height === '0px' ? null : (
<div
- className="formattedTextBox-cont"
- onWheel={e => this.props.isContentActive() && e.stopPropagation()}
+ className="formattedTextBox"
+ onPointerEnter={action(() => (this._isHovering = true))}
+ onPointerLeave={action(() => (this._isHovering = false))}
+ ref={r =>
+ r?.addEventListener(
+ 'wheel', // 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)
+ (e: WheelEvent) => {
+ if (this.props.isContentActive()) {
+ if (!NumCast(this.layoutDoc._scrollTop) && e.deltaY <= 0) e.preventDefault();
+ e.stopPropagation();
+ }
+ },
+ { passive: false }
+ )
+ }
style={{
...(this.props.dontScale
? {}
@@ -1881,6 +2019,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
width: `${100 / scale}%`,
height: `${100 / scale}%`,
}),
+ display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined,
transition: 'inherit',
// overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined,
color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
@@ -1890,7 +2029,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
...styleFromLayoutString,
}}>
<div
- className={`formattedTextBox-cont`}
+ className="formattedTextBox-cont"
ref={this._ref}
style={{
overflow: this.autoHeight && this.props.CollectionFreeFormDocumentView?.() ? 'hidden' : undefined, //x this breaks viewing an autoHeight doc in its own tab, or in the lightbox
@@ -1925,13 +2064,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
- pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? 'none' : undefined) : undefined,
+ pointerEvents: !active && !SnappingManager.GetIsDragging() ? (IsFollowLinkScript(this.layoutDoc.onClick) ? 'none' : undefined) : undefined,
}}
/>
</div>
{this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
{this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle}
{this.audioHandle}
+ {this.layoutDoc._showAltContentUI ? this.overlayAlternateIcon : null}
</div>
</div>
);