diff options
Diffstat (limited to 'src/client/views/nodes/formattedText')
8 files changed, 310 insertions, 384 deletions
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index c05a30d1a..48f4c2afd 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -2,8 +2,9 @@ import { action, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { NodeSelection } from 'prosemirror-state'; import * as ReactDOM from 'react-dom/client'; -import { Doc, HeightSym, WidthSym } from '../../../../fields/Doc'; -import { Cast, NumCast, StrCast } from '../../../../fields/Types'; +import { Doc } from '../../../../fields/Doc'; +import { Height, Width } from '../../../../fields/DocSymbols'; +import { NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnFalse, Utils } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; @@ -41,18 +42,33 @@ export class DashDocView { this.root = ReactDOM.createRoot(this.dom); this.root.render( - <DashDocViewInternal docId={node.attrs.docId} alias={node.attrs.alias} width={node.attrs.width} height={node.attrs.height} hidden={node.attrs.hidden} fieldKey={node.attrs.fieldKey} tbox={tbox} view={view} node={node} getPos={getPos} /> + <DashDocViewInternal + docId={node.attrs.docId} + embedding={node.attrs.embedding} + width={node.attrs.width} + height={node.attrs.height} + hidden={node.attrs.hidden} + fieldKey={node.attrs.fieldKey} + tbox={tbox} + view={view} + node={node} + getPos={getPos} + /> ); } destroy() { - setTimeout(this.root.unmount); + setTimeout(() => { + try { + this.root.unmount(); + } catch {} + }); } selectNode() {} } interface IDashDocViewInternal { docId: string; - alias: string; + embedding: string; tbox: FormattedTextBox; width: string; height: string; @@ -69,25 +85,18 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { _textBox: FormattedTextBox; @observable _dashDoc: Doc | undefined; @observable _finalLayout: any; - @observable _resolvedDataDoc: any; @observable _width: number = 0; @observable _height: number = 0; updateDoc = action((dashDoc: Doc) => { this._dashDoc = dashDoc; - this._finalLayout = this.props.docId ? dashDoc : Doc.expandTemplateLayout(Doc.Layout(dashDoc), dashDoc); + this._finalLayout = dashDoc; - if (this._finalLayout) { - if (!Doc.AreProtosEqual(this._finalLayout, dashDoc)) { - this._finalLayout.rootDocument = dashDoc.aliasOf; - } - this._resolvedDataDoc = Cast(this._finalLayout.resolvedDataDoc, Doc, null); - } if (this.props.width !== (this._dashDoc?._width ?? '') + 'px' || this.props.height !== (this._dashDoc?._height ?? '') + 'px') { try { this._width = NumCast(this._dashDoc?._width); this._height = NumCast(this._dashDoc?._height); - // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made + // bcz: an exception will be thrown if two embeddings are open at the same time when a doc view comment is made this.props.view.dispatch( this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { ...this.props.node.attrs, @@ -105,15 +114,15 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { super(props); this._textBox = this.props.tbox; - DocServer.GetRefField(this.props.docId + this.props.alias).then(async dashDoc => { + DocServer.GetRefField(this.props.docId + this.props.embedding).then(async dashDoc => { if (!(dashDoc instanceof Doc)) { - this.props.alias && + this.props.embedding && DocServer.GetRefField(this.props.docId).then(async dashDocBase => { if (dashDocBase instanceof Doc) { - const aliasedDoc = Doc.MakeAlias(dashDocBase, this.props.docId + this.props.alias); - aliasedDoc.layoutKey = 'layout'; - this.props.fieldKey && DocUtils.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, this.props.fieldKey, undefined); - this.updateDoc(aliasedDoc); + const embedding = Doc.MakeEmbedding(dashDocBase, this.props.docId + this.props.embedding); + embedding.layout_fieldKey = 'layout'; + this.props.fieldKey && DocUtils.makeCustomViewClicked(embedding, Docs.Create.StackingDocument, this.props.fieldKey, undefined); + this.updateDoc(embedding); } }); } else { @@ -191,7 +200,6 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { onWheel={e => e.preventDefault()}> <DocumentView Document={this._finalLayout} - DataDoc={this._resolvedDataDoc} addDocument={returnFalse} rootSelected={returnFalse} //{this._textBox.props.isSelected} removeDocument={this.removeDoc} @@ -203,14 +211,14 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { addDocTab={this._textBox.props.addDocTab} pinToPres={returnFalse} renderDepth={this._textBox.props.renderDepth + 1} - PanelWidth={this._finalLayout[WidthSym]} - PanelHeight={this._finalLayout[HeightSym]} + PanelWidth={this._finalLayout[Width]} + PanelHeight={this._finalLayout[Height]} focus={this.outerFocus} whenChildContentsActiveChanged={returnFalse} bringToFront={emptyFunction} dontRegisterView={false} - docFilters={this.props.tbox?.props.docFilters} - docRangeFilters={this.props.tbox?.props.docRangeFilters} + childFilters={this.props.tbox?.props.childFilters} + childFiltersByRanges={this.props.tbox?.props.childFiltersByRanges} searchFilterDocs={this.props.tbox?.props.searchFilterDocs} /> </div> diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index bf6fa2ec6..b4fb7a44e 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -2,18 +2,17 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable } from 'mobx'; import { observer } from 'mobx-react'; -import { NodeSelection } from 'prosemirror-state'; import * as ReactDOM from 'react-dom/client'; -import { DataSym, Doc, DocListCast, Field } from '../../../../fields/Doc'; +import { Doc } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; -import { ComputedField } from '../../../../fields/ScriptField'; import { Cast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils'; +import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; +import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell'; import { OpenWhere } from '../DocumentView'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; @@ -70,10 +69,10 @@ export class DashFieldView { } catch {} }); } - deselectNode() { + @action deselectNode() { this.dom.classList.remove('ProseMirror-selectednode'); } - selectNode() { + @action selectNode() { this.dom.classList.add('ProseMirror-selectednode'); } } @@ -98,6 +97,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna _fieldKey: string; _fieldStringRef = React.createRef<HTMLSpanElement>(); @observable _dashDoc: Doc | undefined; + @observable _expanded = false; constructor(props: IDashFieldViewInternal) { super(props); @@ -114,139 +114,46 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna this._reactionDisposer?.(); } - public static multiValueDelimeter = ';'; - public static fieldContent(textBoxDoc: Doc, dashDoc: Doc, fieldKey: string) { - const dashVal = dashDoc[fieldKey] ?? dashDoc[DataSym][fieldKey] ?? ''; - const fval = dashVal instanceof List ? dashVal.join(DashFieldViewInternal.multiValueDelimeter) : StrCast(dashVal).startsWith(':=') || dashVal === '' ? Doc.Layout(textBoxDoc)[fieldKey] : dashVal; - return { boolVal: Cast(fval, 'boolean', null), strVal: Field.toString(fval as Field) || '' }; - } - // set the display of the field's value (checkbox for booleans, span of text for strings) @computed get fieldValueContent() { - if (this._dashDoc) { - const { boolVal, strVal } = DashFieldViewInternal.fieldContent(this._textBoxDoc, this._dashDoc, this._fieldKey); - // field value is a boolean, so use a checkbox or similar widget to display it - if (boolVal === true || boolVal === false) { - return ( - <input - className="dashFieldView-fieldCheck" - type="checkbox" - checked={boolVal} - onChange={e => { - if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked; - Doc.SetInPlace(this._dashDoc!, this._fieldKey, e.target.checked, true); - }} - /> - ); - } // field value is a string, so display it as an editable span - else { - // bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't - // use React events. Essentially, React events occur after native events have been processed, so corresponding React events - // will never fire because Prosemirror has handled the native events. So we add listeners for native events here. - return ( - <span - className="dashFieldView-fieldSpan" - contentEditable={!this.props.unclickable()} - style={{ display: strVal.length < 2 ? 'inline-block' : undefined }} - suppressContentEditableWarning={true} - defaultValue={strVal} - ref={r => { - r?.addEventListener('keydown', e => this.fieldSpanKeyDown(e, r)); - r?.addEventListener('blur', e => r && this.updateText(r.textContent!, false)); - r?.addEventListener( - 'pointerdown', - action(e => { - // let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span> - // while (target && !target.dataset?.targethrefs) target = target.parentElement; - this.props.tbox.EditorView!.dispatch(this.props.tbox.EditorView!.state.tr.setSelection(new NodeSelection(this.props.tbox.EditorView!.state.doc.resolve(this.props.getPos())))); - // FormattedTextBoxComment.update(this.props.tbox, this.props.tbox.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc); - // e.stopPropagation(); - }) - ); - }}> - {strVal} - </span> - ); - } - } + return !this._dashDoc ? null : ( + <div onClick={action(e => (this._expanded = !this.props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: this.props.hideKey ? this.props.tbox.props.PanelWidth() - 20 : undefined }}> + <SchemaTableCell + Document={this._dashDoc} + col={0} + deselectCell={emptyFunction} + selectCell={emptyFunction} + maxWidth={this.props.hideKey ? undefined : () => 100} + columnWidth={this.props.hideKey ? () => this.props.tbox.props.PanelWidth() - 20 : returnZero} + selectedCell={() => [this._dashDoc!, 0]} + fieldKey={this._fieldKey} + rowHeight={returnZero} + isRowActive={() => this._expanded && this.props.editable} + padding={0} + getFinfo={emptyFunction} + setColumnValues={returnFalse} + allowCRs={true} + oneLine={!this._expanded} + finishEdit={action(() => (this._expanded = false))} + /> + </div> + ); } - // we need to handle all key events on the input span or else they will propagate to prosemirror. - @action - fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => { - if (e.key === 'c' && (e.ctrlKey || e.metaKey)) { - navigator.clipboard.writeText(window.getSelection()?.toString() || ''); - return; - } - if (e.key === 'Enter') { - // handle the enter key by "submitting" the current text to Dash's database. - this.updateText(span.textContent!, true); - e.preventDefault(); // prevent default to avoid a newline from being generated and wiping out this field view - } - if (e.key === 'a' && (e.ctrlKey || e.metaKey)) { - // handle ctrl-A to select all the text within the span - if (window.getSelection) { - const range = document.createRange(); - range.selectNodeContents(span); - window.getSelection()!.removeAllRanges(); - window.getSelection()!.addRange(range); - } - e.preventDefault(); //prevent default so that all the text in the prosemirror text box isn't selected - } - if (!this.props.editable) { - e.preventDefault(); - } - e.stopPropagation(); // we need to handle all events or else they will propagate to prosemirror. - }; - - @action - updateText = (nodeText: string, forceMatch: boolean) => { - if (nodeText) { - const newText = nodeText.startsWith(':=') || nodeText.startsWith('=:=') ? ':=-computed-' : nodeText; - // look for a document whose id === the fieldKey being displayed. If there's a match, then that document - // holds the different enumerated values for the field in the titles of its collected documents. - // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. - DocServer.GetRefField(this._fieldKey).then(options => { - let modText = ''; - options instanceof Doc && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); - if (modText) { - // elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText; - Doc.SetInPlace(this._dashDoc!, this._fieldKey, modText, true); - } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key - else if (nodeText.startsWith(':=')) { - this._dashDoc![DataSym][this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2)); - } else if (nodeText.startsWith('=:=')) { - Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3)); - } else { - if (Number(newText).toString() === newText) { - if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText); - Doc.SetInPlace(this._dashDoc!, this._fieldKey, Number(newText), true); - } else { - const splits = newText.split(DashFieldViewInternal.multiValueDelimeter); - if (!this._textBoxDoc[this._fieldKey]) { - const strVal = splits.length > 1 ? new List<string>(splits) : newText; - if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal; - Doc.SetInPlace(this._dashDoc!, this._fieldKey, strVal, true); - } - } - } - }); - } - }; - createPivotForField = (e: React.MouseEvent) => { let container = this.props.tbox.props.DocumentView?.().props.docViewPath().lastElement(); if (container) { - const alias = Doc.MakeAlias(container.props.Document); - alias._viewType = CollectionViewType.Time; - let list = Cast(alias._columnHeaders, listSpec(SchemaHeaderField)); + const embedding = Doc.MakeEmbedding(container.rootDoc); + embedding._type_collection = CollectionViewType.Time; + const colHdrKey = '_' + container.LayoutFieldKey + '_columnHeaders'; + let list = Cast(embedding[colHdrKey], listSpec(SchemaHeaderField)); if (!list) { - alias._columnHeaders = list = new List<SchemaHeaderField>(); + embedding[colHdrKey] = list = new List<SchemaHeaderField>(); } list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb')); list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb')); - alias._pivotField = this._fieldKey.startsWith('#') ? 'tags' : this._fieldKey; - this.props.tbox.props.addDocTab(alias, OpenWhere.addRight); + embedding._pivotField = this._fieldKey.startsWith('#') ? 'tags' : this._fieldKey; + this.props.tbox.props.addDocTab(embedding, OpenWhere.addRight); } }; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index b5a3c5d84..109b62e6f 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -41,7 +41,7 @@ audiotag:hover { flex-direction: row; transition: opacity 1s; width: 100%; - position: absolute; + position: relative; top: 0; left: 0; } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 555944438..f4cecb1dc 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -12,7 +12,7 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model'; import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state'; import { EditorView } from 'prosemirror-view'; import { DateField } from '../../../../fields/DateField'; -import { AclAdmin, AclAugment, AclEdit, CssSym, Doc, DocListCast, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc'; +import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, CssSym, Doc, DocListCast, Field, ForceServerWrite, HeightSym, Opt, StrListCast, UpdatingFromServer, WidthSym } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; @@ -74,8 +74,11 @@ const translateGoogleApi = require('translate-google-api'); export const GoogleRef = 'googleDocId'; type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void; +export interface FormattedTextBoxProps { + allowScroll?: boolean; +} @observer -export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { +export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps & FormattedTextBoxProps>() { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } @@ -115,38 +118,38 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps return this._editorView; } public get SidebarKey() { - return this.fieldKey + '-sidebar'; + return this.fieldKey + '_sidebar'; } @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } @computed get noSidebar() { - return this.props.docViewPath().lastElement()?.props.hideDecorationTitle || this.props.noSidebar || this.Document._noSidebar; + return this.props.docViewPath().lastElement()?.props.hideDecorationTitle || this.props.noSidebar || this.Document._layout_noSidebar; } - @computed get sidebarWidthPercent() { - return this._showSidebar ? '20%' : StrCast(this.layoutDoc._sidebarWidthPercent, '0%'); + @computed get layout_sidebarWidthPercent() { + return this._showSidebar ? '20%' : StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } @computed get sidebarColor() { - return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.fieldKey + '-backgroundColor'], '#e4e4e4')); + return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.fieldKey + '_backgroundColor'], '#e4e4e4')); } - @computed get autoHeight() { - return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight; + @computed get layout_autoHeight() { + return (this.props.forceAutoHeight || this.layoutDoc._layout_autoHeight) && !this.props.ignoreAutoHeight; } @computed get textHeight() { - return NumCast(this.rootDoc[this.fieldKey + '-height']); + return NumCast(this.rootDoc[this.fieldKey + '_height']); } @computed get scrollHeight() { - return NumCast(this.rootDoc[this.fieldKey + '-scrollHeight']); + return NumCast(this.rootDoc[this.fieldKey + '_scrollHeight']); } @computed get sidebarHeight() { - return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + '-height']); + return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + '_height']); } @computed get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; } - @computed get autoHeightMargins() { - return this.titleHeight + NumCast(this.layoutDoc._autoHeightMargins); + @computed get layout_autoHeightMargins() { + return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins); } @computed get _recording() { return this.dataDoc?.mediaState === 'recording'; @@ -210,8 +213,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps public RemoveLinkFromDoc(linkDoc?: Doc) { this.unhighlightSearchTerms(); const state = this._editorView?.state; - const a1 = linkDoc?.anchor1 as Doc; - const a2 = linkDoc?.anchor2 as Doc; + const a1 = linkDoc?.link_anchor_1 as Doc; + const a2 = linkDoc?.link_anchor_2 as Doc; if (state && a1 && a2 && this._editorView) { this.removeDocument(a1); this.removeDocument(a2); @@ -240,7 +243,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { if (!pinProps && this._editorView?.state.selection.empty) return this.rootDoc; - const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc, unrendered: true }); + const anchor = Docs.Create.TextConfigDocument({ annotationOn: this.rootDoc }); this.addDocument(anchor); this.makeLinkAnchor(anchor, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true } }, this.rootDoc); @@ -252,18 +255,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps AnchorMenu.Instance.Status = 'marquee'; AnchorMenu.Instance.OnClick = (e: PointerEvent) => { - !this.layoutDoc.showSidebar && this.toggleSidebar(); + !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) => { - !this.layoutDoc.showSidebar && this.toggleSidebar(); + !this.layoutDoc.layout_showSidebar && this.toggleSidebar(); const anchor = this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true, true); setTimeout(() => { const target = this._sidebarRef.current?.anchorMenuClick(anchor); if (target) { anchor.followLinkAudio = true; DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target)); - target.title = ComputedField.MakeFunction(`self["text-audioAnnotations-text"].lastElement()`); + target.title = ComputedField.MakeFunction(`self["text_audioAnnotations_text"].lastElement()`); } }); }; @@ -303,7 +306,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const curProto = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); - const effectiveAcl = GetEffectiveAcl(this.rootDoc); + const effectiveAcl = GetEffectiveAcl(dataDoc); const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"')); @@ -317,29 +320,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps dataDoc.tags = accumTags.length ? new List<string>(Array.from(new Set<string>(accumTags))) : undefined; let unchanged = true; - if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) { + if (this._applyingChange !== this.fieldKey && removeSelection(newJson) !== removeSelection(prevData?.Data)) { this._applyingChange = this.fieldKey; - const textChange = curText !== Cast(dataDoc[this.fieldKey], RichTextField)?.Text; - textChange && (dataDoc[this.fieldKey + '-lastModified'] = new DateField(new Date(Date.now()))); - if ((!curTemp && !curProto) || curText || json.includes('dash')) { + const textChange = newText !== prevData?.Text; + textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now()))); + if ((!prevData && !protoData) || newText || (!newText && !protoData)) { // 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 (removeSelection(json) !== removeSelection(curLayout?.Data)) { + if (removeSelection(newJson) !== removeSelection(prevLayoutData?.Data)) { const numstring = NumCast(dataDoc[this.fieldKey], null); - if (numstring !== undefined) { - dataDoc[this.fieldKey] = Number(curText); - } else { - dataDoc[this.fieldKey] = new RichTextField(json, curText); - } - dataDoc[this.fieldKey + '-noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited - textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); + dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText); + dataDoc[this.fieldKey + '_noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited + textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: newText }); unchanged = false; } } else { // 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((curProto || curTemp).Data))); - dataDoc[this.fieldKey + '-noTemplate'] = undefined; // mark the data field as not being split from any template it might have - ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); + this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((protoData || prevData).Data))); + dataDoc[this.fieldKey + '_noTemplate'] = undefined; // mark the data field as not being split from any template it might have + ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: newText }); unchanged = false; } this._applyingChange = ''; @@ -368,7 +367,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps let linkAnchor; let link; LinkManager.Links(this.dataDoc).forEach((l, i) => { - const anchor = (l.anchor1 as Doc).annotationOn ? (l.anchor1 as Doc) : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined; + 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; if (anchor && (anchor.annotationOn as Doc).mediaState === 'recording') { linkTime = NumCast(anchor._timecodeToShow /* audioStart */); linkAnchor = anchor; @@ -404,7 +403,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps autoLink = () => { const newAutoLinks = new Set<Doc>(); - const oldAutoLinks = LinkManager.Links(this.props.Document).filter(link => link.linkRelationship === LinkManager.AutoKeywords); + const oldAutoLinks = LinkManager.Links(this.props.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords); if (this._editorView?.state.doc.textContent) { const isNodeSel = this._editorView.state.selection instanceof NodeSelection; const f = this._editorView.state.selection.from; @@ -416,7 +415,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); this._editorView?.dispatch(tr); } - oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink); + oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink); }; updateTitle = () => { @@ -425,7 +424,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps !this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing (title.startsWith('-') || title.startsWith('@')) && this._editorView && - !this.dataDoc['title-custom'] && + !this.dataDoc.title_custom && (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === 'text') ) { let node = this._editorView.state.doc; @@ -457,8 +456,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps 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 ?? - (LinkManager.Links(this.Document).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || - DocUtils.MakeLink(this.props.Document, target, { linkRelationship: LinkManager.AutoKeywords })!); + (LinkManager.Links(this.Document).find(link => Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.link_anchor_2, Doc, null), target)) || + DocUtils.MakeLink(this.props.Document, target, { link_relationship: LinkManager.AutoKeywords })!); newAutoLinks.add(alink); const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }]; allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? [])); @@ -525,7 +524,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this.setupEditor(this.config, this.fieldKey); this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc); } - // if (this.autoHeight) this.tryUpdateScrollHeight(); + // if (this.layout_autoHeight) this.tryUpdateScrollHeight(); }; @undoBatch @@ -545,17 +544,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } else if (de.embedKey) { const target = dragData.droppedDocuments[0]; const node = schema.nodes.dashDoc.create({ - width: target[WidthSym](), - height: target[HeightSym](), + width: target[Width](), + height: target[Height](), title: 'dashDoc', docId: target[Id], float: 'unset', }); - if (!['alias', 'copy'].includes((dragData.dropAction ?? '') as any)) { + if (!['embed', 'copy'].includes((dragData.dropAction ?? '') as any)) { dragData.removeDocument?.(dragData.draggedDocuments[0]); } - target._fitContentsToBox = true; - target.context = this.rootDoc; + target._freeform_fitContentsToBox = true; + target.embedContainer = this.rootDoc; const view = this._editorView!; view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); e.stopPropagation(); @@ -612,7 +611,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps if (Array.from(highlights).join('') === FormattedTextBox._globalHighlightsCache) return; setTimeout(() => (FormattedTextBox._globalHighlightsCache = Array.from(highlights).join(''))); clearStyleSheetRules(FormattedTextBox._userStyleSheet); - if (highlights.includes('Audio Tags')) { + if (!highlights.includes('Audio Tags')) { addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, ''); } if (highlights.includes('Text from Others')) { @@ -647,24 +646,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const hr = Math.round(Date.now() / 1000 / 60 / 60); numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); } - this.layoutDoc[CssSym] = this.layoutDoc[CssSym] + 1; // css changes happen outside of react/mobx. so we need to set a flag that will notify anyone intereted in layout changes triggered by css changes (eg., CollectionLinkView) + 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 intereted in layout changes triggered by css changes (eg., CollectionLinkView) }; @observable _showSidebar = false; @computed get SidebarShown() { - return this._showSidebar || this.layoutDoc._showSidebar ? true : false; + return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false; } @action toggleSidebar = (preview: boolean = false) => { const prevWidth = 1 - this.sidebarWidth() / Number(getComputedStyle(this._ref.current!).width.replace('px', '')); if (preview) this._showSidebar = true; - else this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%'; + else this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%'; this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth); }; sidebarDown = (e: React.PointerEvent) => { - const batch = UndoManager.StartBatch('sidebar'); + const batch = UndoManager.StartBatch('toggle sidebar'); setupMoveUpEvents( this, e, @@ -682,22 +681,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps .ScreenToLocalTransform() .scale(this.props.NativeDimScaling?.() || 1) .transformDirection(delta[0], delta[1]); - const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.sidebarWidthPercent.replace('%', ''))) / 100; - const width = this.layoutDoc[WidthSym]() + localDelta[0]; - this.layoutDoc._sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%'; + const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.layout_sidebarWidthPercent.replace('%', ''))) / 100; + const width = this.layoutDoc[Width]() + localDelta[0]; + this.layoutDoc._layout_sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%'; this.layoutDoc.width = width; - this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== '0%'; + this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%'; e.preventDefault(); return false; }; - @undoBatch deleteAnnotation = (anchor: Doc) => { + const batch = UndoManager.StartBatch('delete link'); LinkManager.Instance.deleteLink(LinkManager.Links(anchor)[0]); // const docAnnotations = DocListCast(this.props.dataDoc[this.fieldKey]); // this.props.dataDoc[this.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion)); // AnchorMenu.Instance.fadeOut(true); this.props.select(false); + setTimeout(batch.end); // wait for reaction to remove link from document }; @undoBatch @@ -730,17 +730,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps .split(' ') .filter(h => h); const anchorDoc = Array.from(hrefs).lastElement().replace(Doc.localServerPath(), '').split('?')[0]; + const deleteMarkups = undoBatch(() => { + const sel = editor.state.selection; + editor.dispatch(editor.state.tr.removeMark(sel.from, sel.to, editor.state.schema.marks.linkAnchor)); + }); e.persist(); anchorDoc && DocServer.GetRefField(anchorDoc).then( action(anchor => { + anchor && SelectionManager.SelectSchemaViewDoc(anchor as Doc); AnchorMenu.Instance.Status = 'annotation'; - AnchorMenu.Instance.Delete = () => this.deleteAnnotation(anchor as Doc); + AnchorMenu.Instance.Delete = !anchor && editor.state.selection.empty ? returnFalse : !anchor ? deleteMarkups : () => this.deleteAnnotation(anchor as Doc); AnchorMenu.Instance.Pinned = false; - AnchorMenu.Instance.PinToPres = () => this.pinToPres(anchor as Doc); - AnchorMenu.Instance.MakeTargetToggle = () => this.makeTargetToggle(anchor as Doc); - AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(anchor as Doc); - AnchorMenu.Instance.IsTargetToggler = () => this.isTargetToggler(anchor as Doc); + AnchorMenu.Instance.PinToPres = !anchor ? returnFalse : () => this.pinToPres(anchor as Doc); + AnchorMenu.Instance.MakeTargetToggle = !anchor ? returnFalse : () => this.makeTargetToggle(anchor as Doc); + AnchorMenu.Instance.ShowTargetTrail = !anchor ? returnFalse : () => this.showTargetTrail(anchor as Doc); + AnchorMenu.Instance.IsTargetToggler = !anchor ? returnFalse : () => this.isTargetToggler(anchor as Doc); AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true); }) ); @@ -754,7 +759,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps description: 'plain', event: undoBatch(() => { Doc.setNativeView(this.rootDoc); - this.layoutDoc.autoHeightMargins = undefined; + this.layoutDoc.layout_autoHeightMargins = undefined; }), icon: 'eye', }); @@ -762,18 +767,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps description: 'metadata', event: undoBatch(() => { this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout; - this.rootDoc.layoutKey = 'layout_meta'; - setTimeout(() => (this.rootDoc._headerHeight = this.rootDoc._autoHeightMargins = 50), 50); + this.rootDoc.layout_fieldKey = 'layout_meta'; + setTimeout(() => (this.rootDoc._headerHeight = this.rootDoc._layout_autoHeightMargins = 50), 50); }), icon: 'eye', }); - const noteTypesDoc = Cast(Doc.UserDoc()['template-notes'], Doc, null); + const noteTypesDoc = Cast(Doc.UserDoc().template_notes, Doc, null); DocListCast(noteTypesDoc?.data).forEach(note => { const icon: IconProp = StrCast(note.icon) as IconProp; changeItems.push({ description: StrCast(note.title), event: undoBatch(() => { - this.layoutDoc.autoHeightMargins = undefined; + this.layoutDoc.layout_autoHeightMargins = undefined; Doc.setNativeView(this.rootDoc); DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note); }), @@ -799,11 +804,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps ); const uicontrols: ContextMenuProps[] = []; - uicontrols.push({ description: !this.Document._noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar), icon: !this.Document._noSidebar ? 'eye-slash' : 'eye' }); uicontrols.push({ - description: (this.Document._showAltContentUI ? 'Hide' : 'Show') + ' Alt Content UI', - event: () => (this.layoutDoc._showAltContentUI = !this.layoutDoc._showAltContentUI), - icon: !this.Document._showAltContentUI ? 'eye-slash' : 'eye', + description: !this.Document._layout_noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', + event: () => (this.layoutDoc._layout_noSidebar = !this.layoutDoc._layout_noSidebar), + icon: !this.Document._layout_noSidebar ? 'eye-slash' : 'eye', + }); + uicontrols.push({ + description: (this.Document._layout_enableAltContentUI ? 'Hide' : 'Show') + ' Alt Content UI', + event: () => (this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI), + icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye', }); uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' }); !Doc.noviceMode && @@ -834,10 +843,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this.rootDoc.title = this.layoutDoc.isTemplateForField as string; this.rootDoc.isTemplateDoc = false; this.rootDoc.isTemplateForField = ''; - this.rootDoc.layoutKey = 'layout'; + this.rootDoc.layout_fieldKey = 'layout'; MakeTemplate(this.rootDoc, true, title); setTimeout(() => { - this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height + this.rootDoc._layout_autoHeight = this.layoutDoc._layout_autoHeight; // layout_autoHeight, width and height this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, 'string', null); @@ -845,7 +854,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps }, 10); } Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc); - Doc.AddDocToList(Cast(Doc.UserDoc()['template-notes'], Doc, null), 'data', this.rootDoc); + Doc.AddDocToList(Cast(Doc.UserDoc().template_notes, Doc, null), 'data', this.rootDoc); }, icon: 'eye', }); @@ -856,11 +865,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' }); optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' }); optionItems.push({ - description: !this.Document._singleLine ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns', - event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), - icon: !this.Document._singleLine ? 'grip-lines' : 'bars', + description: !this.Document._createDocOnCR ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns', + event: () => (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR), + icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars', }); - !Doc.noviceMode && optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: this.Document._autoHeight ? 'lock' : 'unlock' }); + !Doc.noviceMode && + optionItems.push({ + description: `${this.Document._layout_autoHeight ? 'Lock' : 'Auto'} Height`, + event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), + icon: this.Document._layout_autoHeight ? 'lock' : 'unlock', + }); optionItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: 'text' }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); this._downX = this._downY = Number.NaN; @@ -900,18 +914,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps y: NumCast(this.rootDoc.y), _height: 200, _width: 200, - 'data-nativeWidth': result.nativeWidth, - 'data-nativeHeight': result.nativeHeight, + data_nativeWidth: result.nativeWidth, + data_nativeHeight: result.nativeHeight, }); - if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc)) { + if (Doc.IsInMyOverlay(this.rootDoc)) { newDoc.overlayX = this.rootDoc.x; newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height); - Doc.AddDocToList(Doc.MyOverlayDocs, undefined, newDoc); + Doc.AddToMyOverlay(newDoc); } else { this.props.addDocument?.(newDoc); } // Create link between prompt and image - DocUtils.MakeLink(this.rootDoc, newDoc, { linkRelationship: 'Image Prompt' }); + DocUtils.MakeLink(this.rootDoc, newDoc, { link_relationship: 'Image Prompt' }); } } catch (err) { console.log(err); @@ -948,14 +962,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps if (this._editorView && this._recordingStart) { if (this._break) { const textanchorFunc = () => { - const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor', unrendered: true }); + const tanch = Docs.Create.TextConfigDocument({ title: 'dictation anchor' }); return this.addDocument(tanch) ? tanch : undefined; }; const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement(); if (link) { Doc.GetProto(link).isDictation = true; - const audioanchor = Cast(link.anchor2, Doc, null); - const textanchor = Cast(link.anchor1, Doc, null); + const audioanchor = Cast(link.link_anchor_2, Doc, null); + const textanchor = Cast(link.link_anchor_1, Doc, null); if (audioanchor) { audioanchor.backgroundColor = 'tan'; const audiotag = this._editorView.state.schema.nodes.audiotag.create({ @@ -986,7 +1000,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); let tr = state.tr.addMark(sel.from, sel.to, splitter); if (sel.from !== sel.to) { - const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: '#' + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true }); + const anchor = + anchorDoc ?? + Docs.Create.TextConfigDocument({ + // + title: 'text(' + this._editorView?.state.doc.textBetween(sel.from, sel.to) + ')', + annotationOn: this.dataDoc, + }); const href = targetHref ?? Doc.localServerPath(anchor); if (anchor !== anchorDoc && addAsAnnotation) this.addDocument(anchor); tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { @@ -1010,7 +1030,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } getView = async (doc: Doc) => { - if (DocListCast(this.rootDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.unrendered ? DocCast(doc.annotationOn) : doc, anno))) { + if (DocListCast(this.rootDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) { !this.SidebarShown && this.toggleSidebar(false); setTimeout(() => this._sidebarRef?.current?.makeDocUnfiltered(doc)); } @@ -1079,11 +1099,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } }; - // if the scroll height has changed and we're in autoHeight mode, then we need to update the textHeight component of the doc. + // if the scroll height has changed and we're in layout_autoHeight mode, then we need to update the textHeight component of the doc. // Since we also monitor all component height changes, this will update the document's height. resetNativeHeight = (scrollHeight: number) => { const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight); - this.rootDoc[this.fieldKey + '-height'] = scrollHeight; + this.rootDoc[this.fieldKey + '_height'] = scrollHeight; if (nh) this.layoutDoc._nativeHeight = scrollHeight; }; @@ -1094,9 +1114,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps !this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. this._cachedLinks = LinkManager.Links(this.Document); this._disposers.breakupDictation = reaction(() => DocumentManager.Instance.RecordingEvent, this.breakupDictation); - this._disposers.autoHeight = reaction( - () => this.autoHeight, - autoHeight => autoHeight && this.tryUpdateScrollHeight() + this._disposers.layout_autoHeight = reaction( + () => this.layout_autoHeight, + layout_autoHeight => layout_autoHeight && this.tryUpdateScrollHeight() ); this._disposers.highlights = reaction( () => Array.from(FormattedTextBox._globalHighlights).slice(), @@ -1108,16 +1128,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps width => this.tryUpdateScrollHeight() ); this._disposers.scrollHeight = reaction( - () => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }), - ({ width, scrollHeight, autoHeight }) => width && autoHeight && this.resetNativeHeight(scrollHeight), + () => ({ scrollHeight: this.scrollHeight, layout_autoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }), + ({ width, scrollHeight, layout_autoHeight }) => width && layout_autoHeight && this.resetNativeHeight(scrollHeight), { fireImmediately: true } ); this._disposers.componentHeights = reaction( - // set the document height when one of the component heights changes and autoHeight is on - () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }), - ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => { + // set the document height when one of the component heights changes and layout_autoHeight is on + () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layout_autoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }), + ({ sidebarHeight, textHeight, layout_autoHeight, marginsHeight }) => { const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight)); - if (autoHeight && newHeight && newHeight !== this.rootDoc.height && !this.props.dontRegisterView) { + if (layout_autoHeight && newHeight && newHeight !== this.rootDoc.height && !this.props.dontRegisterView) { this.props.setHeight?.(newHeight); } }, @@ -1142,8 +1162,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this._disposers.editorState = reaction( () => { const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc?.proto), this.fieldKey) ? DocCast(this.layoutDoc?.proto) : this?.dataDoc; - const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : dataDoc?.[this.fieldKey + '-noTemplate'] || !this.layoutDoc[this.fieldKey] ? dataDoc : this.layoutDoc; - return !whichDoc ? undefined : { data: Cast(whichDoc[this.fieldKey], RichTextField, null), str: Field.toString(whichDoc[this.fieldKey]) }; + const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : dataDoc?.[this.fieldKey + '_noTemplate'] || !this.layoutDoc[this.fieldKey] ? dataDoc : this.layoutDoc; + return !whichDoc ? undefined : { data: Cast(whichDoc[this.fieldKey], RichTextField, null), str: Field.toString(DocCast(whichDoc[this.fieldKey])) }; }, incomingValue => { if (this._editorView && this._applyingChange !== this.fieldKey) { @@ -1188,7 +1208,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps () => this.props.isSelected(), action(selected => { if (FormattedTextBox._globalHighlights.has('Bold Text')) { - this.layoutDoc[CssSym] = this.layoutDoc[CssSym] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed + 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) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined); @@ -1220,7 +1240,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } var quickScroll: string | undefined = ''; this._disposers.scroll = reaction( - () => NumCast(this.layoutDoc._scrollTop), + () => NumCast(this.layoutDoc._layout_scrollTop), pos => { if (!this._ignoreScroll && this._scrollRef.current && !this.props.dontSelectOnLoad) { const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition); @@ -1297,7 +1317,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } }, 0); dataDoc.title = exportState.title; - this.dataDoc['title-custom'] = true; + this.dataDoc.title_custom = true; dataDoc.googleDocUnchanged = true; } else { delete dataDoc[GoogleRef]; @@ -1354,7 +1374,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps view.state.schema.marks.linkAnchor.create({ allAnchors: [{ href: `/doc/${this.rootDoc[Id]}`, title: this.rootDoc.title, anchorId: `${this.rootDoc[Id]}` }], location: 'add:right', - title: `from: ${DocCast(pdfAnchor.context).title}`, + title: `from: ${DocCast(pdfAnchor.embedContainer).title}`, noPreview: true, docref: false, }), @@ -1363,7 +1383,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps ]), ]); - const link = DocUtils.MakeLink(pdfAnchor, this.rootDoc, { linkRelationship: 'PDF pasted' }); + const link = DocUtils.MakeLink(pdfAnchor, this.rootDoc, { link_relationship: 'PDF pasted' }); if (link) { view.dispatch(view.state.tr.replaceSelectionWith(dashField, false).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste')); } @@ -1451,7 +1471,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps if (startupText) { dispatch(state.tr.insertText(startupText)); } - const textAlign = StrCast(this.dataDoc['text-align'], StrCast(Doc.UserDoc().textAlign, 'left')); + const textAlign = StrCast(this.dataDoc.text_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 }))); @@ -1462,11 +1482,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())); - if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) { + if (this._editorView && selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) { const selLoadChar = FormattedTextBox.SelectOnLoadChar; FormattedTextBox.SelectOnLoad = ''; this.props.select(false); - if (selLoadChar && this._editorView) { + 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: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? []; @@ -1476,10 +1496,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps .insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size) .setStoredMarks(storedMarks); this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size)))); - } else if (this._editorView && curText && !FormattedTextBox.DontSelectInitialText) { + } else if (curText && !FormattedTextBox.DontSelectInitialText) { selectAll(this._editorView.state, this._editorView?.dispatch); - this.startUndoTypingBatch(); - } else if (this._editorView) { + } else { this._editorView.dispatch(this._editorView.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); } } @@ -1488,18 +1507,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps if (this._editorView) { const tr = this._editorView.state.tr; const { from, to } = tr.selection; - // for some reason, the selection is sometimes lost in the sidebar view when prosemirror syncs the seledtion with the dom, so reset the selectoin after the document has ben fully instantiated. + // for some reason, the selection is sometimes lost in the sidebar view when prosemirror syncs the seledtion with the dom, so reset the selection after the document has ben fully instantiated. if (FormattedTextBox.DontSelectInitialText) setTimeout(() => this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)))), 250); - this._editorView.state.storedMarks = [ - ...(this._editorView.state.storedMarks ?? []), - ...(!this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark) ? [schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })] : []), - ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), - ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []), - ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []), - ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []), - ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []), - ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []), - ]; + this._editorView.dispatch( + this._editorView.state.tr.setStoredMarks([ + ...(this._editorView.state.storedMarks ?? []), + ...(!this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark) ? [schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })] : []), + ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), + ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []), + ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []), + ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []), + ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []), + ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []), + ]) + ); if (FormattedTextBox.PasteOnLoad) { const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor'); FormattedTextBox.PasteOnLoad = undefined; @@ -1524,7 +1545,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } else (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 context (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. + 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. if ((e.target as any).tagName === 'AUDIOTAG') { e.preventDefault(); e.stopPropagation(); @@ -1575,7 +1596,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu(); else if (this.props.isContentActive(true)) { const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); - !this.props.isSelected(true) && editor.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(pcords?.pos || 0)))); + // !this.props.isSelected(true) && + editor.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(pcords?.pos || 0)))); let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span> while (target && !target.dataset?.targethrefs) target = target.parentElement; FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true'); @@ -1611,7 +1633,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps onFocused = (e: React.FocusEvent): void => { //applyDevTools.applyDevTools(this._editorView); this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); - this.startUndoTypingBatch(); + e.stopPropagation(); }; onClick = (e: React.MouseEvent): void => { @@ -1686,13 +1708,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } } startUndoTypingBatch() { - !this._undoTyping && (this._undoTyping = UndoManager.StartBatch('undoTyping')); + !this._undoTyping && (this._undoTyping = UndoManager.StartBatch('text edits on ' + this.rootDoc.title)); } public endUndoTypingBatch() { - const wasUndoing = this._undoTyping; this._undoTyping?.end(); this._undoTyping = undefined; - return wasUndoing; } @action @@ -1720,13 +1740,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const state = this._editorView!.state; const curText = state.doc.textBetween(0, state.doc.content.size, ' \n'); - if (this.layoutDoc.sidebarViewType === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) { + if (this.layoutDoc.sidebar_collectionType === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) { try { translateGoogleApi(curText, { from: 'en', to: 'es' }).then((result1: any) => { setTimeout( () => translateGoogleApi(result1[0], { from: 'es', to: 'en' }).then((result: any) => { - this.dataDoc[this.fieldKey + '-translation'] = result1 + '\r\n\r\n' + result[0]; + this.dataDoc[this.fieldKey + '_translation'] = result1 + '\r\n\r\n' + result[0]; }), 1000 ); @@ -1736,10 +1756,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } this._lastText = curText; } - if (StrCast(this.rootDoc.title).startsWith('@') && !this.dataDoc['title-custom']) { + if (StrCast(this.rootDoc.title).startsWith('@') && !this.dataDoc.title_custom) { UndoManager.RunInBatch(() => { - this.dataDoc['title-custom'] = true; - this.dataDoc.showTitle = 'title'; + this.dataDoc.title_custom = true; + this.dataDoc.layout_showTitle = 'title'; const tr = this._editorView!.state.tr; this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.rootDoc.title).length + 2))).deleteSelection()); }, 'titler'); @@ -1802,7 +1822,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps if (!LinkDocPreview.LinkInfo && this._scrollRef.current) { if (!this.props.dontSelectOnLoad) { this._ignoreScroll = true; - this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop; + this.layoutDoc._layout_scrollTop = this._scrollRef.current.scrollTop; this._ignoreScroll = false; e.stopPropagation(); e.preventDefault(); @@ -1813,13 +1833,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps 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) { - const proseHeight = !this.ProseRef - ? 0 - : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace('px', '')) + Number(getComputedStyle(child).marginTop.replace('px', '')) + Number(getComputedStyle(child).marginBottom.replace('px', '')), margins); - const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); + const toNum = (val: string) => Number(val.replace('px', '').replace('auto', '0')); + const toHgt = (node: Element) => { + const { height, marginTop, marginBottom } = getComputedStyle(node); + return toNum(height) + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom)); + }; + const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins); + const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.layout_maxAutoHeight, proseHeight), proseHeight); if (this.props.setHeight && scrollHeight && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation - const setScrollHeight = () => (this.rootDoc[this.fieldKey + '-scrollHeight'] = scrollHeight); + const setScrollHeight = () => (this.rootDoc[this.fieldKey + '_scrollHeight'] = scrollHeight); if (this.rootDoc === this.layoutDoc || this.layoutDoc.resolvedDataDoc) { setScrollHeight(); } else { @@ -1828,21 +1851,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } } }; - fitContentsToBox = () => BoolCast(this.props.Document._fitContentsToBox); - sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); + fitContentsToBox = () => BoolCast(this.props.Document._freeform_fitContentsToBox); + sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1); sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => { - if (!this.layoutDoc._showSidebar) this.toggleSidebar(); + 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); - setSidebarHeight = (height: number) => (this.rootDoc[this.SidebarKey + '-height'] = height); - sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth(); + setSidebarHeight = (height: number) => (this.rootDoc[this.SidebarKey + '_height'] = height); + sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth(); sidebarScreenToLocal = () => this.props .ScreenToLocalTransform() .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.NativeDimScaling?.() || 1), 0) - .scale(1 / NumCast(this.layoutDoc._viewScale, 1) / (this.props.NativeDimScaling?.() || 1)); + .scale(1 / NumCast(this.layoutDoc._freeform_scale, 1) / (this.props.NativeDimScaling?.() || 1)); @computed get audioHandle() { return !this._recording ? null : ( @@ -1876,7 +1899,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps color, opacity: annotated ? 1 : undefined, }}> - <FontAwesomeIcon icon={'comment-alt'} /> + <FontAwesomeIcon icon="comment-alt" /> </div> ); } @@ -1928,25 +1951,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps fitContentsToBox={this.fitContentsToBox} noSidebar={true} treeViewHideTitle={true} - fieldKey={this.layoutDoc.sidebarViewType === 'translation' ? `${this.fieldKey}-translation` : `${this.fieldKey}-sidebar`} + fieldKey={this.layoutDoc.sidebar_collectionType === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`} /> </div> ); }; return ( - <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> - {renderComponent(StrCast(this.layoutDoc.sidebarViewType))} + <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> + {renderComponent(StrCast(this.layoutDoc.sidebar_collectionType))} </div> ); } cycleAlternateText = () => { - if (this.layoutDoc._showAltContentUI) { - const usePath = this.rootDoc[`${this.props.fieldKey}-usePath`]; - this.rootDoc[`_${this.props.fieldKey}-usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined; + if (this.layoutDoc._layout_enableAltContentUI) { + const usePath = this.rootDoc[`${this.props.fieldKey}_usePath`]; + this.rootDoc[`_${this.props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined; } }; @computed get overlayAlternateIcon() { - const usePath = this.rootDoc[`${this.props.fieldKey}-usePath`]; + const usePath = this.rootDoc[`${this.props.fieldKey}_usePath`]; return ( <Tooltip title={ @@ -1978,41 +2001,42 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps ); } @computed get fieldKey() { - const usePath = StrCast(this.rootDoc[`${this.props.fieldKey}-usePath`]); - return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering) ? `-${usePath.replace(':hover', '')}` : ''); + const usePath = StrCast(this.rootDoc[`${this.props.fieldKey}_usePath`]); + return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering) ? `_${usePath.replace(':hover', '')}` : ''); } @observable _isHovering = false; + onPassiveWheel = (e: WheelEvent) => { + // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this) + if (this.props.isContentActive() && !this.props.allowScroll) { + if (!NumCast(this.layoutDoc._layout_scrollTop) && e.deltaY <= 0) e.preventDefault(); + e.stopPropagation(); + } + }; + _oldWheel: any; render() { TraceMobx(); const active = this.props.isContentActive() || this.props.isSelected(); const selected = active; - const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); - const rounded = StrCast(this.layoutDoc.borderRounding) === '100%' ? '-rounded' : ''; + const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1); + const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : ''; const interactive = (Doc.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition); if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide); const minimal = this.props.ignoreAutoHeight; const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0); const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); - const selPad = (selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0; - const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? '-selected' : ''; + const selPad = (selected && !this.layoutDoc._createDocOnCR) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0; + const selPaddingClass = selected && !this.layoutDoc._createDocOnCR && paddingY >= 10 ? '-selected' : ''; const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' > return styleFromLayoutString?.height === '0px' ? null : ( <div className="formattedTextBox" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))} - ref={r => - r?.addEventListener( - 'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this) - (e: WheelEvent) => { - if (this.props.isContentActive()) { - if (!NumCast(this.layoutDoc._scrollTop) && e.deltaY <= 0) e.preventDefault(); - e.stopPropagation(); - } - }, - { passive: false } - ) - } + ref={r => { + this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); + this._oldWheel = r; + r?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + }} style={{ ...(this.props.dontScale ? {} @@ -2024,7 +2048,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps }), display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined, transition: 'inherit', - // overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined, + // overflowY: this.layoutDoc._layout_autoHeight ? "hidden" : undefined, color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color), fontSize: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize), fontFamily: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily), @@ -2035,8 +2059,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps className="formattedTextBox-cont" ref={this._ref} style={{ - overflow: this.autoHeight && this.props.CollectionFreeFormDocumentView?.() ? 'hidden' : undefined, //x this breaks viewing an autoHeight doc in its own tab, or in the lightbox - height: this.props.height || (this.autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined), + overflow: this.layout_autoHeight && this.props.CollectionFreeFormDocumentView?.() ? 'hidden' : undefined, //x this breaks viewing an layout_autoHeight doc in its own tab, or in the lightbox + height: this.props.height || (this.layout_autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined), pointerEvents: interactive ? undefined : 'none', }} onContextMenu={this.specificContextMenu} @@ -2052,9 +2076,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps className={`formattedTextBox-outer${selected ? '-selected' : ''}`} ref={this._scrollRef} style={{ - width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.sidebarWidthPercent})`, + width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`, pointerEvents: !active && !SnappingManager.GetIsDragging() ? 'none' : undefined, - overflow: this.layoutDoc._singleLine ? 'hidden' : this.layoutDoc._autoHeight ? 'visible' : undefined, + overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined, }} onScroll={this.onScroll} onDrop={this.ondrop}> @@ -2071,10 +2095,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps }} /> </div> - {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection} - {this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle} + {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection} + {this.noSidebar || this.Document._layout_noSidebar || this.props.dontSelectOnLoad || this.Document._createDocOnCR ? null : this.sidebarHandle} {this.audioHandle} - {this.layoutDoc._showAltContentUI ? this.overlayAlternateIcon : null} + {this.layoutDoc._layout_enableAltContentUI ? this.overlayAlternateIcon : null} </div> </div> ); diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index f0caa1f4f..7c3e4baad 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -138,7 +138,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this.activeListType = this.getActiveListStyle(); this._activeAlignment = this.getActiveAlignment(); - this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document.fontFamily, StrCast(Doc.UserDoc().fontFamily, 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; + this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(Doc.UserDoc().fontFamily, 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0]; this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(Doc.UserDoc().fontColor, 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...'; this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...'; @@ -221,7 +221,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { m.type === state.schema.marks.marker && activeHighlights.add(String(m.attrs.highlight)); }); } else if (SelectionManager.Views().some(dv => dv.ComponentView instanceof EquationBox)) { - SelectionManager.Views().forEach(dv => StrCast(dv.rootDoc._fontSize) && activeSizes.add(StrCast(dv.rootDoc._fontSize))); + SelectionManager.Views().forEach(dv => StrCast(dv.rootDoc._text_fontSize) && activeSizes.add(StrCast(dv.rootDoc._text_fontSize))); } return { activeFamilies: Array.from(activeFamilies), activeSizes: Array.from(activeSizes), activeColors: Array.from(activeColors), activeHighlights: Array.from(activeHighlights) }; } @@ -345,8 +345,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this.view.focus(); } } else if (SelectionManager.Views().some(dv => dv.ComponentView instanceof EquationBox)) { - SelectionManager.Views().forEach(dv => (dv.rootDoc._fontSize = fontSize)); - } else Doc.UserDoc()._fontSize = fontSize; + SelectionManager.Views().forEach(dv => (dv.rootDoc._text_fontSize = fontSize)); + } else Doc.UserDoc().fontSize = fontSize; this.updateMenu(this.view, undefined, this.props); }; @@ -355,7 +355,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { const fmark = this.view.state.schema.marks.pFontFamily.create({ family: family }); this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true); this.view.focus(); - } else Doc.UserDoc()._fontFamily = family; + } else Doc.UserDoc().fontFamily = family; this.updateMenu(this.view, undefined, this.props); }; @@ -623,15 +623,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { if (linkclicked) { const linkDoc = await DocServer.GetRefField(linkclicked); if (linkDoc instanceof Doc) { - const anchor1 = await Cast(linkDoc.anchor1, Doc); - const anchor2 = await Cast(linkDoc.anchor2, Doc); + const link_anchor_1 = await Cast(linkDoc.link_anchor_1, Doc); + const link_anchor_2 = await Cast(linkDoc.link_anchor_2, Doc); const currentDoc = SelectionManager.Docs().lastElement(); - if (currentDoc && anchor1 && anchor2) { - if (Doc.AreProtosEqual(currentDoc, anchor1)) { - return StrCast(anchor2.title); + if (currentDoc && link_anchor_1 && link_anchor_2) { + if (Doc.AreProtosEqual(currentDoc, link_anchor_1)) { + return StrCast(link_anchor_2.title); } - if (Doc.AreProtosEqual(currentDoc, anchor2)) { - return StrCast(anchor1.title); + if (Doc.AreProtosEqual(currentDoc, link_anchor_2)) { + return StrCast(link_anchor_1.title); } } } @@ -758,11 +758,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { // <div className="collectionMenu-divider" key="divider 3" /> // {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => { // this.activeFontSize = val; - // SelectionManager.Views().map(dv => dv.props.Document._fontSize = val); + // SelectionManager.Views().map(dv => dv.props.Document._text_fontSize = val); // })), // this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => { // this.activeFontFamily = val; - // SelectionManager.Views().map(dv => dv.props.Document._fontFamily = val); + // SelectionManager.Views().map(dv => dv.props.Document._text_fontFamily = val); // })), // <div className="collectionMenu-divider" key="divider 4" />, // this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})), diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index cad56b14b..ac1e7ce5d 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -1,11 +1,11 @@ import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules'; import { NodeSelection, TextSelection } from 'prosemirror-state'; -import { DataSym, Doc, StrListCast } from '../../../../fields/Doc'; +import { Doc, StrListCast } from '../../../../fields/Doc'; +import { DocData } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { ComputedField } from '../../../../fields/ScriptField'; -import { NumCast, StrCast } from '../../../../fields/Types'; -import { normalizeEmail } from '../../../../fields/util'; +import { NumCast } from '../../../../fields/Types'; import { Utils } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; @@ -76,14 +76,23 @@ export class RichTextRules { //Create annotation to a field on the text document new InputRule(new RegExp(/>>$/), (state, match, start, end) => { - const textDoc = this.Document[DataSym]; + const textDoc = this.Document[DocData]; const numInlines = NumCast(textDoc.inlineTextCount); textDoc.inlineTextCount = numInlines + 1; const inlineFieldKey = 'inline' + numInlines; // which field on the text document this annotation will write to const inlineLayoutKey = 'layout_' + inlineFieldKey; // the field holding the layout string that will render the inline annotation - const textDocInline = Docs.Create.TextDocument('', { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _fitWidth: true, _autoHeight: true, _fontSize: '9px', title: 'inline comment' }); + const textDocInline = Docs.Create.TextDocument('', { + _layout_fieldKey: inlineLayoutKey, + _width: 75, + _height: 35, + annotationOn: textDoc, + _layout_fitWidth: true, + _layout_autoHeight: true, + _text_fontSize: '9px', + title: 'inline comment', + }); textDocInline.title = inlineFieldKey; // give the annotation its own title - textDocInline['title-custom'] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc + textDocInline.title_custom = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]] textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`); @@ -249,7 +258,7 @@ export class RichTextRules { this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); } - DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { linkRelationship: 'portal to:portal from' }); + DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { link_relationship: 'portal to:portal from' }); const fstate = this.TextBox.EditorView?.state; if (fstate && selection) { @@ -268,7 +277,7 @@ export class RichTextRules { } if (value !== '' && value !== undefined) { const num = value.match(/^[0-9.]$/); - this.Document[DataSym][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; + this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; } const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId, hideKey: false }); return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true); @@ -299,37 +308,15 @@ export class RichTextRules { return tr.setSelection(new NodeSelection(tr.doc.resolve(tr.selection.$from.pos - 1))); }), - // create an inline view of a document {{ <layoutKey> : <Doc> }} - // {{:Doc}} => show default view of document - // {{<layout>}} => show layout for this doc - // {{<layout> : Doc}} => show layout for another doc - new InputRule(new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._/\-]*\))?(:[a-zA-Z_@\.\? \-0-9]+)?\}\}$/), (state, match, start, end) => { - const fieldKey = match[1] || ''; - const fieldParam = match[2]?.replace('…', '...') || ''; - const rawdocid = match[3]?.substring(1); - const docId = rawdocid ? (!rawdocid.includes('@') ? normalizeEmail(Doc.CurrentUserEmail) + '@' + rawdocid : rawdocid) : undefined; - if (!fieldKey && !docId) return state.tr; - docId && - DocServer.GetRefField(docId).then(docx => { - if (!(docx instanceof Doc && docx)) { - Docs.Create.FreeformDocument([], { title: rawdocid, _width: 500, _height: 500 }, docId); - } - }); - const node = (state.doc.resolve(start) as any).nodeAfter; - const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: 'dashDoc', docId, fieldKey: fieldKey + fieldParam, float: 'unset', alias: Utils.GenerateGuid() }); - const sm = state.storedMarks || undefined; - return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; - }), - // create an inline view of a tag stored under the '#' field new InputRule(new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => { const tag = match[1]; if (!tag) return state.tr; - //this.Document[DataSym]['#' + tag] = '#' + tag; - const tags = StrListCast(this.Document[DataSym].tags); + //this.Document[DocData]['#' + tag] = '#' + tag; + const tags = StrListCast(this.Document[DocData].tags); if (!tags.includes(tag)) { tags.push(tag); - this.Document[DataSym].tags = new List<string>(tags); + this.Document[DocData].tags = new List<string>(tags); } const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag }); return state.tr diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 5b47e8a70..7e17008bb 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -46,7 +46,7 @@ export const marks: { [index: string]: MarkSpec } = { toDOM(node: any) { const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (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', { class: anchorids, 'data-targethrefs': targethrefs, 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0]; + return ['a', { class: anchorids, 'data-targethrefs': targethrefs, 'data-noPreview': 'true', 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0]; }, }, noAutoLinkAnchor: { diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 6c9d5d31a..f27fb18e2 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -247,7 +247,7 @@ export const nodes: { [index: string]: NodeSpec } = { hidden: { default: false }, // whether dashComment node has toggle the dashDoc's display off fieldKey: { default: '' }, docId: { default: '' }, - alias: { default: '' }, + embedding: { default: '' }, }, group: 'inline', draggable: false, |
