From b6229b0a6141afbfd0e78e3ec870218187864def Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 19 Apr 2024 11:02:05 -0400 Subject: fixed text search highlighting. fixed first typed characfter of note to have marks. --- src/client/apis/gpt/GPT.ts | 8 +- src/client/util/CurrentUserUtils.ts | 2 +- src/client/util/DropConverter.ts | 77 ++--- src/client/views/nodes/ComparisonBox.tsx | 28 +- src/client/views/nodes/KeyValueBox.tsx | 61 ++-- .../nodes/formattedText/FormattedTextBox.scss | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 346 +++++++++++---------- .../formattedText/FormattedTextBoxComment.tsx | 2 +- src/client/views/nodes/formattedText/marks_rts.ts | 6 +- src/client/views/pdf/AnchorMenu.tsx | 6 +- 10 files changed, 287 insertions(+), 251 deletions(-) (limited to 'src/client') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 63563cb79..e3e87e017 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -27,9 +27,9 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { */ let lastCall = ''; let lastResp = ''; -const gptAPICall = async (inputText: string, callType: GPTCallType) => { - if (callType === GPTCallType.SUMMARY) inputText += '.'; - const opts: GPTCallOpts = callTypeMap[callType]; +const gptAPICall = async (inputTextIn: string, callType: GPTCallType) => { + const inputText = callType === GPTCallType.SUMMARY ? inputTextIn + '.' : inputTextIn; + const opts = callTypeMap[callType]; if (lastCall === inputText) return lastResp; try { const configuration: ClientOptions = { @@ -69,8 +69,8 @@ const gptImageCall = async (prompt: string, n?: number) => { // return response.data.data[0].url; } catch (err) { console.error(err); - return; } + return undefined; }; export { gptAPICall, gptImageCall, GPTCallType }; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 7811f8605..535f7cf9d 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1057,4 +1057,4 @@ ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.import // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; }); // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function getSharingDoc() { return Doc.SharingDoc() }); \ No newline at end of file +ScriptingGlobals.add(function getSharingDoc() { return Doc.SharingDoc() }); diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index ed5749d06..0314af06b 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -1,10 +1,9 @@ -import { Doc, DocListCast, Opt } from '../../fields/Doc'; +import { Doc, DocListCast, StrListCast } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { ObjectField } from '../../fields/ObjectField'; import { RichTextField } from '../../fields/RichTextField'; -import { listSpec } from '../../fields/Schema'; import { ComputedField, ScriptField } from '../../fields/ScriptField'; -import { Cast, StrCast } from '../../fields/Types'; +import { StrCast } from '../../fields/Types'; import { ImageField } from '../../fields/URLField'; import { Docs } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; @@ -12,21 +11,6 @@ import { ButtonType, FontIconBox } from '../views/nodes/FontIconBox/FontIconBox' import { DragManager } from './DragManager'; import { ScriptingGlobals } from './ScriptingGlobals'; -/** - * Converts a Doc to a render template that can be applied to other Docs to customize how they render while - * still using the other Doc as the backing data store (ie, dataDoc). During rendering, if a layout Doc is provided - * with 'isTemplateDoc' set, then the layout Doc is treated as a template for the rendered Doc. The template Doc is - * "expanded" to create an template instance for the rendered Doc. - * - * - * @param doc the doc to convert to a template - * @returns 'doc' - */ -export function MakeTemplate(doc: Doc) { - doc.isTemplateDoc = makeTemplate(doc, true); - return doc; -} - /** * * Recursively converts 'doc' into a template that can be used to render other documents. @@ -63,26 +47,22 @@ function makeTemplate(doc: Doc, first: boolean = true): boolean { } return isTemplate; } -export function convertDropDataToButtons(data: DragManager.DocumentDragData) { - data?.draggedDocuments.map((doc, i) => { - let dbox = doc; - // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant - if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes(FontIconBox.name)) { - if (data.dropPropertiesToRemove || dbox.dropPropertiesToRemove) { - //dbox = Doc.MakeEmbedding(doc); // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon - dbox = Doc.MakeEmbedding(dbox); - const dragProps = Cast(dbox.dropPropertiesToRemove, listSpec('string'), []); - const remProps = (data.dropPropertiesToRemove || []).concat(Array.from(dragProps)); - remProps.map(prop => (dbox[prop] = undefined)); - } - } else if (!doc.onDragStart && !doc.isButtonBar) { - dbox = makeUserTemplateButton(doc); - } else if (doc.isButtonBar) { - dbox.ignoreClick = true; - } - data.droppedDocuments[i] = dbox; - }); + +/** + * Converts a Doc to a render template that can be applied to other Docs to customize how they render while + * still using the other Doc as the backing data store (ie, dataDoc). During rendering, if a layout Doc is provided + * with 'isTemplateDoc' set, then the layout Doc is treated as a template for the rendered Doc. The template Doc is + * "expanded" to create an template instance for the rendered Doc. + * + * + * @param doc the doc to convert to a template + * @returns 'doc' + */ +export function MakeTemplate(doc: Doc) { + doc.isTemplateDoc = makeTemplate(doc, true); + return doc; } + export function makeUserTemplateButton(doc: Doc) { const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; if (layoutDoc.type !== DocumentType.FONTICON) { @@ -106,7 +86,30 @@ export function makeUserTemplateButton(doc: Doc) { dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory)'); return dbox; } +export function convertDropDataToButtons(data: DragManager.DocumentDragData) { + data?.draggedDocuments.forEach((doc, i) => { + let dbox = doc; + // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant + if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes(FontIconBox.name)) { + if (data.dropPropertiesToRemove || dbox.dropPropertiesToRemove) { + // dbox = Doc.MakeEmbedding(doc); // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon + dbox = Doc.MakeEmbedding(dbox); + const dragProps = StrListCast(dbox.dropPropertiesToRemove); + const remProps = (data.dropPropertiesToRemove || []).concat(Array.from(dragProps)); + remProps.forEach(prop => { + dbox[prop] = undefined; + }); + } + } else if (!doc.onDragStart && !doc.isButtonBar) { + dbox = makeUserTemplateButton(doc); + } else if (doc.isButtonBar) { + dbox.ignoreClick = true; + } + data.droppedDocuments[i] = dbox; + }); +} ScriptingGlobals.add( + // eslint-disable-next-line prefer-arrow-callback function convertToButtons(dragData: any) { convertDropDataToButtons(dragData as DragManager.DocumentDragData); }, diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 99f9c03bf..708536de0 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -52,13 +52,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() @undoBatch private internalDrop = (e: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => { if (dropEvent.complete.docDragData) { - const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments; - const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocs, this.Document, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey)); - Doc.SetContainer(droppedDocs.lastElement(), this.dataDoc); + const { droppedDocuments } = dropEvent.complete.docDragData; + const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocuments, this.Document, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey)); + Doc.SetContainer(droppedDocuments.lastElement(), this.dataDoc); !added && e.preventDefault(); e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place return added; } + return undefined; }; private registerSliding = (e: React.PointerEvent, targetWidth: number) => { @@ -83,7 +84,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() // on click, animate slider movement to the targetWidth this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth(); setTimeout( - action(() => (this._animating = '')), + action(() => { + this._animating = ''; + }), 200 ); }) @@ -109,7 +112,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() }); if (anchor) { if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; - /* addAsAnnotation &&*/ this.addDocument(anchor); + /* addAsAnnotation && */ this.addDocument(anchor); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.Document); return anchor; } @@ -148,7 +151,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() return true; }, emptyFunction, - e => this.clearDoc(which) + () => this.clearDoc(which) ); }; docStyleProvider = (doc: Opt, props: Opt, property: string): any => { @@ -213,15 +216,16 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() const displayDoc = (whichSlot: string) => { const whichDoc = DocCast(this.dataDoc[whichSlot]); const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc); - const layoutTemplateString = targetDoc ? '' : this.testForTextFields(whichSlot); - return targetDoc || layoutTemplateString ? ( + const layoutString = targetDoc ? '' : this.testForTextFields(whichSlot); + return targetDoc || layoutString ? ( <> () hideLinkButton pointerEvents={this._isAnyChildContentActive ? undefined : returnNone} /> - {layoutTemplateString ? null : clearButton(whichSlot)} + {layoutString ? null : clearButton(whichSlot)} // placeholder image if doc is missing ) : (
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 74773b244..b8296ce51 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -1,3 +1,4 @@ +/* eslint-disable jsx-a11y/control-has-associated-label */ import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -79,7 +80,8 @@ export class KeyValueBox extends ObservableReactComponent { * @param value * @returns */ - public static CompileKVPScript(rawvalue: string): KVPScript | undefined { + public static CompileKVPScript(rawvalueIn: string): KVPScript | undefined { + let rawvalue = rawvalueIn; const onDelegate = rawvalue.startsWith('='); rawvalue = onDelegate ? rawvalue.substring(1) : rawvalue; const type: 'computed' | 'script' | false = rawvalue.startsWith(':=') ? 'computed' : rawvalue.startsWith('$=') ? 'script' : false; @@ -87,7 +89,7 @@ export class KeyValueBox extends ObservableReactComponent { rawvalue = rawvalue.replace(/.*\(\((.*)\)\)/, 'dashCallChat(_setCacheResult_, this, `$1`)'); const value = ["'", '"', '`'].includes(rawvalue.length ? rawvalue[0] : '') || !isNaN(rawvalue as any) ? rawvalue : '`' + rawvalue + '`'; - var script = ScriptField.CompileScript(rawvalue, {}, true, undefined, DocumentIconContainer.getTransformer()); + let script = ScriptField.CompileScript(rawvalue, {}, true, undefined, DocumentIconContainer.getTransformer()); if (!script.compiled) { script = ScriptField.CompileScript(value, {}, true, undefined, DocumentIconContainer.getTransformer()); } @@ -153,20 +155,20 @@ export class KeyValueBox extends ObservableReactComponent { const ids: { [key: string]: string } = {}; const protos = Doc.GetAllPrototypes(doc); - for (const proto of protos) { + protos.forEach(proto => { Object.keys(proto).forEach(key => { if (!(key in ids) && realDoc[key] !== ComputedField.undefined) { ids[key] = key; } }); - } + }); const rows: JSX.Element[] = []; let i = 0; const self = this; const keys = Object.keys(ids).slice(); - //for (const key of [...keys.filter(id => id !== 'layout' && !id.includes('_')).sort(), ...keys.filter(id => id === 'layout' || id.includes('_')).sort()]) { - for (const key of keys.sort((a: string, b: string) => { + // for (const key of [...keys.filter(id => id !== 'layout' && !id.includes('_')).sort(), ...keys.filter(id => id === 'layout' || id.includes('_')).sort()]) { + const sortedKeys = keys.sort((a: string, b: string) => { const a_ = a.split('_')[0]; const b_ = b.split('_')[0]; if (a_ < b_) return -1; @@ -174,7 +176,8 @@ export class KeyValueBox extends ObservableReactComponent { if (a === a_) return -1; if (b === b_) return 1; return a === b ? 0 : a < b ? -1 : 1; - })) { + }); + sortedKeys.forEach(key => { rows.push( { keyName={key} /> ); - } + }); return rows; } @computed get newKeyValue() { @@ -229,7 +232,7 @@ export class KeyValueBox extends ObservableReactComponent { this._splitPercentage = Math.max(0, 100 - Math.round(((e.clientX - nativeWidth.left) / nativeWidth.width) * 100)); }; @action - onDividerUp = (e: PointerEvent): void => { + onDividerUp = (): void => { document.removeEventListener('pointermove', this.onDividerMove); document.removeEventListener('pointerup', this.onDividerUp); }; @@ -244,11 +247,11 @@ export class KeyValueBox extends ObservableReactComponent { const rows = this.rows.filter(row => row.isChecked); if (rows.length > 1) { const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this._props.Document).title}`, _chromeHidden: true }); - for (const row of rows) { + rows.forEach(row => { const field = this.createFieldView(DocCast(this._props.Document), row); field && Doc.AddDocToList(parent, 'data', field); row.uncheck(); - } + }); return parent; } return rows.length ? this.createFieldView(DocCast(this._props.Document), rows.lastElement()) : undefined; @@ -256,22 +259,23 @@ export class KeyValueBox extends ObservableReactComponent { createFieldView = (templateDoc: Doc, row: KeyValuePair) => { const metaKey = row._props.keyName; - const fieldTemplate = Doc.IsDelegateField(templateDoc, metaKey) ? Doc.MakeDelegate(templateDoc) : Doc.MakeEmbedding(templateDoc); - fieldTemplate.title = metaKey; - fieldTemplate.layout_fitWidth = true; - fieldTemplate._xMargin = 10; - fieldTemplate._yMargin = 10; - fieldTemplate._width = 100; - fieldTemplate._height = 40; - fieldTemplate.layout = this.inferType(templateDoc[metaKey], metaKey); - return fieldTemplate; + const fieldTempDoc = Doc.IsDelegateField(templateDoc, metaKey) ? Doc.MakeDelegate(templateDoc) : Doc.MakeEmbedding(templateDoc); + fieldTempDoc.title = metaKey; + fieldTempDoc.layout_fitWidth = true; + fieldTempDoc._xMargin = 10; + fieldTempDoc._yMargin = 10; + fieldTempDoc._width = 100; + fieldTempDoc._height = 40; + fieldTempDoc.layout = this.inferType(templateDoc[metaKey], metaKey); + return fieldTempDoc; }; inferType = (data: FieldResult, metaKey: string) => { const options = { _width: 300, _height: 300, title: metaKey }; if (data instanceof RichTextField || typeof data === 'string' || typeof data === 'number') { return FormattedTextBox.LayoutString(metaKey); - } else if (data instanceof List) { + } + if (data instanceof List) { if (data.length === 0) { return Docs.Create.StackingDocument([], options); } @@ -280,21 +284,18 @@ export class KeyValueBox extends ObservableReactComponent { return Docs.Create.StackingDocument([], options); } switch (first.data.constructor) { - case RichTextField: - return Docs.Create.TreeDocument([], options); - case ImageField: - return Docs.Create.MasonryDocument([], options); - default: - console.log(`Template for ${first.data.constructor} not supported!`); - return undefined; - } + case RichTextField: return Docs.Create.TreeDocument([], options); + case ImageField: return Docs.Create.MasonryDocument([], options); + default: console.log(`Template for ${first.data.constructor} not supported!`); + return undefined; + } // prettier-ignore } else if (data instanceof ImageField) { return ImageBox.LayoutString(metaKey); } return new Doc(); }; - specificContextMenu = (e: React.MouseEvent): void => { + specificContextMenu = (): void => { const cm = ContextMenu.Instance; const open = cm.findByDescription('Change Perspective...'); const openItems: ContextMenuProps[] = open && 'subitems' in open ? open.subitems : []; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 38dd2e847..5b2b558fc 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -350,7 +350,7 @@ footnote::before { span { font-family: inherit; background-color: inherit; - display: contents; // fixes problem where extra space is added around
    lists when inside a prosemirror span + display: inline; // needs to be inline for search highlighting to appear // contents; // fixes problem where extra space is added around
      lists when inside a prosemirror span } blockquote { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 31252e0ab..c7543560d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from ' import { EditorView } from 'prosemirror-view'; import * as React from 'react'; import { BsMarkdownFill } from 'react-icons/bs'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, smoothScroll } from '../../../../ClientUtils'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, StopEvent } from '../../../../ClientUtils'; import { DateField } from '../../../../fields/DateField'; import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols'; @@ -82,6 +82,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent EditorState.create(FormattedTextBox.Instance.config); + // eslint-disable-next-line no-use-before-define public static Instance: FormattedTextBox; public static LiveTextUndo: UndoManager.Batch | undefined; static _globalHighlightsCache: string = ''; @@ -100,7 +101,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + let allFoundLinkAnchors: any[] = []; + state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any /* , pos: number, parent: any */) => { const foundLinkAnchors = findLinkMark(node.marks)?.attrs.allAnchors.filter((a: any) => a.anchorId === a1[Id] || a.anchorId === a2[Id]) || []; allFoundLinkAnchors = foundLinkAnchors.length ? foundLinkAnchors : allFoundLinkAnchors; return true; @@ -249,7 +245,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const rootDoc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document); + 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 }); this.addDocument(anchor); @@ -264,11 +260,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { AnchorMenu.Instance.Status = 'marquee'; - AnchorMenu.Instance.OnClick = (e: PointerEvent) => { + 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 }; - AnchorMenu.Instance.OnAudio = (e: PointerEvent) => { + AnchorMenu.Instance.OnAudio = () => { !this.layoutDoc.layout_showSidebar && this.toggleSidebar(); const anchor = this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true, true); @@ -279,7 +275,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (stopFunc = stop)); + DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore + const reactionDisposer = reaction( () => target.mediaState, dictation => { @@ -324,7 +321,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { @@ -339,7 +338,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (this._editorView && (this._editorView as any).docView) { + if (this._editorView) { const state = this._editorView.state.apply(tx); this._editorView.updateState(state); this.tryUpdateDoc(false); @@ -347,13 +346,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (this._editorView && (this._editorView as any).docView) { + 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()); const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box - const templateData = this.Document !== this.layoutDoc ? prevData : undefined; // the default text stored in a layout template const protoData = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const layoutData = this.layoutDoc.isTemplateDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text inherited from a prototype const effectiveAcl = GetEffectiveAcl(dataDoc); @@ -362,7 +360,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any /* , pos: number, parent: any */) => { if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) { accumTags.push(node.attrs.fieldKey); } @@ -417,37 +415,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { let linkTime; let linkAnchor; - let link; - LinkManager.Links(this.dataDoc).forEach((l, i) => { - const anchor = (l.link_anchor_1 as Doc).annotationOn ? (l.link_anchor_1 as Doc) : (l.link_anchor_2 as Doc).annotationOn ? (l.link_anchor_2 as Doc) : undefined; + LinkManager.Links(this.dataDoc).forEach(l => { + const anchor = DocCast(l.link_anchor_1)?.annotationOn ? DocCast(l.link_anchor_1) : DocCast(l.link_anchor_2)?.annotationOn ? DocCast(l.link_anchor_2) : undefined; if (anchor && (anchor.annotationOn as Doc).mediaState === mediaState.Recording) { linkTime = NumCast(anchor._timecodeToShow /* audioStart */); linkAnchor = anchor; - link = l; } }); if (this._editorView && linkTime) { - const state = this._editorView.state; - const now = Date.now(); - let mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified: Math.floor(now / 1000) }); - if (!this._break && state.selection.to !== state.selection.from) { - for (let i = state.selection.from; i <= state.selection.to; i++) { - const pos = state.doc.resolve(i); - const um = Array.from(pos.marks()).find(m => m.type === schema.marks.user_mark); - if (um) { - mark = um; - break; - } - } - } - - const path = (this._editorView.state.selection.$from as any).path; - if (linkAnchor && path[path.length - 3].type !== this._editorView.state.schema.nodes.code_block) { + const { state } = this._editorView; + const { path } = state.selection.$from as any; + if (linkAnchor && path[path.length - 3].type !== state.schema.nodes.code_block) { const time = linkTime + Date.now() / 1000 - this._recordingStart / 1000; this._break = false; - const from = state.selection.from; - const value = this._editorView.state.schema.nodes.audiotag.create({ timeCode: time, audioId: linkAnchor[Id] }); - const replaced = this._editorView.state.tr.insert(from - 1, value); + 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)))); } } @@ -464,14 +447,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent term.title).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks))); - tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); + 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); } oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(LinkManager.Instance.deleteLink); @@ -507,11 +490,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent) => { + hyperlinkTerm = (trIn: any, target: Doc, newAutoLinks: Set) => { + let tr = trIn; const editorView = this._editorView; - if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) { + if (editorView && !Doc.AreProtosEqual(target, this.Document)) { const autoLinkTerm = Field.toString(target.title as FieldType).replace(/^@/, ''); - var alink: Doc | undefined; + let alink: Doc | undefined; this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => { if ( !sel.$anchor.pos || @@ -523,7 +507,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number /* , parent: any */) => { if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { alink = alink ?? @@ -554,12 +538,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { - const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); - const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); - const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); - const length = res[0].length; - let tr = this._editorView.state.tr; + const { _editorView } = this; + if (_editorView && terms.some(t => t)) { + const { state } = _editorView; + let { tr } = state; + const mark = state.schema.mark(state.schema.marks.search_highlight); + const activeMark = state.schema.mark(state.schema.marks.search_highlight, { selected: true }); + const res = terms.filter(t => t).map(term => this.findInNode(_editorView, state.doc, term)); + const { length } = res[0]; const flattened: TextSelection[] = []; res.map(r => r.map(h => flattened.push(h))); this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; @@ -574,18 +560,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark))); - flattened[lastSel] && this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView()); + flattened.forEach((h: TextSelection, ind: number) => { + tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark); + }); + flattened[lastSel] && _editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView()); } }; unhighlightSearchTerms = () => { - if (window.screen.width < 600) null; - else if (this._editorView && (this._editorView as any).docView) { - const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); - const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); - const end = this._editorView.state.doc.nodeSize - 2; - this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); + 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)); + } } }; adoptAnnotation = (start: number, end: number, mark: Mark) => { @@ -634,7 +624,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent -1) { const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + 1), pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + find.length + 1)); ret.push(sel); @@ -713,14 +702,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (ret = ret.concat(this.findInNode(pm, child, find)))); + node.content.forEach(child => { + ret = ret.concat(this.findInNode(pm, child, find)); + }); } return ret; } updateHighlights = (highlights: string[]) => { if (Array.from(highlights).join('') === FormattedTextBox._globalHighlightsCache) return; - setTimeout(() => (FormattedTextBox._globalHighlightsCache = Array.from(highlights).join(''))); + setTimeout(() => { + FormattedTextBox._globalHighlightsCache = Array.from(highlights).join(''); + }); clearStyleSheetRules(FormattedTextBox._userStyleSheet); if (!highlights.includes('Audio Tags')) { addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, ''); @@ -757,12 +750,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); } + // eslint-disable-next-line operator-assignment 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 ? true : false; + return !!(this._showSidebar || this.layoutDoc._layout_showSidebar); } @action @@ -820,7 +814,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this._props.pinToPres(anchor, {}); @undoBatch - makeTargetToggle = (anchor: Doc) => (anchor.followLinkToggle = !anchor.followLinkToggle); + makeTargetToggle = (anchor: Doc) => { + anchor.followLinkToggle = !anchor.followLinkToggle; + }; @undoBatch showTargetTrail = (anchor: Doc) => { @@ -836,11 +832,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const cm = ContextMenu.Instance; - const editor = this._editorView!; - const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); let target = e.target as any; // hrefs are stored on the database of the node that wraps the hyerlink while (target && !target.dataset?.targethrefs) target = target.parentElement; - if (target && !(e.nativeEvent as any).dash) { + const editor = this._editorView; + if (editor && target && !(e.nativeEvent as any).dash) { const hrefs = (target.dataset?.targethrefs as string) ?.trim() .split(' ') @@ -850,8 +845,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const sel = editor.state.selection; - editor.dispatch(editor.state.tr.removeMark(sel.from, sel.to, editor.state.schema.marks.linkAnchor)); + const { selection } = editor.state; + editor.dispatch(editor.state.tr.removeMark(selection.from, selection.to, editor.state.schema.marks.linkAnchor)); }); e.persist(); anchorDoc && @@ -887,7 +882,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { 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); + setTimeout(() => { + this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50; + }, 50); }), icon: 'eye', }); @@ -926,19 +923,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.layoutDoc._layout_noSidebar = !this.layoutDoc._layout_noSidebar), + event: () => { + this.layoutDoc._layout_noSidebar = !this.layoutDoc._layout_noSidebar; + }, icon: !this.Document._layout_noSidebar ? 'eye-slash' : 'eye', }); appearanceItems.push({ description: (this.Document._layout_enableAltContentUI ? 'Hide' : 'Show') + ' Alt Content UI', - event: () => (this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI), + event: () => { + this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI; + }, icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye', }); !Doc.noviceMode && appearanceItems.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' }); !Doc.noviceMode && appearanceItems.push({ description: 'Broadcast Message', - event: () => DocServer.GetRefField('rtfProto').then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text)), + event: () => + DocServer.GetRefField('rtfProto').then(proto => { + proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text); + }), icon: 'expand-arrows-alt', }); @@ -960,19 +964,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate']), icon: 'star' }); + optionItems.push({ + description: `Toggle auto update from template`, + event: () => { + this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate']; + }, + icon: 'star', + }); optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' }); optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' }); this._props.renderDepth && optionItems.push({ description: !this.Document._createDocOnCR ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns', - event: () => (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR), + event: () => { + this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR; + }, 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), + event: () => { + this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight; + }, icon: this.Document._layout_autoHeight ? 'lock' : 'unlock', }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); @@ -980,7 +994,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent }); !help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' }); - this._downX = this._downY = Number.NaN; }; animateRes = (resIndex: number, newText: string) => { @@ -995,7 +1008,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { try { - let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION); + const res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION); if (!res) { console.error('GPT call failed'); this.animateRes(0, 'Something went wrong.'); @@ -1020,8 +1033,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + tr.doc.nodesBetween(selection.from, selection.to, (node: any, pos: number /* , parent: any */) => { if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { const allAnchors = [{ href, title, anchorId: anchor[Id] }]; allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allAnchors ?? [])); @@ -1102,7 +1116,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this._sidebarRef?.current?.makeDocUnfiltered(doc)); } - return new Promise>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); + return new Promise>(res => { + DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)); + }); }; focus = (textAnchor: Doc, options: FocusViewOptions) => { const focusSpeed = options.zoomTime ?? 500; const textAnchorId = textAnchor[Id]; + let start = 0; const findAnchorFrag = (frag: Fragment, editor: EditorView) => { const nodes: Node[] = []; let hadStart = start !== 0; frag.forEach((node, index) => { + // eslint-disable-next-line no-use-before-define 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)) { nodes.push(examinedNode.node); @@ -1163,7 +1181,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent textAnchorId === item.href.replace(/.*\/doc\//, '')) ? { node, start: 0 } : undefined; }; - let start = 0; 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 editor = this._editorView; @@ -1179,13 +1196,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent= '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(() => (this._focusSpeed = undefined), this._focusSpeed); + setTimeout(() => { + this._focusSpeed = undefined; + }, this._focusSpeed); setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000)); return focusSpeed; - } else { - return this._props.focus(this.Document, options); } + return this._props.focus(this.Document, options); } + return undefined; }; // if the scroll height has changed and we're in layout_autoHeight mode, then we need to update the textHeight component of the doc. @@ -1205,7 +1224,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Doc.RecordingEvent, this.breakupDictation); this._disposers.layout_autoHeight = reaction( () => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize, css: this.Document[DocCss] }), - (autoHeight, fontSize) => setTimeout(() => autoHeight && this.tryUpdateScrollHeight()) + autoHeight => setTimeout(() => autoHeight && this.tryUpdateScrollHeight()) ); this._disposers.highlights = reaction( () => Array.from(FormattedTextBox._globalHighlights).slice(), @@ -1214,7 +1233,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this._props.PanelWidth(), - width => this.tryUpdateScrollHeight() + () => this.tryUpdateScrollHeight() ); this._disposers.scrollHeight = reaction( () => ({ scrollHeight: this.scrollHeight, layoutAutoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }), @@ -1348,7 +1367,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + handlePaste = (view: EditorView, event: Event /* , slice: Slice */): boolean => { const pdfAnchorId = (event as ClipboardEvent).clipboardData?.getData('dash/pdfAnchor'); return !!(pdfAnchorId && this.addPdfReference(pdfAnchorId)); }; @@ -1381,7 +1400,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent 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 } } if (selectOnLoad) { @@ -1526,8 +1555,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if ((e.nativeEvent as any).handledByInnerReactInstance) { - return; //e.stopPropagation(); - } else (e.nativeEvent as any).handledByInnerReactInstance = true; + return; // e.stopPropagation(); + } + (e.nativeEvent as any).handledByInnerReactInstance = true; if (this.Document.forceActive) e.stopPropagation(); this.tryUpdateScrollHeight(); // if a doc a fitWidth doc is being viewed in different embedContainer (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view. @@ -1553,9 +1583,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - document.removeEventListener('pointerup', this.onSelectEnd); - }; + onSelectEnd = (): void => document.removeEventListener('pointerup', this.onSelectEnd); onPointerUp = (e: React.PointerEvent): void => { const state = this.EditorView?.state; if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) { @@ -1607,7 +1632,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - //applyDevTools.applyDevTools(this._editorView); + // applyDevTools.applyDevTools(this._editorView); this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this._props, this.layoutDoc); e.stopPropagation(); }; @@ -1618,10 +1643,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent 4 || Math.abs(e.clientY - this._downY) > 4) { - this._forceDownNode = undefined; - return; - } if (!this._forceUncollapse || (this._editorView!.root as any).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 = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); @@ -1645,13 +1666,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent !this.ProseRef?.contains(document.activeElement) && this._props.onBlur?.()); }; onKeyDown = (e: React.KeyboardEvent) => { + const { _editorView } = this; + if (!_editorView) return; if ((e.altKey || e.ctrlKey) && e.key === 't') { - e.preventDefault(); - e.stopPropagation(); this._props.setTitleFocus?.(); + StopEvent(e); return; } - const state = this._editorView!.state; + const { state } = _editorView; if (!state.selection.empty && e.key === '%') { this._rules!.EnteringStyle = true; - e.preventDefault(); - e.stopPropagation(); + StopEvent(e); return; } if (state.selection.empty || !this._rules!.EnteringStyle) { this._rules!.EnteringStyle = false; } - let stopPropagation = true; - for (var i = state.selection.from; i <= state.selection.to; i++) { + for (let i = state.selection.from; i <= state.selection.to; i++) { const node = state.doc.resolve(i); if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== ClientUtils.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.Document))) { e.preventDefault(); @@ -1769,22 +1788,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent m.type === schema.marks.user_mark && m.attrs.modified === modified); + _editorView.dispatch(state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(mark ?? schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified }))); } break; } - if (stopPropagation) e.stopPropagation(); + e.stopPropagation(); this.startUndoTypingBatch(); }; ondrop = (e: React.DragEvent) => { @@ -1804,6 +1823,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0; const toNum = (val: string) => Number(val.replace('px', '')); const toHgt = (node: Element): number => { @@ -1815,7 +1835,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight); + const setScrollHeight = () => { + this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight; + }; if (this.Document === this.layoutDoc || this.layoutDoc.resolvedDataDoc) { setScrollHeight(); @@ -1833,7 +1855,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent 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); + setSidebarHeight = (height: number) => { + this.dataDoc[this.SidebarKey + '_height'] = height; + }; sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth(); sidebarScreenToLocal = () => this._props @@ -1851,10 +1875,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this._recordingDictation = !this._recordingDictation)) + action(() => { + this._recordingDictation = !this._recordingDictation; + }) ) }> - +
); } @@ -1883,6 +1909,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}> @@ -1963,7 +1991,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent
setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => this.cycleAlternateText())} + onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => this.cycleAlternateText())} style={{ display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none', background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray', @@ -2000,7 +2028,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (p ? p + ' ' + item.href : item.href), ''); const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), ''); - return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0]; + return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /* 'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0]; }, }, noAutoLinkAnchor: { @@ -60,7 +60,7 @@ export const marks: { [index: string]: MarkSpec } = { }, }, ], - toDOM(node: any) { + toDOM() { return ['span', { 'data-noAutoLink': 'true' }, 0]; }, }, diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 9c4080154..02ff661e5 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -5,7 +5,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { ColorResult } from 'react-color'; import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; -import { unimplementedFunction } from '../../../Utils'; +import { emptyFunction, unimplementedFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -50,8 +50,8 @@ export class AnchorMenu extends AntimodeMenu { public OnAudio: (e: PointerEvent) => void = unimplementedFunction; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; - public Highlight: (color: string) => Opt = (color: string) => undefined; - public GetAnchor: (savedAnnotations: Opt>, addAsAnnotation: boolean) => Opt = (savedAnnotations: Opt>, addAsAnnotation: boolean) => undefined; + public Highlight: (color: string) => Opt = (/* color: string */) => undefined; + public GetAnchor: (savedAnnotations: Opt>, addAsAnnotation: boolean) => Opt = emptyFunction; public Delete: () => void = unimplementedFunction; public PinToPres: () => void = unimplementedFunction; public MakeTargetToggle: () => void = unimplementedFunction; -- cgit v1.2.3-70-g09d2