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.tsx568
1 files changed, 339 insertions, 229 deletions
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0d7914a82..38817ac6d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,11 +13,11 @@ 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, returnFalse, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, imageUrlToBase64, 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 { Id } from '../../../../fields/FieldSymbols';
+import { Id, ToString } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
import { PrefetchProxy } from '../../../../fields/Proxy';
@@ -26,7 +26,7 @@ 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 } from '../../../apis/gpt/GPT';
+import { gptAPICall, GPTCallType, gptImageLabel } from '../../../apis/gpt/GPT';
import { DocServer } from '../../../DocServer';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -65,6 +65,8 @@ 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 {
@@ -77,35 +79,36 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
public static MakeConfig(rules?: RichTextRules, props?: FormattedTextBoxProps) {
- const keymapping = buildKeymap(schema, props ?? {});
return {
schema,
plugins: [
inputRules(rules?.inpRules ?? { rules: [] }),
...(props ? [FormattedTextBox.richTextMenuPlugin(props)] : []),
history(),
- keymap(keymapping),
+ keymap(buildKeymap(schema, props ?? {})),
keymap(baseKeymap),
new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }),
new Plugin({ view: () => new FormattedTextBoxComment() }),
],
};
}
- private static nodeViews: (self: FormattedTextBox) => { [key: string]: NodeViewConstructor };
/**
* Initialize the class with all the plugin node view components
* @param nodeViews prosemirror plugins that render a custom UI for specific node types
*/
- public static Init(nodeViews: (self: FormattedTextBox) => { [key: string]: NodeViewConstructor }) {
- FormattedTextBox.nodeViews = nodeViews;
- }
+ public static Init(nodeViews: (self: FormattedTextBox) => { [key: string]: NodeViewConstructor }) { FormattedTextBox._nodeViews = nodeViews; } // prettier-ignore
+
+ public static PasteOnLoad: ClipboardEvent | undefined;
+ public static SelectOnLoadChar = '';
public static LiveTextUndo: UndoManager.Batch | undefined; // undo batch when typing a new text note into a collection
- static _globalHighlightsCache: string = '';
- static _globalHighlights = new ObservableSet<string>(['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items']);
- static _highlightStyleSheet = addStyleSheet();
- static _bulletStyleSheet = addStyleSheet();
- static _userStyleSheet = addStyleSheet();
- static _hadSelection: boolean = false;
+
+ 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 _oldWheel: HTMLDivElement | null = null;
private _selectionHTML: string | undefined;
private _sidebarRef = React.createRef<SidebarAnnos>();
@@ -113,7 +116,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _scrollRef: HTMLDivElement | null = null;
private _editorView: Opt<EditorView & { TextView?: FormattedTextBox | undefined }>;
- public _applyingChange: string = '';
private _inDrop = false;
private _finishingLink = false;
private _searchIndex = 0;
@@ -127,38 +129,45 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
private _rules: RichTextRules | undefined;
private _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle
private _break = true;
+
public ProseRef?: HTMLDivElement;
+
+ /**
+ * ApplyingChange - Marks whether an interactive text edit is currently in the process of being written to the database.
+ * This is needed to distinguish changes to text fields caused by editing vs those caused by changes to
+ * the prototype or other external edits
+ */
+ public ApplyingChange: string = '';
+
+ @observable _showSidebar = false;
+
+ @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
+
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 allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } // 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 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 sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.dataDoc[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 config() {
- this._rules = new RichTextRules(this.Document, this);
- return FormattedTextBox.MakeConfig(this._rules, this._props);
- }
-
- public get EditorView() {
- return this._editorView;
- }
- public get SidebarKey() {
- return this.fieldKey + '_sidebar';
- }
- public makeAIFlashcards: () => void = unimplementedFunction;
- public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
-
- public static PasteOnLoad: ClipboardEvent | undefined;
- public static DontSelectInitialText = false; // whether initial text should be selected or not
- public static SelectOnLoadChar = '';
+ @computed get sidebarKey() { return this.fieldKey + '_sidebar'; } // prettier-ignore
+ @computed get isLabel() { return this.dataDoc[this.fieldKey+"_fitBox"]; } // prettier-ignore
constructor(props: FormattedTextBoxProps) {
super(props);
@@ -166,15 +175,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._recordingStart = Date.now();
}
+ public get EditorView() { return this.isLabel ? undefined : this._editorView; } // prettier-ignore
+
+ // public makeAIFlashcards: () => void = unimplementedFunction;
+ public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
+
// removes all hyperlink anchors for the removed linkDoc
// TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one.
// but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing.
public RemoveLinkFromDoc(linkDoc?: Doc) {
this.unhighlightSearchTerms();
- const state = this._editorView?.state;
+ const state = this.EditorView?.state;
const a1 = DocCast(linkDoc?.link_anchor_1);
const a2 = DocCast(linkDoc?.link_anchor_2);
- if (state && a1 && a2 && this._editorView) {
+ if (state && a1 && a2 && this.EditorView) {
this.removeDocument(a1);
this.removeDocument(a2);
let allFoundLinkAnchors: { href: string; title: string; anchorId: string }[] = [];
@@ -184,7 +198,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return true;
});
if (allFoundLinkAnchors.length) {
- this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.linkAnchor, { allAnchors: allFoundLinkAnchors }));
+ this.EditorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.linkAnchor, { allAnchors: allFoundLinkAnchors }));
this.setupEditor(this.config, this.fieldKey);
}
@@ -193,16 +207,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// removes all the specified link references from the selection.
// NOTE: as above, this won't work correctly if there are marks with overlapping but not exact sets of link references.
public RemoveAnchorFromSelection(allAnchors: { href: string; title: string; linkId: string; targetId: string }[]) {
- const state = this._editorView?.state;
- if (state && this._editorView) {
- this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allAnchors }));
+ const state = this.EditorView?.state;
+ if (state && this.EditorView) {
+ this.EditorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allAnchors }));
this.setupEditor(this.config, this.fieldKey);
}
}
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;
+ if (!pinProps && this.EditorView?.state.selection.empty) return rootDoc;
const anchor = Docs.Create.ConfigDocument({ title: StrCast(rootDoc.title), annotationOn: rootDoc });
this.addDocument(anchor);
this._finishingLink = true;
@@ -212,9 +226,24 @@ 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';
+ // AnchorMenu.Instance.gptFlashcards = this.selectionToFlashcards;
+ AnchorMenu.Instance.makeLabels = unimplementedFunction;
+ AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument;
AnchorMenu.Instance.OnClick = () => {
!this.layoutDoc.layout_showSidebar && this.toggleSidebar();
setTimeout(() => this._sidebarRef.current?.anchorMenuClick(this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true))); // give time for sidebarRef to be created
@@ -245,7 +274,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
});
};
- AnchorMenu.Instance.Highlight = undoable((color: string) => this._editorView?.state && RichTextMenu.Instance?.setFontField(color, 'fontHighlight'), 'highlght text');
+ AnchorMenu.Instance.Highlight = undoable((color: string) => this.EditorView?.state && RichTextMenu.Instance?.setFontField(color, 'fontHighlight'), 'highlght text');
AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
AnchorMenu.Instance.StartCropDrag = unimplementedFunction;
/**
@@ -257,7 +286,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
e.stopPropagation();
const targetCreator = (annotationOn?: Doc) => {
const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn);
- Doc.SetSelectOnLoad(target);
+ DocumentView.SetSelectOnLoad(target);
return target;
};
@@ -274,7 +303,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
AnchorMenu.Instance.setSelectedText(window.getSelection()?.toString() ?? '');
- const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
+ const coordsB = this.EditorView!.coordsAtPos(this.EditorView!.state.selection.to);
this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
let ele: Opt<HTMLDivElement>;
try {
@@ -291,7 +320,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
leafText = (node: Node) => {
- if (node.type === this._editorView?.state.schema.nodes.dashField) {
+ 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 fieldKey = StrCast(node.attrs.fieldKey);
return (
@@ -299,19 +328,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
(node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as FieldType))
);
}
+ 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);
+ return refDoc[ToString]();
+ }
return '';
};
dispatchTransaction = (tx: Transaction) => {
- if (this._editorView && !this._editorView.isDestroyed) {
- const state = this._editorView.state.apply(tx);
- this._editorView.updateState(state);
+ if (this.EditorView && !this.EditorView.isDestroyed) {
+ const state = this.EditorView.state.apply(tx);
+ this.EditorView.updateState(state);
this.tryUpdateDoc(false);
}
};
tryUpdateDoc = (force: boolean) => {
- if (this._editorView) {
- const { state } = this._editorView;
+ if (this.EditorView) {
+ const { state } = this.EditorView;
const { dataDoc } = this;
const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText);
const newJson = JSON.stringify(state.toJSON());
@@ -336,27 +369,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
let unchanged = true;
const textChange = newText !== prevData?.Text; // the Text string can change even if the RichText doesn't because dashFieldViews may return new strings as the data they reference changes
const rtField = (layoutData !== prevData ? layoutData : undefined) ?? protoData;
- if (this._applyingChange !== this.fieldKey && (force || textChange || removeSelection(newJson) !== removeSelection(prevData?.Data))) {
- this._applyingChange = this.fieldKey;
- textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
+ if (this.ApplyingChange !== this.fieldKey && (force || textChange || removeSelection(newJson) !== removeSelection(prevData?.Data))) {
+ this.ApplyingChange = this.fieldKey;
if ((!prevData && !protoData && !layoutData) || newText || (!newText && !protoData && !layoutData)) {
// if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
if (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && (textChange || removeSelection(newJson) !== removeSelection(prevData?.Data)))) {
+ textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
+ textChange && (dataDoc[this.fieldKey + '_placeholder'] = undefined);
const numstring = NumCast(dataDoc[this.fieldKey], null);
dataDoc[this.fieldKey] =
numstring !== undefined ? Number(newText) : newText || (DocCast(dataDoc.proto)?.[this.fieldKey] === undefined && this.layoutDoc[this.fieldKey] === undefined) ? new RichTextField(newJson, newText) : undefined;
textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.Document, text: newText });
- this._applyingChange = ''; // turning this off here allows a Doc to retrieve data from template if noTemplate below is changed to false
+ this.ApplyingChange = ''; // turning this off here allows a Doc to retrieve data from template if noTemplate below is changed to false
unchanged = false;
}
} else if (rtField) {
+ textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
// if we've deleted all the text in a note driven by a template, then restore the template data
dataDoc[this.fieldKey] = undefined;
- this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(rtField.Data)));
+ this.EditorView.updateState(EditorState.fromJSON(this.config, JSON.parse(rtField.Data)));
ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, text: newText });
unchanged = false;
}
- this._applyingChange = '';
+ this.ApplyingChange = '';
if (!unchanged) {
this.updateTitle();
this.tryUpdateScrollHeight();
@@ -367,7 +402,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (jsonstring) {
const json = JSON.parse(jsonstring);
json.selection = state.toJSON().selection;
- this._editorView.updateState(EditorState.fromJSON(this.config, json));
+ this.EditorView.updateState(EditorState.fromJSON(this.config, json));
}
}
if (window.getSelection()?.isCollapsed && this._props.rootSelected?.()) {
@@ -387,8 +422,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
linkAnchor = anchor;
}
});
- if (this._editorView && linkTime) {
- const { state } = this._editorView;
+ if (this.EditorView && linkTime) {
+ const { state } = this.EditorView;
const node = state.selection.$from.node();
if (linkAnchor && node.type !== state.schema.nodes.code_block) {
const time = linkTime + Date.now() / 1000 - this._recordingStart / 1000;
@@ -396,7 +431,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const { from } = state.selection;
const value = state.schema.nodes.audiotag.create({ timeCode: time, audioId: linkAnchor[Id] });
const replaced = state.tr.insert(from - 1, value);
- this._editorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + 1))));
+ this.EditorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + 1))));
}
}
};
@@ -411,16 +446,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
(Doc.isTemplateForField(this.Document) && (link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document))) &&
link.link_relationship === LinkManager.AutoKeywords
); // prettier-ignore
- if (this._editorView?.state.doc.textContent) {
- let { tr } = this._editorView.state;
- const { from, to } = this._editorView.state.selection;
- const { autoLinkAnchor } = this._editorView.state.schema.marks;
+ if (this.EditorView?.state.doc.textContent) {
+ let { tr } = this.EditorView.state;
+ const { from, to } = this.EditorView.state.selection;
+ const { autoLinkAnchor } = this.EditorView.state.schema.marks;
tr = tr.removeMark(0, tr.doc.content.size, autoLinkAnchor);
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)));
- this._editorView?.dispatch(tr);
+ this.EditorView?.dispatch(tr);
}
oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(doc => Doc.DeleteLink?.(doc));
};
@@ -430,11 +465,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (
!this._props.dontRegisterView && // (this.Document.isTemplateForField === "text" || !this.Document.isTemplateForField) && // only update the title if the data document's data field is changing
title.startsWith('-') &&
- this._editorView &&
+ this.EditorView &&
!this.dataDoc.title_custom &&
(Doc.LayoutFieldKey(this.Document) === this.fieldKey || this.fieldKey === 'text')
) {
- let node = this._editorView.state.doc;
+ let node = this.EditorView.state.doc;
while (node.firstChild && node.firstChild.type.name !== 'text') node = node.firstChild;
const str = node.textContent;
const prefix = '-';
@@ -457,7 +492,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
*/
hyperlinkTerm = (trIn: Transaction, target: Doc, newAutoLinks: Set<Doc>) => {
let tr = trIn;
- const editorView = this._editorView;
+ const editorView = this.EditorView;
if (editorView && !Doc.AreProtosEqual(target, this.Document)) {
const autoLinkTerm = Field.toString(target.title as FieldType).replace(/^@/, '');
let alink: Doc | undefined;
@@ -533,18 +568,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
unhighlightSearchTerms = () => {
- if (this._editorView) {
- const { state } = this._editorView;
+ if (this.EditorView) {
+ const { state } = this.EditorView;
if (state) {
const mark = state.schema.mark(state.schema.marks.search_highlight);
const activeMark = state.schema.mark(state.schema.marks.search_highlight, { selected: true });
const end = state.doc.nodeSize - 2;
- this._editorView.dispatch(state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
+ this.EditorView.dispatch(state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
}
}
};
adoptAnnotation = (start: number, end: number, mark: Mark) => {
- const view = this._editorView!;
+ const view = this.EditorView!;
const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: ClientUtils.CurrentUserEmail() });
view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
};
@@ -596,7 +631,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (added) {
draggedDoc._freeform_fitContentsToBox = true;
Doc.SetContainer(draggedDoc, this.Document);
- const view = this._editorView!;
+ const view = this.EditorView!;
try {
this._inDrop = true;
const pos = view.posAtCoords({ left: de.x, top: de.y })?.pos;
@@ -717,18 +752,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
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)
};
- @observable _showSidebar = false;
- @computed get SidebarShown() {
- return !!(this._showSidebar || this.layoutDoc._layout_showSidebar);
- }
-
@action
toggleSidebar = (preview: boolean = false) => {
const defaultSidebar = 250;
const prevWidth = 1 - this.sidebarWidth() / DivWidth(this._ref.current!);
if (preview) this._showSidebar = true;
else {
- this.layoutDoc[this.SidebarKey + '_freeform_scale_max'] = 1;
+ 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%';
}
@@ -750,10 +780,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
);
};
sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
- const localDelta = this._props
- .ScreenToLocalTransform()
- .scale(this._props.NativeDimScaling?.() || 1)
- .transformDirection(delta[0], delta[1]);
+ const localDelta = this.DocumentView?.().screenToViewTransform().transformDirection(delta[0], delta[1]) ?? delta;
const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.layout_sidebarWidthPercent.replace('%', ''))) / 100;
const width = NumCast(this.layoutDoc._width) + localDelta[0];
this.layoutDoc._layout_sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%';
@@ -793,11 +820,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
isTargetToggler = (anchor: Doc) => BoolCast(anchor.followLinkToggle);
specificContextMenu = (e: React.MouseEvent): void => {
+ if (this._props.dontSelect?.()) return;
const cm = ContextMenu.Instance;
let target: Element | HTMLElement | null = e.target as HTMLElement; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>
while (target && (!(target instanceof HTMLElement) || !target.dataset?.targethrefs)) target = target.parentElement;
- const editor = this._editorView;
+ const editor = this.EditorView;
if (editor && target && !(e.nativeEvent instanceof simMouseEvent ? e.nativeEvent.dash : false)) {
const hrefs = (target.dataset?.targethrefs as string)
?.trim()
@@ -886,6 +914,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
);
const appearance = cm.findByDescription('Appearance...');
const appearanceItems = appearance?.subitems ?? [];
+ // appearanceItems.push({
+ // description: 'Find image tags',
+ // event: this.findImageTags,
+ // icon: !this.Document._layout_noSidebar ? 'eye-slash' : 'eye',
+ // });
appearanceItems.push({
description: !this.Document._layout_noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle',
@@ -948,8 +981,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
},
icon: 'star',
});
- optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
- optionItems.push({ description: `Make AI Flashcards`, event: () => this.makeAIFlashcards(), icon: 'lightbulb' });
+ optionItems.push({ description: `Generate Dall-E Image`, event: this.generateImage, icon: 'star' });
+ // optionItems.push({ description: `Make AI Flashcards`, event: () => this.makeAIFlashcards(), icon: 'lightbulb' });
optionItems.push({ description: `Ask GPT-3`, event: this.askGPT, icon: 'lightbulb' });
this._props.renderDepth &&
optionItems.push({
@@ -967,6 +1000,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
},
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')),
+ icon: this.Document.savedAsSticker ? 'clipboard-check' : 'file-arrow-down',
+ });
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
const help = cm.findByDescription('Help...');
const helpItems = help?.subitems ?? [];
@@ -974,23 +1012,48 @@ 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 ?? [];
- this._editorView?.dispatch(this._editorView?.state.tr.insertText(newText[resIndex]).setStoredMarks(marks));
+ const marks = this.EditorView?.state.storedMarks ?? [];
+ this.EditorView?.dispatch(this.EditorView?.state.tr.insertText(newText[resIndex]).setStoredMarks(marks));
setTimeout(() => this.animateRes(resIndex + 1, newText), 20);
}
};
askGPT = action(async () => {
try {
- GPTPopup.Instance.setSidebarId(this.SidebarKey);
+ GPTPopup.Instance.setSidebarFieldKey(this.sidebarKey);
GPTPopup.Instance.addDoc = this.sidebarAddDocument;
const res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
if (!res) {
this.animateRes(0, 'Something went wrong.');
- } else if (this._editorView) {
- const { dispatch, state } = this._editorView;
+ } else if (this.EditorView) {
+ const { dispatch, state } = this.EditorView;
// for no animation, use: dispatch(state.tr.insertText(res));
// for animted response starting at end of text, use:
dispatch(state.tr.setSelection(Selection.atEnd(state.doc)));
@@ -1002,22 +1065,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
});
- generateImage = async () => {
+ generateImage = () => {
GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
- GPTPopup.Instance?.setImgTargetDoc(this.Document);
- GPTPopup.Instance.addToCollection = this._props.addDocument;
- GPTPopup.Instance.setImgDesc((this.dataDoc.text as RichTextField)?.Text);
- GPTPopup.Instance.generateImage();
+ GPTPopup.Instance.generateImage((this.dataDoc.text as RichTextField)?.Text, this.Document, this._props.addDocument);
};
breakupDictation = () => {
- if (this._editorView && this._recordingDictation) {
+ if (this.EditorView && this._recordingDictation) {
this.stopDictation(/* true */);
this._break = true;
- const { state } = this._editorView;
+ 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({})));
+ this.EditorView.dispatch(state.tr.setSelection(updated).insert(to, state.schema.nodes.paragraph.create({})));
if (this._recordingDictation) {
this.recordDictation();
}
@@ -1036,7 +1096,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
stopDictation = (/* abort: boolean */) => DictationManager.Controls.stop(/* !abort */);
setDictationContent = (value: string) => {
- if (this._editorView && this._recordingStart) {
+ if (this.EditorView && this._recordingStart) {
if (this._break) {
const textanchorFunc = () => {
const tanch = Docs.Create.ConfigDocument({ title: 'dictation anchor' });
@@ -1049,22 +1109,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const textanchor = Cast(link.link_anchor_1, Doc, null);
if (audioanchor) {
audioanchor.backgroundColor = 'tan';
- const audiotag = this._editorView.state.schema.nodes.audiotag.create({
+ const audiotag = this.EditorView.state.schema.nodes.audiotag.create({
timeCode: NumCast(audioanchor._timecodeToShow),
audioId: audioanchor[Id],
textId: textanchor[Id],
});
textanchor[DocData].title = 'dictation:' + audiotag.attrs.timeCode;
- const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag);
+ 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)));
+ this.EditorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size)));
}
}
}
- const { from } = this._editorView.state.selection;
+ const { from } = this.EditorView.state.selection;
this._break = false;
- const tr = this._editorView.state.tr.insertText(value);
- this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, from, tr.doc.content.size)).scrollIntoView());
+ const tr = this.EditorView.state.tr.insertText(value);
+ this.EditorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, from, tr.doc.content.size)).scrollIntoView());
}
};
@@ -1097,7 +1157,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
});
this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
- this._editorView!.dispatch(tr.removeMark(selection.from, selection.to, splitter));
+ this.EditorView!.dispatch(tr.removeMark(selection.from, selection.to, splitter));
this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
anchor.text = selectedText;
anchor.text_html = this._selectionHTML ?? selectedText;
@@ -1110,8 +1170,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return anchorDoc ?? this.Document;
}
- getView = async (doc: Doc, options: FocusViewOptions) => {
- if (DocListCast(this.dataDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
+ getView = (doc: Doc, options: FocusViewOptions) => {
+ if (DocListCast(this.dataDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
if (!this.SidebarShown) {
this.toggleSidebar(false);
options.didMove = true;
@@ -1131,7 +1191,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
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.dashDoc || 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;
@@ -1140,13 +1200,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return { frag: Fragment.fromArray(nodes), start };
};
const findAnchorNode = (node: Node, editor: EditorView) => {
- if (node.type === this._editorView?.state.schema.nodes.audiotag) {
+ if (node.type === this.EditorView?.state.schema.nodes.audiotag) {
if (node.attrs.textId === textAnchorId) {
return { node, start: 0 };
}
return undefined;
}
- if (node.type === this._editorView?.state.schema.nodes.dashDoc) {
+ if (node.type === this.EditorView?.state.schema.nodes.dashDoc) {
if (node.attrs.docId === textAnchorId) {
return { node, start: 0 };
}
@@ -1162,9 +1222,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
this._didScroll = false; // assume we don't need to scroll. if we do, this will get set to true in handleScrollToSelextion when we dispatch the setSelection below
- if (this._editorView && textAnchorId) {
- const { state } = this._editorView;
- const ret = findAnchorFrag(state.doc.content, this._editorView);
+ if (this.EditorView && textAnchorId) {
+ const { state } = this.EditorView;
+ const ret = findAnchorFrag(state.doc.content, this.EditorView);
const firstChild = ret.frag.childCount ? ret.frag.child(0) : undefined;
if (ret.start >= 0 && (ret.frag.size || (firstChild && [state.schema.nodes.dashDoc, state.schema.nodes.audioTag].includes(firstChild.type)))) {
@@ -1173,7 +1233,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (ret.frag.firstChild) {
selection = TextSelection.between(state.doc.resolve(ret.start), state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
}
- this._editorView.dispatch(state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
+ this.EditorView.dispatch(state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
const escAnchorId = textAnchorId[0] >= '0' && textAnchorId[0] <= '9' ? `\\3${textAnchorId[0]} ${textAnchorId.substr(1)}` : textAnchorId;
addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' });
setTimeout(() => {
@@ -1207,7 +1267,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._cachedLinks = Doc.Links(this.Document);
this._disposers.breakupDictation = reaction(() => Doc.RecordingEvent, this.breakupDictation);
this._disposers.layout_autoHeight = reaction(
- () => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize, css: this.Document[DocCss] }),
+ () => ({ 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(
@@ -1226,14 +1286,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
);
this._disposers.componentHeights = reaction(
// set the document height when one of the component heights changes and layout_autoHeight is on
- () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins, tagsHeight: this.tagsHeight }),
- ({ sidebarHeight, textHeight, layoutAutoHeight, marginsHeight, tagsHeight }) => {
- const newHeight = this.contentScaling * (tagsHeight + marginsHeight + Math.max(sidebarHeight, textHeight));
+ () => ({ border: this._props.PanelHeight(), sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }),
+ ({ 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()) && //
layoutAutoHeight &&
newHeight &&
- newHeight !== this.layoutDoc.height &&
+ (newHeight !== this.layoutDoc.height || border < NumCast(this.layoutDoc.height)) &&
!this._props.dontRegisterView
) {
this._props.setHeight?.(newHeight);
@@ -1261,15 +1321,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return !whichData ? undefined : { data: RTFCast(whichData), str: Field.toString(DocCast(whichData) ?? StrCast(whichData)) };
},
incomingValue => {
- if (this._editorView && this._applyingChange !== this.fieldKey) {
+ if (this.EditorView && this.ApplyingChange !== this.fieldKey) {
if (incomingValue?.data) {
const updatedState = JSON.parse(incomingValue.data.Data);
- if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) {
- this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
+ if (JSON.stringify(this.EditorView.state.toJSON()) !== JSON.stringify(updatedState)) {
+ this.EditorView.updateState(EditorState.fromJSON(this.config, updatedState));
this.tryUpdateScrollHeight();
}
- } else if (this._editorView.state.doc.textContent !== incomingValue?.str) {
- selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue?.str ?? '')));
+ } else if (this.EditorView.state.doc.textContent !== (incomingValue?.str ?? '')) {
+ selectAll(this.EditorView.state, tx => this.EditorView?.dispatch(tx.insertText(incomingValue?.str ?? '')));
}
}
},
@@ -1283,18 +1343,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
);
this._disposers.selected = reaction(
- () => this._props.rootSelected?.(),
+ () => this._props.rootSelected?.() || this._props.isContentActive(),
action(selected => {
+ if (selected && this.dataDoc[this.fieldKey + '_placeholder']) {
+ setTimeout(() => {
+ selectAll(this.EditorView!.state, (tx: Transaction) => {
+ this.EditorView?.dispatch(tx);
+ this.EditorView!.focus();
+ });
+ });
+ }
this.prepareForTyping();
if (FormattedTextBox._globalHighlights.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 && !selected) {
+ if (((RichTextMenu.Instance?.view === this.EditorView && this.EditorView) || this.isLabel) && !selected) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
}
- if (this._editorView && selected) {
- RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this._props, this.layoutDoc);
- setTimeout(this.autoLink, 20);
+ if (selected) {
+ RichTextMenu.Instance?.updateMenu(this.EditorView, undefined, this._props, this.dataDoc);
+ this.EditorView && setTimeout(this.autoLink, 20);
}
}),
{ fireImmediately: true }
@@ -1323,7 +1391,32 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
{ fireImmediately: true }
);
this.tryUpdateScrollHeight();
+
+ if (this.Document.image) {
+ // const node = schema.nodes.dashDoc.create({
+ // width: 200,
+ // height: 200,
+ // title: 'dashDoc',
+ // docId: DocCast(this.Document.image)[Id],
+ // float: 'unset',
+ // });
+
+ // DocCast(this.Document.image)._freeform_fitContentsToBox = true;
+ // Doc.SetContainer(DocCast(this.Document.image), this.Document);
+ // const view = this.EditorView!;
+ // try {
+ // this._inDrop = true;
+ // const pos = view.posAtCoords({ left: 0, top: 0 })?.pos;
+ // pos && view.dispatch(view.state.tr.insert(pos, node));
+ // } catch (err) {
+ // console.log('Drop failed', err);
+ // }
+ this.addDocument?.(DocCast(this.Document.image));
+ }
+
+ //if (this.Document.image) this.addDocument?.(DocCast(this.Document.image));
setTimeout(this.tryUpdateScrollHeight, 250);
+ AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument;
}
clipboardTextSerializer = (slice: Slice): string => {
@@ -1356,7 +1449,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
addPdfReference = (pdfAnchorId: string) => {
- const view = this._editorView!;
+ const view = this.EditorView!;
if (pdfAnchorId) {
DocServer.GetRefField(pdfAnchorId).then(pdfAnchor => {
if (pdfAnchor instanceof Doc) {
@@ -1407,7 +1500,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
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();
+ this.EditorView?.destroy();
this._editorView = new EditorView(this.ProseRef, {
state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
handleScrollToSelection: editorView => {
@@ -1431,83 +1524,68 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return true;
},
dispatchTransaction: this.dispatchTransaction,
- nodeViews: FormattedTextBox.nodeViews(this),
+ nodeViews: FormattedTextBox._nodeViews(this),
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
});
- const { state, dispatch } = this._editorView;
+ const { state } = this._editorView;
if (!rtfField) {
const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
const startupText = Field.toString(dataDoc[fieldKey] as FieldType);
- if (startupText) {
- dispatch(state.tr.insertText(startupText));
- }
- const textAlign = StrCast(this.dataDoc.text_align, StrCast(Doc.UserDoc().textAlign, 'left'));
+ const textAlign = StrCast(this.dataDoc[this.fieldKey + '_align'], StrCast(Doc.UserDoc().textAlign)) || 'left';
if (textAlign !== 'left') {
selectAll(this._editorView.state, tr => {
- this._editorView!.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: textAlign })));
+ this.EditorView?.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: textAlign })));
});
}
+ if (startupText) {
+ this.EditorView?.dispatch(this.EditorView.state.tr.insertText(startupText));
+ }
+ this.tryUpdateDoc(true);
}
this._editorView.TextView = this;
}
- const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, Doc.SelectOnLoad) && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.()));
+ const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, DocumentView.SelectOnLoad) && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.()));
const selLoadChar = FormattedTextBox.SelectOnLoadChar;
if (selectOnLoad) {
- Doc.SetSelectOnLoad(undefined);
+ DocumentView.SetSelectOnLoad(undefined);
FormattedTextBox.SelectOnLoadChar = '';
}
- if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
- this._props.select(false);
+ 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 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 $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
- 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];
- const tr1 = this._editorView.state.tr.setStoredMarks(storedMarks);
- const tr2 = 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);
- const tr = tr2.setStoredMarks(storedMarks);
-
- this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
- 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
- } else if (!FormattedTextBox.DontSelectInitialText) {
- const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) });
- selectAll(this._editorView.state, (tx: Transaction) => {
- this._editorView?.dispatch(tx.addStoredMark(mark));
- });
- 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
- } else {
- const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
- 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];
- const { tr } = this._editorView.state;
- this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))).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
+ 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));
+ 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) {
- FormattedTextBox.DontSelectInitialText = false;
- this._editorView!.focus();
+ this.EditorView!.focus();
}
if (this._props.isContentActive()) this.prepareForTyping();
- if (this._editorView && FormattedTextBox.PasteOnLoad) {
+ if (this.EditorView && FormattedTextBox.PasteOnLoad) {
const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
FormattedTextBox.PasteOnLoad = undefined;
pdfAnchorId && this.addPdfReference(pdfAnchorId);
}
- if (this._props.autoFocus) setTimeout(() => this._editorView!.focus()); // not sure why setTimeout is needed but editing dashFieldView's doesn't work without it.
+ if (this._props.autoFocus) setTimeout(() => this.EditorView!.focus()); // not sure why setTimeout is needed but editing dashFieldView's doesn't work without it.
}
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
prepareForTyping = () => {
- if (this._editorView) {
+ if (this.EditorView) {
const { text, paragraph } = schema.nodes;
- const selNode = this._editorView.state.selection.$anchor.node();
- if (this._editorView.state.selection.from === 1 && this._editorView.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) {
+ const selNode = this.EditorView.state.selection.$anchor.node();
+ if (this.EditorView.state.selection.from === 1 && this.EditorView.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) {
const docDefaultMarks = [schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })];
- this._editorView.state.selection.empty && this._editorView.state.selection.from === 1 && this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks).removeStoredMark(schema.marks.pFontColor));
+ this.EditorView.state.selection.empty && this.EditorView.state.selection.from === 1 && this.EditorView?.dispatch(this.EditorView?.state.tr.setStoredMarks(docDefaultMarks).removeStoredMark(schema.marks.pFontColor));
}
}
};
@@ -1521,7 +1599,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
FormattedTextBox.LiveTextUndo?.end();
FormattedTextBox.LiveTextUndo = undefined;
this.unhighlightSearchTerms();
- this._editorView?.destroy();
+ this.EditorView?.destroy();
RichTextMenu.Instance?.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined, undefined);
FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = 'none');
}
@@ -1566,7 +1644,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
};
onSelectEnd = () => {
- GPTPopup.Instance.setSidebarId(this.SidebarKey);
+ GPTPopup.Instance.setSidebarFieldKey(this.sidebarKey);
GPTPopup.Instance.addDoc = this.sidebarAddDocument;
document.removeEventListener('pointerup', this.onSelectEnd);
};
@@ -1578,7 +1656,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
for (let target: HTMLElement | Element | null = clickTarget as HTMLElement; target instanceof HTMLElement && !target.dataset?.targethrefs; target = target.parentElement);
while (clickTarget instanceof HTMLElement && !clickTarget.dataset?.targethrefs) clickTarget = clickTarget.parentElement;
const dataset = clickTarget instanceof HTMLElement ? clickTarget?.dataset : undefined;
- FormattedTextBoxComment.update(this, this.EditorView!, undefined, dataset?.targethrefs, dataset?.linkdoc, dataset?.nopreview === 'true');
+
+ if (dataset?.targethrefs && !dataset.targethrefs.startsWith('/doc'))
+ window
+ .open(
+ dataset?.targethrefs
+ ?.trim()
+ .split(' ')
+ .filter(h => h)
+ .lastElement(),
+ '_blank'
+ )
+ ?.focus();
+ else FormattedTextBoxComment.update(this, this.EditorView!, undefined, dataset?.targethrefs, dataset?.linkdoc, dataset?.nopreview === 'true');
}
};
@action
@@ -1600,26 +1690,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
};
setFocus = (ipos?: number) => {
- const pos = ipos ?? (this._editorView?.state.selection.$from.pos || 1);
- setTimeout(() => this._editorView?.dispatch(this._editorView.state.tr.setSelection(TextSelection.near(this._editorView.state.doc.resolve(pos)))), 100);
+ const pos = ipos ?? (this.EditorView?.state.selection.$from.pos || 1);
+ setTimeout(() => this.EditorView?.dispatch(this.EditorView.state.tr.setSelection(TextSelection.near(this.EditorView.state.doc.resolve(pos)))), 100);
setTimeout(() => (this.ProseRef?.children?.[0] as HTMLElement).focus(), 200);
};
+
@action
onFocused = (e: React.FocusEvent): void => {
- // applyDevTools.applyDevTools(this._editorView);
+ // applyDevTools.applyDevTools(this.EditorView);
e.stopPropagation();
};
onClick = (e: React.MouseEvent): void => {
if (!this._props.isContentActive()) return;
- const editorView = this._editorView;
+ const editorView = this.EditorView;
const editorRoot = editorView?.root instanceof Document ? editorView.root : undefined;
if (editorView && (!this._forceUncollapse || editorRoot?.getSelection()?.isCollapsed)) {
// this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
const pcords = editorView.posAtCoords({ left: e.clientX, top: e.clientY });
const node = pcords && editorView.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
if (pcords && node?.type === editorView.state.schema.nodes.dashComment) {
- this._editorView!.dispatch(editorView.state.tr.setSelection(TextSelection.create(editorView.state.doc, pcords.pos + 2)));
+ this.EditorView!.dispatch(editorView.state.tr.setSelection(TextSelection.create(editorView.state.doc, pcords.pos + 2)));
e.preventDefault();
}
if (!node && this.ProseRef) {
@@ -1645,33 +1736,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean, selectOrderedList: boolean = false) {
this._forceUncollapse = false;
clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
- const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
+ const clickPos = this.EditorView!.posAtCoords({ left: x, top: y });
const clickPosVal = clickPos?.pos || 1;
let olistPos = clickPosVal;
if (clickPos && olistPos && this._props.rootSelected?.()) {
- const clickNode = this._editorView?.state.doc.resolve(olistPos).node();
- const nodeBef = this._editorView?.state.doc.resolve(Math.max(0, olistPos - 1)).node();
- olistPos = nodeBef?.type === this._editorView?.state.schema.nodes.ordered_list ? olistPos - 1 : olistPos;
- let $olistPos = this._editorView?.state.doc.resolve(olistPos);
- let olistNode = (nodeBef !== null || clickNode?.type === this._editorView?.state.schema.nodes.list_item) && olistPos === clickPos?.pos ? clickNode : nodeBef;
- if (olistNode?.type === this._editorView?.state.schema.nodes.list_item) {
+ const clickNode = this.EditorView?.state.doc.resolve(olistPos).node();
+ const nodeBef = this.EditorView?.state.doc.resolve(Math.max(0, olistPos - 1)).node();
+ olistPos = nodeBef?.type === this.EditorView?.state.schema.nodes.ordered_list ? olistPos - 1 : olistPos;
+ let $olistPos = this.EditorView?.state.doc.resolve(olistPos);
+ let olistNode = (nodeBef !== null || clickNode?.type === this.EditorView?.state.schema.nodes.list_item) && olistPos === clickPos?.pos ? clickNode : nodeBef;
+ if (olistNode?.type === this.EditorView?.state.schema.nodes.list_item) {
if ($olistPos && $olistPos.depth) {
olistNode = $olistPos.parent;
- $olistPos = this._editorView?.state.doc.resolve($olistPos.start($olistPos.depth - 1));
+ $olistPos = this.EditorView?.state.doc.resolve($olistPos.start($olistPos.depth - 1));
}
}
- const maxSize = this._editorView?.state.doc.content.size ?? 0;
- const listPos = this._editorView?.state.doc.resolve(Math.min(maxSize, clickPosVal === olistPos ? clickPosVal + 1 : clickPosVal));
+ const maxSize = this.EditorView?.state.doc.content.size ?? 0;
+ const listPos = this.EditorView?.state.doc.resolve(Math.min(maxSize, clickPosVal === olistPos ? clickPosVal + 1 : clickPosVal));
const listNode = listPos?.node();
- if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list && listNode) {
+ if (olistNode && olistNode.type === this.EditorView?.state.schema.nodes.ordered_list && listNode) {
if (!highlightOnly) {
if (selectOrderedList) {
- this._editorView.dispatch(this._editorView.state.tr.setSelection(new NodeSelection(selectOrderedList ? $olistPos! : listPos!)));
+ this.EditorView.dispatch(this.EditorView.state.tr.setSelection(new NodeSelection(selectOrderedList ? $olistPos! : listPos!)));
} else {
const nodePos = clickPosVal - (olistPos === clickPosVal ? 0 : 1);
- if (this._editorView.state.doc.nodeAt(nodePos)) {
- const tr = this._editorView.state.tr.setNodeMarkup(nodePos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility });
- this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, nodePos)));
+ if (this.EditorView.state.doc.nodeAt(nodePos)) {
+ const tr = this.EditorView.state.tr.setNodeMarkup(nodePos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility });
+ this.EditorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, nodePos)));
}
}
}
@@ -1687,26 +1778,40 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._undoTyping = undefined;
}
+ /**
+ * When a text box loses focus, it might be because a text button was clicked (eg, bold, italics) or color picker.
+ * In these cases, force focus back onto the text box.
+ * @param target
+ */
+ 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') {
+ return this.EditorView?.focus(); // keep focus on text box
+ }
+ }
+ };
+
@action
onBlur = (e: React.FocusEvent) => {
+ this.tryKeepingFocus(e.relatedTarget);
if (this.ProseRef?.children[0] !== e.nativeEvent.target) return;
if (!(this.EditorView?.state.selection instanceof NodeSelection) || this.EditorView.state.selection.node.type !== this.EditorView.state.schema.nodes.footnote) {
- const stordMarks = this._editorView?.state.storedMarks?.slice();
+ const stordMarks = this.EditorView?.state.storedMarks?.slice();
if (!(this.EditorView?.state.selection instanceof NodeSelection)) {
this.autoLink();
- if (this._editorView?.state.tr) {
+ if (this.EditorView?.state.tr) {
const tr = stordMarks?.reduce((tr2, m) => {
tr2.addStoredMark(m);
return tr2;
- }, this._editorView.state.tr);
- tr && this._editorView.dispatch(tr);
+ }, this.EditorView.state.tr);
+ tr && this.EditorView.dispatch(tr);
}
}
}
- if (RichTextMenu.Instance?.view === this._editorView && !this._props.rootSelected?.()) {
+ if (RichTextMenu.Instance?.view === this.EditorView && !(this._props.isContentActive() || this._props.rootSelected?.())) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
}
- FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
// this is the markdown for @<published name> document publishing to Doc.myPublishedDocs
const match = RTFCast(this.Document[this.fieldKey])?.Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
@@ -1751,7 +1856,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
switch (e.key) {
case 'Escape':
- this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
+ this.EditorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
(document.activeElement as HTMLElement).blur?.();
DocumentView.DeselectAll();
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
@@ -1777,7 +1882,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this.startUndoTypingBatch();
};
ondrop = (e: React.DragEvent) => {
- this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema));
+ this.EditorView?.dispatch(updateBullets(this.EditorView.state.tr, this.EditorView.state.schema));
e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash.
};
onScroll = (e: React.UIEvent) => {
@@ -1792,7 +1897,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
tryUpdateScrollHeight = () => {
const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
- if (children && !SnappingManager.IsDragging) {
+ if (this.EditorView && children && !SnappingManager.IsDragging) {
const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0;
const toNum = (val: string) => Number(val.replace('px', ''));
const toHgt = (node: Element): number => {
@@ -1818,14 +1923,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
};
fitContentsToBox = () => BoolCast(this.Document._freeform_fitContentsToBox);
sidebarContentScaling = () => (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
- sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
+ sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.sidebarKey) => {
if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
return this.addDocument(doc, sidebarKey);
};
- 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);
+ 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.dataDoc[this.sidebarKey + '_height'] = height;
};
sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
sidebarScreenToLocal = () =>
@@ -1855,7 +1960,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
@computed get sidebarHandle() {
TraceMobx();
- const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
+ const annotated = DocListCast(this.dataDoc[this.sidebarKey]).filter(d => d?.author).length;
const color = !annotated ? Colors.WHITE : Colors.BLACK;
const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : (this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.WidgetColor + (annotated ? ':annotated' : '')) as string);
@@ -1907,7 +2012,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
PanelWidth={this.sidebarWidth}
xPadding={0}
yPadding={0}
- viewField={this.SidebarKey}
+ viewField={this.sidebarKey}
isAnnotationOverlay={false}
select={emptyFunction}
isAnyChildContentActive={returnFalse}
@@ -1922,14 +2027,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
fitContentsToBox={this.fitContentsToBox}
noSidebar
treeViewHideTitle
- fieldKey={this.layoutDoc[this.SidebarKey + '_type_collection'] === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
+ fieldKey={this.layoutDoc[this.sidebarKey + '_type_collection'] === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
/>
</div>
);
};
return (
<div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
- {renderComponent(StrCast(this.layoutDoc[this.SidebarKey + '_type_collection']))}
+ {renderComponent(StrCast(this.layoutDoc[this.sidebarKey + '_type_collection']))}
</div>
);
}
@@ -1971,6 +2076,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
</Tooltip>
);
}
+
get fieldKey() {
return this._fieldKey;
}
@@ -1999,19 +2105,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
e.stopPropagation();
}
};
- @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
+
render() {
TraceMobx();
const scale = this._props.NativeDimScaling?.() || 1;
const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
- const paddingX = Math.max(this._props.xPadding ?? 0, NumCast(this.layoutDoc._xMargin));
- const paddingY = Math.max(this._props.yPadding ?? 0, NumCast(this.layoutDoc._yMargin));
+
+ 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 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 styleFromLayout?.height === '0px' ? null : (
+ return this.isLabel ? (
+ <LabelBox {...this._props} />
+ ) : styleFromLayout?.height === '0px' ? null : (
<div
className="formattedTextBox"
ref={r => {
@@ -2034,6 +2143,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
fontSize: this.fontSize,
fontFamily: this.fontFamily,
fontWeight: this.fontWeight,
+ fontStyle: this.fontStyle,
+ textDecoration: this.fontDecoration,
...styleFromLayout,
}}>
<div
@@ -2065,7 +2176,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
onScroll={this.onScroll}
onDrop={this.ondrop}>
<div
- className={`formattedTextBox-inner${rounded} ${this.layoutDoc._layout_centered ? 'centered' : ''} ${this.layoutDoc.hCentering}`}
+ 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}`}
ref={this.createDropTarget}
style={{
padding: StrCast(this.layoutDoc._textBoxPadding),
@@ -2074,13 +2185,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY}px`),
paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY}px`),
color: StrCast(this.layoutDoc.text_fontColor),
- fontWeight: `${this.layoutDoc.contentBold ? 'bold' : ''}`,
- textTransform: `${this.layoutDoc.textTransform}` as Property.TextTransform,
+ fontWeight: this.layoutDoc.contentBold ? 'bold' : '',
+ textTransform: StrCast(this.dataDoc[this.fieldKey + '_transform']) as Property.TextTransform,
}}
/>
</div>
{this.noSidebar || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
- {this.noSidebar || this.Document._layout_noSidebar || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
+ {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}
</div>
@@ -2099,7 +2210,6 @@ Docs.Prototypes.TemplateMap.set(DocumentType.RTF, {
_layout_nativeDimEditable: true,
_layout_reflowVertical: true,
_layout_reflowHorizontal: true,
- _layout_noSidebar: true,
defaultDoubleClick: 'ignore',
systemIcon: 'BsFileEarmarkTextFill',
},