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.tsx511
1 files changed, 227 insertions, 284 deletions
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 38817ac6d..dc23a695d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,10 +1,10 @@
/* eslint-disable no-use-before-define */
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
+import { Property } from 'csstype';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import { baseKeymap, selectAll } from 'prosemirror-commands';
+import { baseKeymap, selectAll, splitBlock } from 'prosemirror-commands';
import { history } from 'prosemirror-history';
import { inputRules } from 'prosemirror-inputrules';
import { keymap } from 'prosemirror-keymap';
@@ -13,20 +13,19 @@ import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transacti
import { EditorView, NodeViewConstructor } from 'prosemirror-view';
import * as React from 'react';
import { BsMarkdownFill } from 'react-icons/bs';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, imageUrlToBase64, returnFalse, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, removeStyleSheet, returnFalse, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils';
import { DateField } from '../../../../fields/DateField';
import { CreateLinkToActiveAudio, Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
import { Id, ToString } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
-import { PrefetchProxy } from '../../../../fields/Proxy';
import { RichTextField } from '../../../../fields/RichTextField';
import { ComputedField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
import { emptyFunction, numberRange, unimplementedFunction, Utils } from '../../../../Utils';
-import { gptAPICall, GPTCallType, gptImageLabel } from '../../../apis/gpt/GPT';
+import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
import { DocServer } from '../../../DocServer';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -34,7 +33,6 @@ import { DocUtils } from '../../../documents/DocUtils';
import { DictationManager } from '../../../util/DictationManager';
import { DragManager } from '../../../util/DragManager';
import { dropActionType } from '../../../util/DropActionTypes';
-import { MakeTemplate } from '../../../util/DropConverter';
import { LinkManager } from '../../../util/LinkManager';
import { RTFMarkup } from '../../../util/RTFMarkup';
import { SnappingManager } from '../../../util/SnappingManager';
@@ -49,12 +47,14 @@ import { AnchorMenu } from '../../pdf/AnchorMenu';
import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
import { PinDocView, PinProps } from '../../PinFuncs';
import { SidebarAnnos } from '../../SidebarAnnos';
+import { StickerPalette } from '../../smartdraw/StickerPalette';
import { StyleProp } from '../../StyleProp';
import { styleFromLayoutString } from '../../StyleProvider';
import { mediaState } from '../AudioBox';
import { DocumentView } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { FocusViewOptions } from '../FocusViewOptions';
+import { LabelBox } from '../LabelBox';
import { LinkInfo } from '../LinkDocPreview';
import { OpenWhere } from '../OpenWhere';
import './FormattedTextBox.scss';
@@ -64,9 +64,6 @@ import { removeMarkWithAttrs } from './prosemirrorPatches';
import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
import { RichTextRules } from './RichTextRules';
import { schema } from './schema_rts';
-import { Property } from 'csstype';
-import { LabelBox } from '../LabelBox';
-import { StickerPalette } from '../../smartdraw/StickerPalette';
// import * as applyDevTools from 'prosemirror-dev-tools';
export interface FormattedTextBoxProps extends FieldViewProps {
@@ -78,17 +75,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
- public static MakeConfig(rules?: RichTextRules, props?: FormattedTextBoxProps) {
+ public static MakeConfig(rules?: RichTextRules, textBox?: FormattedTextBox, plugs?: Plugin[]) {
return {
schema,
plugins: [
inputRules(rules?.inpRules ?? { rules: [] }),
- ...(props ? [FormattedTextBox.richTextMenuPlugin(props)] : []),
+ ...(textBox?._props ? [FormattedTextBox.richTextMenuPlugin(textBox._props)] : []),
history(),
- keymap(buildKeymap(schema, props ?? {})),
+ keymap(buildKeymap(schema, textBox)),
keymap(baseKeymap),
new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }),
new Plugin({ view: () => new FormattedTextBoxComment() }),
+ ...(plugs ?? []),
],
};
}
@@ -103,12 +101,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
public static LiveTextUndo: UndoManager.Batch | undefined; // undo batch when typing a new text note into a collection
private static _nodeViews: (self: FormattedTextBox) => { [key: string]: NodeViewConstructor };
- private static _globalHighlightsCache: string = '';
- private static _globalHighlights = new ObservableSet<string>(['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items']);
- private static _highlightStyleSheet = addStyleSheet();
- private static _bulletStyleSheet = addStyleSheet();
- private static _userStyleSheet = addStyleSheet();
+ private _curHighlights = new ObservableSet<string>(['Audio Tags']);
+ private static _highlightStyleSheet = addStyleSheet().sheet;
+ private static _bulletStyleSheet = addStyleSheet().sheet;
+ private _userStyleSheetElement: HTMLStyleElement | undefined;
+ private _enteringStyle = false;
private _oldWheel: HTMLDivElement | null = null;
private _selectionHTML: string | undefined;
private _sidebarRef = React.createRef<SidebarAnnos>();
@@ -140,30 +138,31 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
public ApplyingChange: string = '';
@observable _showSidebar = false;
+ @observable _userPlugins: Plugin[] = [];
- @computed get fontColor() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontColor) as string; } // prettier-ignore
- @computed get fontSize() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) as string; } // prettier-ignore
- @computed get fontFamily() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) as string; } // prettier-ignore
- @computed get fontWeight() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontWeight) as string; } // prettier-ignore
- @computed get fontStyle() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontStyle) as string; } // prettier-ignore
- @computed get fontDecoration() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontDecoration) as string; } // prettier-ignore
+ @computed get fontColor() { return this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.FontColor) as string; } // prettier-ignore
+ @computed get fontSize() { return this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.FontSize) as string; } // prettier-ignore
+ @computed get fontFamily() { return this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.FontFamily) as string; } // prettier-ignore
+ @computed get fontWeight() { return this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.FontWeight) as string; } // prettier-ignore
+ @computed get fontStyle() { return this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.FontStyle) as string; } // prettier-ignore
+ @computed get fontDecoration() { return this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.FontDecoration) as string; } // prettier-ignore
- set _recordingDictation(value) {
+ set recordingDictation(value) {
!this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? mediaState.Recording : undefined);
}
// eslint-disable-next-line no-return-assign
- @computed get config() { return FormattedTextBox.MakeConfig(this._rules = new RichTextRules(this.Document, this), this._props); } // prettier-ignore
- @computed get _recordingDictation() { return this.dataDoc?.mediaState === mediaState.Recording; } // prettier-ignore
- @computed get SidebarShown() { return !!(this._showSidebar || this.layoutDoc._layout_showSidebar); } // prettier-ignore
+ @computed get config() { return FormattedTextBox.MakeConfig(this._rules = new RichTextRules(this.Document, this), this, this._userPlugins ?? []); } // prettier-ignore
+ @computed get recordingDictation() { return this.dataDoc?.mediaState === mediaState.Recording; } // prettier-ignore
+ @computed get SidebarShown() { return !!(this._showSidebar || this.layoutDoc._layout_showSidebar); } // prettier-ignore
@computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.sidebarKey]); } // prettier-ignore
@computed get noSidebar() { return this.DocumentView?.()._props.hideDecorationTitle || this._props.noSidebar || this.Document._layout_noSidebar; } // prettier-ignore
- @computed get layout_sidebarWidthPercent() { return this._showSidebar ? '20%' : StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } // prettier-ignore
- @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.fieldKey + '_backgroundColor'], '#e4e4e4')); } // prettier-ignore
+ @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, this._showSidebar ? '20%' :'0%'); } // prettier-ignore
+ @computed get sidebarColor() { return StrCast(this.layoutDoc._sidebar_color, StrCast(this.layoutDoc["_"+this.fieldKey + '_backgroundColor'], '#e4e4e4')); } // prettier-ignore
@computed get layout_autoHeight() { return (this._props.forceAutoHeight || this.layoutDoc._layout_autoHeight) && !this._props.ignoreAutoHeight; } // prettier-ignore
- @computed get textHeight() { return NumCast(this.dataDoc[this.fieldKey + '_height']); } // prettier-ignore
- @computed get scrollHeight() { return NumCast(this.dataDoc[this.fieldKey + '_scrollHeight']); } // prettier-ignore
- @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.dataDoc[this.sidebarKey + '_height']); } // prettier-ignore
+ @computed get textHeight() { return NumCast(this.layoutDoc["_"+this.fieldKey + '_height']); } // prettier-ignore
+ @computed get scrollHeight() { return NumCast(this.layoutDoc["_"+this.fieldKey + '_scrollHeight']); } // prettier-ignore
+ @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.layoutDoc["_"+this.sidebarKey + '_height']); } // prettier-ignore
@computed get titleHeight() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) as number || 0; } // prettier-ignore
@computed get layout_autoHeightMargins() { return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins); } // prettier-ignore
@computed get sidebarKey() { return this.fieldKey + '_sidebar'; } // prettier-ignore
@@ -215,9 +214,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
- const rootDoc: Doc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document);
- if (!pinProps && this.EditorView?.state.selection.empty) return rootDoc;
- const anchor = Docs.Create.ConfigDocument({ title: StrCast(rootDoc.title), annotationOn: rootDoc });
+ if (!pinProps && this.EditorView?.state.selection.empty) return this.rootDoc;
+ const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.rootDoc?.title), annotationOn: this.rootDoc });
this.addDocument(anchor);
this._finishingLink = true;
this.makeLinkAnchor(anchor, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation);
@@ -226,18 +224,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return anchor;
};
- selectionToFlashcards = async () => {
- const queryText = window.getSelection()?.toString() ?? '';
- try {
- if (queryText) {
- const res = await gptAPICall(queryText, GPTCallType.FLASHCARD);
- AnchorMenu.Instance.transferToFlashcard(res || 'Something went wrong', NumCast(this.layoutDoc.x), NumCast(this.layoutDoc.y));
- }
- } catch (err) {
- console.error(err);
- }
- };
-
@action
setupAnchorMenu = () => {
AnchorMenu.Instance.Status = 'marquee';
@@ -257,9 +243,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (target) {
anchor.followLinkAudio = true;
let stopFunc: () => void = emptyFunction;
- const targetData = target[DocData];
- targetData.mediaState = mediaState.Recording;
- DictationManager.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore
+ target.$mediaState = mediaState.Recording;
+ DictationManager.recordAudioAnnotation(target, Doc.LayoutDataKey(target), stop => { stopFunc = stop }); // prettier-ignore
const reactionDisposer = reaction(
() => target.mediaState,
@@ -321,7 +306,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
leafText = (node: Node) => {
if (node.type === this.EditorView?.state.schema.nodes.dashField) {
- const refDoc = !node.attrs.docId ? DocCast(this.Document.rootDocument, this.Document) : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
+ const refDoc = !node.attrs.docId ? this.rootDoc : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
const fieldKey = StrCast(node.attrs.fieldKey);
return (
(node.attrs.hideKey ? '' : fieldKey + ':') + //
@@ -329,7 +314,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
);
}
if (node.type === this.EditorView?.state.schema.nodes.dashDoc) {
- const refDoc = !node.attrs.docId ? DocCast(this.Document.rootDocument, this.Document) : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
+ const refDoc = !node.attrs.docId ? this.rootDoc : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
return refDoc[ToString]();
}
return '';
@@ -441,8 +426,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const oldAutoLinks = Doc.Links(this.Document).filter(
link =>
((!Doc.isTemplateForField(this.Document) &&
- (!Doc.isTemplateForField(DocCast(link.link_anchor_1)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) &&
- (!Doc.isTemplateForField(DocCast(link.link_anchor_2)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_2), this.Document))) ||
+ ((DocCast(link.link_anchor_1) && !Doc.isTemplateForField(DocCast(link.link_anchor_1)!)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) &&
+ ((DocCast(link.link_anchor_2) && !Doc.isTemplateForField(DocCast(link.link_anchor_2)!)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_2), this.Document))) ||
(Doc.isTemplateForField(this.Document) && (link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document))) &&
link.link_relationship === LinkManager.AutoKeywords
); // prettier-ignore
@@ -454,7 +439,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
Doc.MyPublishedDocs.filter(term => term.title).forEach(term => {
tr = this.hyperlinkTerm(tr, term, newAutoLinks);
});
- tr = tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)));
+ const marks = tr.storedMarks;
+ tr = tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to))).setStoredMarks(marks);
this.EditorView?.dispatch(tr);
}
oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(doc => Doc.DeleteLink?.(doc));
@@ -463,18 +449,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
updateTitle = () => {
const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text);
if (
- !this._props.dontRegisterView && // (this.Document.isTemplateForField === "text" || !this.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+ !this._props.dontRegisterView && // only update the title if the data document's data field is changing
title.startsWith('-') &&
this.EditorView &&
!this.dataDoc.title_custom &&
- (Doc.LayoutFieldKey(this.Document) === this.fieldKey || this.fieldKey === 'text')
+ (Doc.LayoutDataKey(this.Document) === this.fieldKey || this.fieldKey === 'text')
) {
let node = this.EditorView.state.doc;
while (node.firstChild && node.firstChild.type.name !== 'text') node = node.firstChild;
const str = node.textContent;
const prefix = '-';
- const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title));
+ const cfield = ComputedField.DisableCompute(() => FieldValue(this.dataDoc.title));
if (!(cfield instanceof ComputedField)) {
this.dataDoc.title = (prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '')).trim();
}
@@ -601,7 +587,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
const dragData = de.complete.docDragData;
if (dragData) {
- const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
+ const layoutProto = DocCast(this.layoutDoc.proto);
+ const dataDoc = layoutProto && Doc.IsDelegateField(layoutProto, this.fieldKey) ? layoutProto : this.dataDoc;
const effectiveAcl = GetEffectiveAcl(dataDoc);
const draggedDoc = dragData.droppedDocuments.lastElement();
let added: Opt<boolean>;
@@ -609,7 +596,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl) && !dragData.draggedDocuments.includes(this.Document)) {
// replace text contents when dragging with Alt
if (de.altKey) {
- const fieldKey = Doc.LayoutFieldKey(draggedDoc);
+ const fieldKey = Doc.LayoutDataKey(draggedDoc);
if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this.Document)) {
Doc.GetProto(this.dataDoc)[this.fieldKey] = Field.Copy(draggedDoc[fieldKey]);
}
@@ -709,45 +696,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
updateHighlights = (highlights: string[]) => {
- if (Array.from(highlights).join('') === FormattedTextBox._globalHighlightsCache) return;
- setTimeout(() => {
- FormattedTextBox._globalHighlightsCache = Array.from(highlights).join('');
- });
- clearStyleSheetRules(FormattedTextBox._userStyleSheet);
- if (!highlights.includes('Audio Tags')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, '');
- }
- if (highlights.includes('Text from Others')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' });
- }
- if (highlights.includes('My Text')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace(/\./g, '').replace(/@/g, ''), { background: 'moccasin' });
- }
- if (highlights.includes('Todo Items')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-todo', { outline: 'black solid 1px' });
- }
- if (highlights.includes('Important Items')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-important', { 'font-size': 'larger' });
- }
- 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.includes('Disagree Items')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-disagree', { 'text-decoration': 'line-through' });
- }
- if (highlights.includes('Ignore Items')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-ignore', { 'font-size': '1' });
- }
+ const userStyleSheet = () => {
+ if (!this._userStyleSheetElement) {
+ this._userStyleSheetElement = addStyleSheet();
+ }
+ return this._userStyleSheetElement.sheet;
+ };
+ const viewId = this.DocumentView?.().ViewGuid ?? 1;
+ const userId = ClientUtils.CurrentUserEmail().replace(/\./g, '').replace('@', ''); // must match marks_rts -> user_mark's uid
+ highlights.filter(f => f !== 'Audio Tags').length && clearStyleSheetRules(userStyleSheet());
+ if (!highlights.includes('Audio Tags')) addStyleSheetRule(userStyleSheet(), `#${viewId} .audiotag`, { display: 'none' }, ''); // prettier-ignore
+ if (highlights.includes('Text from Others')) addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-remote`, { background: 'yellow' }, ''); // prettier-ignore
+ if (highlights.includes('My Text')) addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-${userId}`, { background: 'moccasin' }, ''); // prettier-ignore
+ if (highlights.includes('Todo Items')) addStyleSheetRule(userStyleSheet(), `#${viewId} .UT-todo`, { outline: 'black solid 1px' }, ''); // prettier-ignore
+ if (highlights.includes('Important Items')) addStyleSheetRule(userStyleSheet(), `#${viewId} .UT-important`, { 'font-size': 'larger' }, ''); // prettier-ignore
+ if (highlights.includes('Disagree Items')) addStyleSheetRule(userStyleSheet(), `#${viewId} .UT-disagree`, { 'text-decoration': 'line-through' }, ''); // prettier-ignore
+ if (highlights.includes('Ignore Items')) addStyleSheetRule(userStyleSheet(), `#${viewId} .UT-ignore`, { 'font-size': '1' }, ''); // prettier-ignore
+ if (highlights.includes('Bold Text')) { addStyleSheetRule(userStyleSheet(), `#${viewId} .formattedTextBox-inner .ProseMirror p:not(:has(strong))`, { 'font-size': '0px' }, '');
+ addStyleSheetRule(userStyleSheet(), `#${viewId} .formattedTextBox-inner .ProseMirror p:not(:has(strong)) ::after`, { content: '...', 'font-size': '5px' }, '')} // prettier-ignore
if (highlights.includes('By Recent Minute')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace('.', '').replace('@', ''), { opacity: '0.1' });
+ addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-${userId}`, { 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() }));
+ numberRange(10).map(i => addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-min-` + (min - i), { opacity: ((10 - i - 1) / 10).toString() }, ''));
}
if (highlights.includes('By Recent Hour')) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace('.', '').replace('@', ''), { opacity: '0.1' });
+ addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-${userId}`, { 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() }));
+ numberRange(10).map(i => addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-hr-` + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }, ''));
}
this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx. so we need to set a flag that will notify anyone interested in layout changes triggered by css changes (eg., CollectionLinkView)
};
@@ -755,15 +730,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
@action
toggleSidebar = (preview: boolean = false) => {
const defaultSidebar = 250;
- const prevWidth = 1 - this.sidebarWidth() / DivWidth(this._ref.current!);
+ const dw = DivWidth(this._ref.current);
+ const prevWidth = 1 - this.sidebarWidth() / dw / this.nativeScaling();
if (preview) this._showSidebar = true;
else {
- this.layoutDoc[this.sidebarKey + '_freeform_scale_max'] = 1;
- this.layoutDoc._layout_showSidebar =
- (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(defaultSidebar / (NumCast(this.layoutDoc._width) + defaultSidebar)) * 100}%` : '0%') !== '0%';
+ this.layoutDoc._layout_sidebarWidthPercent =
+ this.sidebarWidthPercent === '0%' //
+ ? `${(defaultSidebar / (NumCast(this.layoutDoc._width) + defaultSidebar)) * 100}%` //
+ : '0%';
+ this.layoutDoc._layout_showSidebar = this.sidebarWidthPercent !== '0%';
}
- this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) + defaultSidebar : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth);
+ this.layoutDoc._width =
+ !preview && this.SidebarShown //
+ ? NumCast(this.layoutDoc._width) + defaultSidebar
+ : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth);
};
sidebarDown = (e: React.PointerEvent) => {
const batch = UndoManager.StartBatch('toggle sidebar');
@@ -781,7 +762,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
const localDelta = this.DocumentView?.().screenToViewTransform().transformDirection(delta[0], delta[1]) ?? delta;
- const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.layout_sidebarWidthPercent.replace('%', ''))) / 100;
+ const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.sidebarWidthPercent.replace('%', ''))) / 100;
const width = NumCast(this.layoutDoc._width) + localDelta[0];
this.layoutDoc._layout_sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%';
this.layoutDoc.width = width;
@@ -859,57 +840,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return;
}
- const changeItems: ContextMenuProps[] = [];
- changeItems.push({
- description: 'plain',
- event: undoable(() => {
- Doc.setNativeView(this.Document);
- this.layoutDoc.layout_autoHeightMargins = undefined;
- }, 'set plain view'),
- icon: 'eye',
- });
- changeItems.push({
- description: 'metadata',
- event: undoable(() => {
- this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
- this.Document.layout_fieldKey = 'layout_meta';
- setTimeout(() => {
- this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50;
- }, 50);
- }, 'set metadata view'),
- icon: 'eye',
- });
- const noteTypesDoc = Cast(Doc.UserDoc().template_notes, Doc, null);
- DocListCast(noteTypesDoc?.data).forEach(note => {
- const icon: IconProp = StrCast(note.icon) as IconProp;
- changeItems.push({
- description: StrCast(note.title),
- event: undoable(
- () => {
- this.layoutDoc.layout_autoHeightMargins = undefined;
- Doc.setNativeView(this.Document);
- DocUtils.makeCustomViewClicked(this.Document, Docs.Create.TreeDocument, StrCast(note.title), note);
- },
- `set ${StrCast(note.title)} view}`
- ),
- icon: icon,
- });
- });
const highlighting: ContextMenuProps[] = [];
const noviceHighlighting = ['Audio Tags', 'My Text', 'Text from Others', 'Bold Text'];
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.has(option) ? 'Highlight ' : 'Unhighlight ') + option,
+ description: (!this._curHighlights.has(option) ? 'Highlight ' : 'Unhighlight ') + option,
event: action(() => {
e.stopPropagation();
- if (!FormattedTextBox._globalHighlights.has(option)) {
- FormattedTextBox._globalHighlights.add(option);
+ if (!this._curHighlights.has(option)) {
+ this._curHighlights.add(option);
} else {
- FormattedTextBox._globalHighlights.delete(option);
+ this._curHighlights.delete(option);
}
}),
- icon: !FormattedTextBox._globalHighlights.has(option) ? 'highlighter' : 'remove-format',
+ icon: !this._curHighlights.has(option) ? 'highlighter' : 'remove-format',
})
);
const appearance = cm.findByDescription('Appearance...');
@@ -956,20 +901,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
icon: 'expand-arrows-alt',
});
- appearanceItems.push({ description: 'Change Style...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' });
-
- !Doc.noviceMode &&
- appearanceItems.push({
- description: 'Make Default Layout',
- event: () => {
- if (!this.layoutDoc.isTemplateDoc) {
- MakeTemplate(this.Document);
- }
- Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.Document);
- Doc.AddDocToList(Cast(Doc.UserDoc().template_notes, Doc, null), 'data', this.Document);
- },
- icon: 'eye',
- });
!appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
const options = cm.findByDescription('Options...');
@@ -992,14 +923,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
},
icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars',
});
- !Doc.noviceMode &&
- optionItems.push({
- description: `${this.Document._layout_autoHeight ? 'Lock' : 'Auto'} Height`,
- event: () => {
- this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight;
- },
- icon: this.Document._layout_autoHeight ? 'lock' : 'unlock',
- });
optionItems.push({
description: this.Document.savedAsSticker ? 'Sticker Saved!' : 'Save to Stickers',
event: action(undoable(async () => await StickerPalette.addToPalette(this.Document), 'save to palette')),
@@ -1012,31 +935,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
!help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' });
};
- findImageTags = async () => {
- const c = this.ProseRef?.getElementsByTagName('img');
- if (c) {
- for (const i of c) {
- // console.log(canvas.toDataURL());
- // canvas.style.zIndex = '2000000';
- // document.body.appendChild(canvas);
- if (i.className !== 'ProseMirror-separator') this.getImageDesc(i.src);
- }
- }
- };
-
- getImageDesc = async (u: string) => {
- try {
- const hrefBase64 = await imageUrlToBase64(u);
- const response = await gptImageLabel(
- hrefBase64,
- 'Make flashcards out of this text and image with each question and answer labeled as question and answer. Do not label each flashcard and do not include asterisks: ' + (this.dataDoc.text as RichTextField)?.Text
- );
- AnchorMenu.Instance.transferToFlashcard(response || 'Something went wrong', NumCast(this.dataDoc['x']), NumCast(this.dataDoc['y']));
- } catch (error) {
- console.log('Error', error);
- }
- };
-
animateRes = (resIndex: number, newText: string) => {
if (resIndex < newText.length) {
const marks = this.EditorView?.state.storedMarks ?? [];
@@ -1071,14 +969,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
breakupDictation = () => {
- if (this.EditorView && this._recordingDictation) {
+ if (this.EditorView && this.recordingDictation) {
this.stopDictation(/* true */);
this._break = true;
const { state } = this.EditorView;
const { to } = state.selection;
const updated = TextSelection.create(state.doc, to, to);
this.EditorView.dispatch(state.tr.setSelection(updated).insert(to, state.schema.nodes.paragraph.create({})));
- if (this._recordingDictation) {
+ if (this.recordingDictation) {
this.recordDictation();
}
}
@@ -1104,17 +1002,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
const link = CreateLinkToActiveAudio(textanchorFunc, false).lastElement();
if (link) {
- link[DocData].isDictation = true;
- const audioanchor = Cast(link.link_anchor_2, Doc, null);
- const textanchor = Cast(link.link_anchor_1, Doc, null);
+ link.$isDictation = true;
+ const audioanchor = DocCast(link.link_anchor_2);
+ const textanchor = DocCast(link.link_anchor_1);
if (audioanchor) {
audioanchor.backgroundColor = 'tan';
const audiotag = this.EditorView.state.schema.nodes.audiotag.create({
timeCode: NumCast(audioanchor._timecodeToShow),
audioId: audioanchor[Id],
- textId: textanchor[Id],
+ textId: textanchor?.[Id] ?? '',
});
- textanchor[DocData].title = 'dictation:' + audiotag.attrs.timeCode;
+ textanchor && (textanchor.$title = 'dictation:' + audiotag.attrs.timeCode);
const tr = this.EditorView.state.tr.insert(this.EditorView.state.doc.content.size, audiotag);
const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size));
this.EditorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size)));
@@ -1249,10 +1147,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// if the scroll height has changed and we're in layout_autoHeight mode, then we need to update the textHeight component of the doc.
// Since we also monitor all component height changes, this will update the document's height.
- resetNativeHeight = (scrollHeight: number) => {
- const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight);
- this.dataDoc[this.fieldKey + '_height'] = scrollHeight;
- if (nh) this.layoutDoc._nativeHeight = scrollHeight;
+ resetNativeHeight = action((scrollHeight: number) => {
+ this.layoutDoc['_' + this.fieldKey + '_height'] = scrollHeight;
+ if (!this.layoutDoc.isTemplateForField) this.layoutDoc._nativeHeight = scrollHeight;
+ });
+
+ addPlugin = (plugin: Plugin) => {
+ const editorView = this.EditorView;
+ if (editorView) {
+ this._userPlugins.push(plugin);
+ const newState = editorView.state.reconfigure({
+ plugins: [...editorView.state.plugins, plugin],
+ });
+ editorView.updateState(newState);
+ }
};
@computed get tagsHeight() {
@@ -1260,7 +1168,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
@computed get contentScaling() {
- return Doc.NativeAspect(this.Document, this.dataDoc, false) ? this._props.NativeDimScaling?.() || 1 : 1;
+ return Doc.NativeAspect(this.Document, this.dataDoc, false) ? this.nativeScaling() : 1;
}
componentDidMount() {
!this._props.dontSelectOnLoad && this._props.setContentViewBox?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
@@ -1270,15 +1178,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
() => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize, css: this.Document[DocCss], xMargin: this.Document.xMargin, yMargin: this.Document.yMargin }),
autoHeight => setTimeout(() => 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(),
- () => this.tryUpdateScrollHeight()
- );
+ this._disposers.highlights = reaction(() => Array.from(this._curHighlights).slice(), this.updateHighlights, { fireImmediately: true });
+ this._disposers.width = reaction(this._props.PanelWidth, this.tryUpdateScrollHeight);
this._disposers.scrollHeight = reaction(
() => ({ scrollHeight: this.scrollHeight, layoutAutoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }),
({ width, scrollHeight, layoutAutoHeight }) => width && layoutAutoHeight && this.resetNativeHeight(scrollHeight),
@@ -1290,7 +1191,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
({ border, sidebarHeight, textHeight, layoutAutoHeight, marginsHeight }) => {
const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight));
if (
- (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && //
+ (!Array.from(this._curHighlights).includes('Bold Text') || this._props.isSelected()) && //
layoutAutoHeight &&
newHeight &&
(newHeight !== this.layoutDoc.height || border < NumCast(this.layoutDoc.height)) &&
@@ -1299,7 +1200,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._props.setHeight?.(newHeight);
}
},
- { fireImmediately: !Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') }
+ { fireImmediately: !Array.from(this._curHighlights).includes('Bold Text') }
);
this._disposers.links = reaction(
() => Doc.Links(this.dataDoc), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
@@ -1314,8 +1215,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const dataData = this.dataDoc[this.fieldKey];
const layoutData = Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? undefined : this.layoutDoc[this.fieldKey];
const dataTime = dataData ? (DateCast(this.dataDoc[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
- const layoutTime = layoutData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? (DateCast(DocCast(this.layoutDoc)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
- const protoTime = protoData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? (DateCast(DocCast(this.dataDoc.proto)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
+ const layoutTime = layoutData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? (DateCast(DocCast(this.layoutDoc)?.[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
+ const protoTime = protoData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? (DateCast(DocCast(this.dataDoc.proto)?.[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
const recentData = dataTime >= layoutTime ? (protoTime >= dataTime ? protoData : dataData) : layoutTime >= protoTime ? layoutData : protoData;
const whichData = recentData ?? (this.layoutDoc.isTemplateDoc ? layoutData : protoData) ?? protoData;
return !whichData ? undefined : { data: RTFCast(whichData), str: Field.toString(DocCast(whichData) ?? StrCast(whichData)) };
@@ -1354,7 +1255,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
});
}
this.prepareForTyping();
- if (FormattedTextBox._globalHighlights.has('Bold Text')) {
+ if (this._curHighlights.has('Bold Text')) {
this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 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 && this.EditorView) || this.isLabel) && !selected) {
@@ -1370,14 +1271,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (!this._props.dontRegisterView) {
this._disposers.record = reaction(
- () => this._recordingDictation,
+ () => this.recordingDictation,
() => {
this.stopDictation(/* true */);
- this._recordingDictation && this.recordDictation();
+ this.recordingDictation && this.recordDictation();
},
{ fireImmediately: true }
);
- if (this._recordingDictation) setTimeout(this.recordDictation);
+ if (this.recordingDictation) setTimeout(this.recordDictation);
}
this._disposers.scroll = reaction(
() => NumCast(this.layoutDoc._layout_scrollTop),
@@ -1411,7 +1312,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// } catch (err) {
// console.log('Drop failed', err);
// }
- this.addDocument?.(DocCast(this.Document.image));
+ DocCast(this.Document.image) && this.addDocument?.(DocCast(this.Document.image)!);
}
//if (this.Document.image) this.addDocument?.(DocCast(this.Document.image));
@@ -1434,7 +1335,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
} else if (!separated && node.isBlock) {
text += '\n';
separated = true;
- } else if (node.type.name === 'hard_break') {
+ } else if (node.type.name === schema.nodes.hard_break.name) {
text += '\n';
}
},
@@ -1495,42 +1396,57 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
_didScroll = false;
_scrollStopper: undefined | (() => void);
+ scrollToSelection = () => {
+ if (this.EditorView && this._ref.current) {
+ const editorView = this.EditorView;
+ const docPos = editorView.coordsAtPos(editorView.state.selection.to);
+ const viewRect = this._ref.current.getBoundingClientRect();
+ const scrollRef = this._scrollRef;
+ const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined;
+ const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
+ if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
+ const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
+ const scrollPos = scrollRef.scrollTop + shift * this.ScreenToLocalBoxXf().Scale;
+ if (this._focusSpeed !== undefined) {
+ setTimeout(() => {
+ scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper));
+ });
+ } else {
+ scrollRef.scrollTo({ top: scrollPos });
+ }
+ this._didScroll = true;
+ }
+ }
+ return true;
+ };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
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);
if (this.ProseRef) {
this.EditorView?.destroy();
+ const edState = () => {
+ try {
+ return rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config);
+ } catch {
+ return EditorState.create(config);
+ }
+ };
this._editorView = new EditorView(this.ProseRef, {
- state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
- handleScrollToSelection: editorView => {
- const docPos = editorView.coordsAtPos(editorView.state.selection.to);
- const viewRect = this._ref.current!.getBoundingClientRect();
- const scrollRef = this._scrollRef;
- const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined;
- const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
- if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
- const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
- const scrollPos = scrollRef.scrollTop + shift * this.ScreenToLocalBoxXf().Scale;
- if (this._focusSpeed !== undefined) {
- setTimeout(() => {
- scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper));
- });
- } else {
- scrollRef.scrollTo({ top: scrollPos });
- }
- this._didScroll = true;
- }
- return true;
- },
+ state: edState(),
+ handleScrollToSelection: this.scrollToSelection,
dispatchTransaction: this.dispatchTransaction,
nodeViews: FormattedTextBox._nodeViews(this),
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
});
+ // bcz: major hack! a patch to prosemirror broke scrolling to selection when the selection is not a dom selection
+ // this replaces prosemirror's scrollToSelection function with Dash's
+ (this.EditorView as unknown as { scrollToSelection: unknown }).scrollToSelection = this.scrollToSelection;
const { state } = this._editorView;
if (!rtfField) {
- const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
+ const layoutProto = DocCast(this.layoutDoc.proto);
+ const dataDoc = layoutProto && Doc.IsDelegateField(layoutProto, this.fieldKey) ? layoutProto : this.dataDoc;
const startupText = Field.toString(dataDoc[fieldKey] as FieldType);
const textAlign = StrCast(this.dataDoc[this.fieldKey + '_align'], StrCast(Doc.UserDoc().textAlign)) || 'left';
if (textAlign !== 'left') {
@@ -1546,25 +1462,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._editorView.TextView = this;
}
- const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, DocumentView.SelectOnLoad) && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.()));
+ const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.rootDoc, DocumentView.SelectOnLoad) && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.()));
const selLoadChar = FormattedTextBox.SelectOnLoadChar;
if (selectOnLoad) {
DocumentView.SetSelectOnLoad(undefined);
FormattedTextBox.SelectOnLoadChar = '';
}
if (this.EditorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
- const $from = this.EditorView.state.selection.anchor ? this.EditorView.state.doc.resolve(this.EditorView.state.selection.anchor - 1) : undefined;
+ const { $from } = this.EditorView.state.selection;
const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) });
const curMarks = this.EditorView.state.storedMarks ?? $from?.marksAcross(this.EditorView.state.selection.$head) ?? [];
const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
- let { tr } = this.EditorView.state;
- if (selLoadChar) {
- const tr1 = this.EditorView.state.tr.setStoredMarks(storedMarks);
- tr = selLoadChar === 'Enter' ? tr1.insert(this.EditorView.state.doc.content.size - 1, schema.nodes.paragraph.create()) : tr1.insertText(selLoadChar, this.EditorView.state.doc.content.size - 1);
- }
- this.EditorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size - 1))).setStoredMarks(storedMarks));
+ if (selLoadChar === 'Enter') {
+ splitBlock(this.EditorView.state, (tx3: Transaction) => this.EditorView?.dispatch(tx3.setStoredMarks(storedMarks)));
+ } else if (selLoadChar) {
+ this.EditorView.dispatch(this.EditorView.state.tr.replaceSelectionWith(this.EditorView.state.schema.text(selLoadChar, storedMarks))); // prettier-ignore
+ } else this.EditorView.dispatch(this.EditorView.state.tr.setStoredMarks(storedMarks));
this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
- console.log(this.EditorView.state);
}
if (selectOnLoad) {
this.EditorView!.focus();
@@ -1591,9 +1505,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
componentWillUnmount() {
- if (this._recordingDictation) {
- this._recordingDictation = !this._recordingDictation;
+ if (this.recordingDictation) {
+ this.recordingDictation = !this.recordingDictation;
}
+ removeStyleSheet(this._userStyleSheetElement);
Object.values(this._disposers).forEach(disposer => disposer?.());
this.endUndoTypingBatch();
FormattedTextBox.LiveTextUndo?.end();
@@ -1627,7 +1542,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
});
}
- if (this._recordingDictation && !e.ctrlKey && e.button === 0) {
+ if (this.recordingDictation && !e.ctrlKey && e.button === 0) {
this.breakupDictation();
}
FormattedTextBoxComment.textBox = this;
@@ -1785,8 +1700,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
*/
tryKeepingFocus = (target: Element | null) => {
for (let newFocusEle = target instanceof HTMLElement ? target : null; newFocusEle; newFocusEle = newFocusEle?.parentElement) {
- // test if parent of new focused element is a UI button (should be more specific than testing className)
- if (newFocusEle?.className === 'fonticonbox' || newFocusEle?.className === 'popup-container') {
+ // bcz: HACK!! test if parent of new focused element is a UI button (should be more specific than testing className)
+ if (['fonticonbox', 'antimodeMenu-cont', 'popup-container'].includes(newFocusEle?.className ?? '')) {
return this.EditorView?.focus(); // keep focus on text box
}
}
@@ -1840,13 +1755,30 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
const { state } = _editorView;
if (!state.selection.empty && e.key === '%') {
- this._rules!.EnteringStyle = true;
+ this._enteringStyle = true;
StopEvent(e);
return;
}
+ if (this._enteringStyle && 'tix!'.includes(e.key)) {
+ const tag = e.key === 't' ? 'todo' : e.key === 'i' ? 'ignore' : e.key === 'x' ? 'disagree' : e.key === '!' ? 'important' : '??';
+ const node = state.selection.$from.nodeAfter;
+ const start = state.selection.from;
+ const end = state.selection.to;
+
+ if (node) {
+ StopEvent(e);
+ _editorView.dispatch(
+ state.tr
+ .removeMark(start, end, schema.marks.user_mark)
+ .addMark(start, end, schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }))
+ .addMark(start, end, schema.marks.user_tag.create({ userid: ClientUtils.CurrentUserEmail(), tag, modified: Math.round(Date.now() / 1000 / 60) }))
+ );
+ return;
+ }
+ }
- if (state.selection.empty || !this._rules!.EnteringStyle) {
- this._rules!.EnteringStyle = false;
+ if (state.selection.empty || !this._enteringStyle) {
+ this._enteringStyle = false;
}
for (let i = state.selection.from; i <= state.selection.to; i++) {
const node = state.doc.resolve(i);
@@ -1910,10 +1842,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (this._props.setHeight && !this._props.suppressSetHeight && 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
const setScrollHeight = () => {
- this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight;
+ this.layoutDoc['_' + this.fieldKey + '_scrollHeight'] = scrollHeight;
};
- if (this.Document === this.layoutDoc || this.layoutDoc.resolvedDataDoc) {
+ if (this.Document === this.layoutDoc || this.layoutDoc.rootDocument) {
setScrollHeight();
} else {
setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived...
@@ -1922,7 +1854,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
};
fitContentsToBox = () => BoolCast(this.Document._freeform_fitContentsToBox);
- sidebarContentScaling = () => (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
+ nativeScaling = () => this._props.NativeDimScaling?.() || 1;
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.sidebarKey) => {
if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
return this.addDocument(doc, sidebarKey);
@@ -1930,17 +1862,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.sidebarKey);
setSidebarHeight = (height: number) => {
- this.dataDoc[this.sidebarKey + '_height'] = height;
+ this.layoutDoc['_' + this.sidebarKey + '_height'] = height;
};
- sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
+ sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
sidebarScreenToLocal = () =>
this._props
.ScreenToLocalTransform()
- .translate(-(this._props.PanelWidth() - this.sidebarWidth()) / (this._props.NativeDimScaling?.() || 1), 0)
- .scale(1 / NumCast(this.layoutDoc._freeform_scale, 1) / (this._props.NativeDimScaling?.() || 1));
+ .translate(-(this._props.PanelWidth() - this.sidebarWidth()) / this.nativeScaling(), 0)
+ .scale(1 / this.nativeScaling());
@computed get audioHandle() {
- return !this._recordingDictation ? null : (
+ return !this.recordingDictation ? null : (
<div
className="formattedTextBox-dictation"
onPointerDown={e =>
@@ -1950,7 +1882,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
returnFalse,
emptyFunction,
action(() => {
- this._recordingDictation = !this._recordingDictation;
+ this.recordingDictation = !this.recordingDictation;
})
)
}>
@@ -1958,6 +1890,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
</div>
);
}
+ private _sideBtnWidth = 35;
+ /**
+ * How much the content of the view is being scaled based on its nesting and its fit-to-width settings
+ */
+ @computed get viewScaling() { return this.ScreenToLocalBoxXf().Scale ; } // prettier-ignore
+ /**
+ * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its normal 35 pixel size.
+ */
+ @computed get maxWidgetSize() { return Math.min(this._sideBtnWidth, 0.2 * this._props.PanelWidth())*this.viewScaling; } // prettier-ignore
+ /**
+ * How much to reactively scale a UI element so that it is as big as it can be (up to its normal 35pixel size) without being too big for the Doc content
+ */
+ @computed get uiBtnScaling() { return this.maxWidgetSize / this._sideBtnWidth; } // prettier-ignore
+
@computed get sidebarHandle() {
TraceMobx();
const annotated = DocListCast(this.dataDoc[this.sidebarKey]).filter(d => d?.author).length;
@@ -1972,6 +1918,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
backgroundColor,
color,
opacity: annotated ? 1 : undefined,
+ transform: `scale(${this.uiBtnScaling})`,
}}>
<FontAwesomeIcon icon="comment-alt" />
</div>
@@ -1985,7 +1932,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
<SidebarAnnos
ref={this._sidebarRef}
{...this._props}
- Document={this.Document}
+ Doc={this.Document}
layoutDoc={this.layoutDoc}
dataDoc={this.dataDoc}
usePanelWidth
@@ -2016,7 +1963,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
isAnnotationOverlay={false}
select={emptyFunction}
isAnyChildContentActive={returnFalse}
- NativeDimScaling={this.sidebarContentScaling}
+ NativeDimScaling={this.nativeScaling}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
removeDocument={this.sidebarRemDocument}
moveDocument={this.sidebarMoveDocument}
@@ -2033,7 +1980,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
);
};
return (
- <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+ <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
{renderComponent(StrCast(this.layoutDoc[this.sidebarKey + '_type_collection']))}
</div>
);
@@ -2081,7 +2028,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return this._fieldKey;
}
@computed get _fieldKey() {
- const usePath = this._props.ignoreUsePath ? '' : StrCast(this.layoutDoc[`${this._props.fieldKey}_usePath`]);
+ const usePath = StrCast(this.layoutDoc[`${this._props.fieldKey}_usePath`]);
return this._props.fieldKey + (usePath && (!usePath.includes(':hover') || this._props.isHovering?.() || this._props.isContentActive()) ? `_${usePath.replace(':hover', '')}` : '');
}
onPassiveWheel = (e: WheelEvent) => {
@@ -2091,7 +2038,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// 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)
if (this._props.isContentActive()) {
- const scale = this._props.NativeDimScaling?.() || 1;
+ const scale = this.nativeScaling();
const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
const height = Number(styleFromLayout.height?.replace('px', ''));
// prevent default if selected || child is active but this doc isn't scrollable
@@ -2108,15 +2055,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
render() {
TraceMobx();
- const scale = this._props.NativeDimScaling?.() || 1;
- const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
+ const scale = this.nativeScaling();
+ const rounded = StrCast(this.layoutDoc._layout_borderRounding) === '100%' ? '-rounded' : '';
setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
- const scrSize = (which: number, view = this._props.docViewPath().slice(-which)[0]) =>
- [view._props.PanelWidth() / view.screenToLocalScale(), view._props.PanelHeight() / view.screenToLocalScale()]; // prettier-ignore
- const scrMargin = [Math.max(0, (scrSize(2)[0] - scrSize(1)[0]) / 2), Math.max(0, (scrSize(2)[1] - scrSize(1)[1]) / 2)];
- const paddingX = Math.max(NumCast(this.layoutDoc._xMargin), this._props.xPadding ?? 0, 0, ((this._props.screenXPadding?.() ?? 0) - scrMargin[0]) * this.ScreenToLocalBoxXf().Scale);
- const paddingY = Math.max(NumCast(this.layoutDoc._yMargin), 0, ((this._props.yPadding ?? 0) - scrMargin[1]) * this.ScreenToLocalBoxXf().Scale);
+ const paddingX = Math.max(NumCast(this.layoutDoc._xMargin), 0, this._props.xPadding ?? 0, this._props.screenXPadding?.(this._props.DocumentView?.()) ?? 0);
+ const paddingY = Math.max(NumCast(this.layoutDoc._yMargin), 0, this._props.yPadding ?? 0); // prettier-ignore
const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
return this.isLabel ? (
<LabelBox {...this._props} />
@@ -2137,7 +2081,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
height: `${100 / scale}%`,
}),
transition: 'inherit',
- paddingBottom: this.tagsHeight,
// overflowY: this.layoutDoc._layout_autoHeight ? "hidden" : undefined,
color: this.fontColor,
fontSize: this.fontSize,
@@ -2170,13 +2113,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._scrollRef = r;
}}
style={{
- width: this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
+ width: this.noSidebar ? '100%' : `calc(100% - ${this.sidebarWidthPercent})`,
overflow: this.layoutDoc._createDocOnCR || this.layoutDoc._layout_hideScroll ? 'hidden' : this.layout_autoHeight ? 'visible' : undefined,
}}
onScroll={this.onScroll}
onDrop={this.ondrop}>
<div
- className={`formattedTextBox-inner${rounded} ${this.layoutDoc._layout_centered && this.scrollHeight <= (this._props.fitWidth?.(this.Document) ? this._props.PanelHeight() : NumCast(this.layoutDoc._height)) ? 'centered' : ''} ${this.layoutDoc.hCentering}`}
+ className={`formattedTextBox-inner${rounded} ${this.dataDoc.text_centered && this.scrollHeight <= (this._props.fitWidth?.(this.Document) ? this._props.PanelHeight() : NumCast(this.layoutDoc._height)) ? 'centered' : ''} ${this.layoutDoc.hCentering}`}
ref={this.createDropTarget}
style={{
padding: StrCast(this.layoutDoc._textBoxPadding),
@@ -2190,7 +2133,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}}
/>
</div>
- {this.noSidebar || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
+ {this.noSidebar || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
{this.noSidebar || this.Document._layout_noSidebar || this.Document._createDocOnCR || this.layoutDoc._chromeHidden || this.Document.quiz ? null : this.sidebarHandle}
{this.audioHandle}
{this.layoutDoc._layout_enableAltContentUI && !this.layoutDoc._chromeHidden ? this.overlayAlternateIcon : null}