diff options
Diffstat (limited to 'src/client/views')
| -rw-r--r-- | src/client/views/ContextMenu.tsx | 18 | ||||
| -rw-r--r-- | src/client/views/GlobalKeyHandler.ts | 2 | ||||
| -rw-r--r-- | src/client/views/InkControlPtHandles.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 6 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 10 | ||||
| -rw-r--r-- | src/client/views/nodes/FontIconBox/FontIconBox.scss | 10 | ||||
| -rw-r--r-- | src/client/views/nodes/FontIconBox/FontIconBox.tsx | 8 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/DashDocCommentView.tsx | 39 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 7 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/PaintButtonView.tsx | 113 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/RichTextRules.ts | 164 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/nodes_rts.ts | 12 |
12 files changed, 315 insertions, 76 deletions
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 8dcdd80e5..8c3c9df2e 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -183,11 +183,12 @@ export class ContextMenu extends ObservableReactComponent<{}> { @computed get menuItems() { if (!this._searchString) { - return this._items.map((item, ind) => <ContextMenuItem {...item} noexpand={this.itemsNeedSearch ? true : (item as any).noexpand} key={ind + item.description} closeMenu={this.closeMenu} />); + return this._items.map((item, ind) => <ContextMenuItem key={item.description + ind} {...item} noexpand={this.itemsNeedSearch ? true : (item as any).noexpand} closeMenu={this.closeMenu} />); } return this.filteredItems.map((value, index) => Array.isArray(value) ? ( <div + key={index + value.join(' -> ')} className="contextMenu-group" style={{ background: StrCast(SettingsManager.userVariantColor), @@ -204,7 +205,10 @@ export class ContextMenu extends ObservableReactComponent<{}> { return this._showSearch ? 1 : this._items.reduce((p, mi) => p + ((mi as any).noexpand ? 1 : (mi as any).subitems?.length || 1), 0) > 15; } + _searchRef = React.createRef<HTMLInputElement>(); // bcz: we shouldn't need this, since we set autoFocus on the <input> tag, but for some reason we do... + render() { + this.itemsNeedSearch && setTimeout(() => this._searchRef.current?.focus()); return ( <div className="contextMenu-cont" @@ -226,7 +230,17 @@ export class ContextMenu extends ObservableReactComponent<{}> { <span className="icon-background"> <FontAwesomeIcon icon="search" size="lg" /> </span> - <input style={{ color: 'black' }} className="contextMenu-item contextMenu-description search" type="text" placeholder="Filter Menu..." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus /> + <input + ref={this._searchRef} + style={{ color: 'black' }} + className="contextMenu-item contextMenu-description search" + type="text" + placeholder="Filter Menu..." + value={this._searchString} + onKeyDown={this.onKeyDown} + onChange={this.onChange} + autoFocus + /> </span> )} {this.menuItems} diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index d134d9e7b..733383002 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -160,7 +160,7 @@ export class KeyManager { if (LightboxView.LightboxDoc) { LightboxView.Instance.SetLightboxDoc(undefined); SelectionManager.DeselectAll(); - } else DocumentDecorations.Instance.onCloseClick(true); + } else if (!window.getSelection()?.toString()) DocumentDecorations.Instance.onCloseClick(true); return { stopPropagation: true, preventDefault: true }; } break; diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index 7dd57e04d..31b13d2c8 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -189,6 +189,7 @@ export class InkEndPtHandles extends React.Component<InkEndProps> { @observable _overStart: boolean = false; @observable _overEnd: boolean = false; + _throttle = 0; // need to throttle dragging since the position may change when the control points change. this allows the stroke to settle so that we don't get increasingly bad jitter @action dragRotate = (e: React.PointerEvent, pt1: () => { X: number; Y: number }, pt2: () => { X: number; Y: number }) => { SnappingManager.SetIsDragging(true); @@ -196,6 +197,7 @@ export class InkEndPtHandles extends React.Component<InkEndProps> { this, e, action(e => { + if (this._throttle++ % 2 !== 0) return false; if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('stretch ink'); // compute stretch factor by finding scaling along axis between start and end points const p1 = pt1(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 82ada4fb5..54e8b08b6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -5,7 +5,7 @@ import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; import { DateField } from '../../../../fields/DateField'; -import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc'; import { DocData, Height, Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField'; @@ -53,6 +53,7 @@ import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannable import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; +import { RichTextField } from '../../../../fields/RichTextField'; export interface collectionFreeformViewProps { NativeWidth?: () => number; @@ -75,7 +76,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, ''); @computed get paintFunc() { - const paintFunc = StrCast(this.Document[DocData].paintFunc).trim(); + const field = this.layoutDoc[this.fieldKey]; + const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as Field)).trim(); return !paintFunc ? '' : `const dashDiv = document.querySelector('#${this._paintedId}'); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2e1ba2a7e..51f4b1a68 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -456,11 +456,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc'); setToggleDetail = undoable( - () => + (defaultLayout: string) => (this.Document.onClick = ScriptField.MakeScript( `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey) .replace('layout_', '') - .replace(/^layout$/, 'detail')}")`, + .replace(/^layout$/, 'detail')}", "${defaultLayout}")`, { documentView: 'any' } )), 'set toggle detail' @@ -1212,7 +1212,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { }; public noOnClick = () => this._docViewInternal?.noOnClick(); public toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle); - public setToggleDetail = () => this._docViewInternal?.setToggleDetail(); + public setToggleDetail = (defaultLayout = '') => this._docViewInternal?.setToggleDetail(defaultLayout); public onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY); public cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents(); public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource); @@ -1460,8 +1460,8 @@ ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); //, 0); }); -ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { - if (dv.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(false, 'layout'); +ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string, defaultLayout = '') { + if (dv.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(defaultLayout ? true : false, defaultLayout, undefined, true); else dv.switchViews(true, detailLayoutKeySuffix, undefined, true); }); diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss index db2ffa756..2db285910 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.scss +++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss @@ -1,5 +1,15 @@ @import '../../global/globalCssVariables.module.scss'; +// bcz: something's messed up with the IconButton css. this mostly fixes the fit-all button, the color buttons, the undo +/- expander and the dropdown doc type list (eg 'text') +.iconButton-container { + width: unset !important; + min-width: 30px !important; + height: unset !important; + min-height: 30px; + .color { + height: 3px !important; + } +} .menuButton { height: 100%; display: flex; diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 8290e102c..3577cc8d9 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -381,7 +381,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() { case ButtonType.ColorButton: return this.colorButton; case ButtonType.MultiToggleButton: return this.multiToggleButton; case ButtonType.ToggleButton: return this.toggleButton; - case ButtonType.ClickButton: + case ButtonType.ClickButton:return <IconButton {...btnProps} size={Size.MEDIUM} color={color} />; case ButtonType.ToolButton: return <IconButton {...btnProps} size={Size.LARGE} color={color} />; case ButtonType.TextButton: return <Button {...btnProps} color={color} background={SettingsManager.userBackgroundColor} text={StrCast(this.dataDoc.buttonText)}/>; @@ -392,6 +392,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() { }; render() { - return <div onContextMenu={this.specificContextMenu}>{this.renderButton()}</div>; + return ( + <div style={{ margin: 'auto', width: '100%' }} onContextMenu={this.specificContextMenu}> + {this.renderButton()} + </div> + ); } } diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx index b7d2a24c2..a72ed1813 100644 --- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx +++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx @@ -3,6 +3,8 @@ import * as ReactDOM from 'react-dom/client'; import { Doc } from '../../../../fields/Doc'; import { DocServer } from '../../../DocServer'; import * as React from 'react'; +import { IReactionDisposer, computed, reaction } from 'mobx'; +import { NumCast } from '../../../../fields/Types'; // creates an inline comment in a note when '>>' is typed. // the comment sits on the right side of the note and vertically aligns with its anchor in the text. @@ -10,8 +12,10 @@ import * as React from 'react'; export class DashDocCommentView { dom: HTMLDivElement; // container for label and value root: any; + node: any; constructor(node: any, view: any, getPos: any) { + this.node = node; this.dom = document.createElement('div'); this.dom.style.width = node.attrs.width; this.dom.style.height = node.attrs.height; @@ -32,10 +36,14 @@ export class DashDocCommentView { }; this.root = ReactDOM.createRoot(this.dom); - this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} docId={node.attrs.docId} />); + this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />); (this as any).dom = this.dom; } + setHeight = (hgt: number) => { + !this.node.attrs.reflow && DocServer.GetRefField(this.node.attrs.docId).then(doc => doc instanceof Doc && (this.dom.style.height = hgt + '')); + }; + destroy() { this.root.unmount(); } @@ -51,9 +59,15 @@ interface IDashDocCommentViewInternal { docId: string; view: any; getPos: any; + setHeight: (height: number) => void; } export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> { + _reactionDisposer: IReactionDisposer | undefined; + + @computed get _dashDoc() { + return DocServer.GetRefField(this.props.docId); + } constructor(props: any) { super(props); this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this); @@ -61,15 +75,32 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV this.onPointerUpCollapsed = this.onPointerUpCollapsed.bind(this); this.onPointerDownCollapsed = this.onPointerDownCollapsed.bind(this); } + componentDidMount(): void { + this._reactionDisposer?.(); + this._dashDoc.then( + doc => + doc instanceof Doc && + (this._reactionDisposer = reaction( + () => NumCast((doc as Doc)._height), + hgt => this.props.setHeight(hgt), + { + fireImmediately: true, + } + )) + ); + } + componentWillUnmount(): void { + this._reactionDisposer?.(); + } onPointerLeaveCollapsed(e: any) { - DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight()); + this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight()); e.preventDefault(); e.stopPropagation(); } onPointerEnterCollapsed(e: any) { - DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); + this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); e.preventDefault(); e.stopPropagation(); } @@ -82,7 +113,7 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true }); this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs setTimeout(() => { - expand && DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); + expand && this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (e) {} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 0b857794b..973f90501 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -68,6 +68,7 @@ import { RichTextRules } from './RichTextRules'; import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; import { CollectionView } from '../../collections/CollectionView'; +import { PaintButtonView } from './PaintButtonView'; // import * as applyDevTools from 'prosemirror-dev-tools'; @observer export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface { @@ -1410,6 +1411,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps dashField(node: any, view: any, getPos: any) { return new DashFieldView(node, view, getPos, self); }, + paintButton(node: any, view: any, getPos: any) { + return new PaintButtonView(node, view, getPos, self); + }, equation(node: any, view: any, getPos: any) { return new EquationView(node, view, getPos, self); }, @@ -1538,15 +1542,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this._downX = e.clientX; this._downY = e.clientY; this._downTime = Date.now(); - this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false; FormattedTextBoxComment.textBox = this; if (e.button === 0 && this._props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) { if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar, otherwise nested boxes will lose focus to outer boxes. e.stopPropagation(); // if the text box's content is active, then it consumes all down events document.addEventListener('pointerup', this.onSelectEnd); + (this.ProseRef?.children?.[0] as any).focus(); } } + this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false; if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { e.preventDefault(); } diff --git a/src/client/views/nodes/formattedText/PaintButtonView.tsx b/src/client/views/nodes/formattedText/PaintButtonView.tsx new file mode 100644 index 000000000..74423c772 --- /dev/null +++ b/src/client/views/nodes/formattedText/PaintButtonView.tsx @@ -0,0 +1,113 @@ +import { action, computed, IReactionDisposer, makeObservable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import * as ReactDOM from 'react-dom/client'; +import { Doc } from '../../../../fields/Doc'; +import { ObservableReactComponent } from '../../ObservableReactComponent'; +import './DashFieldView.scss'; +import { FormattedTextBox } from './FormattedTextBox'; +import { CollectionViewType } from '../../../documents/DocumentTypes'; +import { CollectionView } from '../../collections/CollectionView'; +import { StrCast } from '../../../../fields/Types'; + +export class PaintButtonView { + dom: HTMLDivElement; // container for label and value + root: any; + node: any; + tbox: FormattedTextBox; + + constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + this.node = node; + this.tbox = tbox; + this.dom = document.createElement('div'); + this.dom.style.width = node.attrs.width; + this.dom.style.height = node.attrs.height; + this.dom.style.position = 'relative'; + this.dom.style.display = 'inline-block'; + this.dom.onkeypress = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeydown = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeyup = function (e: any) { + e.stopPropagation(); + }; + this.dom.onmousedown = function (e: any) { + e.stopPropagation(); + }; + + this.root = ReactDOM.createRoot(this.dom); + this.root.render(<PaintButtonViewInternal node={node} getPos={getPos} width={node.attrs.width} height={node.attrs.height} tbox={tbox} />); + } + destroy() { + setTimeout(() => { + try { + this.root.unmount(); + } catch {} + }); + } + deselectNode() { + this.dom.classList.remove('ProseMirror-selectednode'); + } + selectNode() { + this.dom.classList.add('ProseMirror-selectednode'); + } +} + +interface IPaintButtonViewInternal { + tbox: FormattedTextBox; + width: number; + height: number; + node: any; + getPos: any; +} + +@observer +export class PaintButtonViewInternal extends ObservableReactComponent<IPaintButtonViewInternal> { + _reactionDisposer: IReactionDisposer | undefined; + _textBoxDoc: Doc; + + constructor(props: IPaintButtonViewInternal) { + super(props); + makeObservable(this); + this._textBoxDoc = this._props.tbox.Document; + } + + return100 = () => 100; + @computed get _checked() { + return this._props.tbox.Document.onClick ? true : false; + } + + onCheckClick = () => { + const textView = this._props.tbox.DocumentView?.(); + if (textView) { + const paintedField = 'layout_' + this._props.tbox.fieldKey + 'Painted'; + const layoutFieldKey = StrCast(textView.layoutDoc.layout_fieldKey, 'layout'); + if (textView.layoutDoc.onClick) { + textView.layoutDoc[paintedField] = undefined; + textView.layoutDoc.onClick = undefined; + } else { + textView.layoutDoc.type_collection = CollectionViewType.Freeform; + textView.dataDoc[paintedField] = CollectionView.LayoutString(this._props.tbox.fieldKey); + textView.layoutDoc.layout_fieldKey = paintedField; + textView.setToggleDetail(layoutFieldKey.replace('layout_', '').replace('layout', '')); + textView.layoutDoc.layout_fieldKey = layoutFieldKey; + } + } + }; + + render() { + return ( + <div + className="dashFieldView" + style={{ + width: this._props.width, + height: this._props.height, + pointerEvents: this._props.tbox._props.isSelected() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none', + }}> + <input type="checkbox" value="paint" checked={this._checked} onChange={e => this.onCheckClick()} /> + </div> + ); + } +} diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index be32a2c4a..8f7bc5282 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -4,8 +4,7 @@ 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 } from '../../../../fields/Types'; +import { NumCast, StrCast } from '../../../../fields/Types'; import { Utils } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; @@ -15,6 +14,7 @@ import { RichTextMenu } from './RichTextMenu'; import { schema } from './schema_rts'; import { CollectionView } from '../../collections/CollectionView'; import { CollectionViewType } from '../../../documents/DocumentTypes'; +import { ContextMenu } from '../../ContextMenu'; export class RichTextRules { public Document: Doc; @@ -69,23 +69,39 @@ export class RichTextRules { // ``` create code block textblockTypeInputRule(/^```$/, schema.nodes.code_block), - - new InputRule(new RegExp(/^\^@paint/), (state, match, start, end) => { - const { dataDoc, layoutDoc, fieldKey } = this.TextBox; - layoutDoc.type_collection = CollectionViewType.Freeform; - dataDoc.layout_painted = CollectionView.LayoutString('painted'); - const layoutFieldKey = layoutDoc.layout_fieldKey; - layoutDoc.layout_fieldKey = 'layout_painted'; - this.TextBox.DocumentView?.().setToggleDetail(); - layoutDoc.layout_fieldKey = layoutFieldKey; - dataDoc.paintFunc = ComputedField.MakeFunction(`toJavascriptString(this['${fieldKey}']?.Text)`); - const comment = '/* this is now a paint func */'; - const tr = state.tr - .deleteRange(start, end) - .insertText(comment) - .insert(start + comment.length, schema.nodes.code_block.create()); - return tr.setSelection(new TextSelection(tr.doc.resolve(start + comment.length + 2))); - }), + new InputRule( + new RegExp(/(^|\n)\^@paint/), // for code blocks '^' means the beginning of the block, not the line, so need to test for \n + (state, match, start, end) => { + const { dataDoc, layoutDoc, fieldKey } = this.TextBox; + layoutDoc.type_collection = CollectionViewType.Freeform; + const paintedField = 'layout_' + this.TextBox.fieldKey + 'Painted'; + dataDoc[paintedField] = CollectionView.LayoutString(this.TextBox.fieldKey); + const layoutFieldKey = StrCast(layoutDoc.layout_fieldKey); + layoutDoc.layout_fieldKey = paintedField; + this.TextBox.DocumentView?.().setToggleDetail(layoutFieldKey.replace('layout_', '').replace('layout', '')); + layoutDoc.layout_fieldKey = layoutFieldKey; + const comment = '/* enable as paint function '; + const endComment = ' */\n'; + const inCode = state.tr.selection.$anchor.node().type === schema.nodes.code_block; + if (inCode) { + const tr = state.tr + .deleteRange(start, end) + .insertText(comment) + .insert(start + comment.length, schema.nodes.paintButton.create()) + .insertText(endComment); + return tr.setSelection(new TextSelection(tr.doc.resolve(start + comment.length + endComment.length + 1))); + } else { + const tr = state.tr + .deleteRange(start, end) + .insertText(comment) + .insert(start + comment.length, schema.nodes.paintButton.create()) + .insertText(endComment) + .insert(start + comment.length + endComment.length + 1, schema.nodes.code_block.create()); + return tr.setSelection(new TextSelection(tr.doc.resolve(start + comment.length + endComment.length + 3))); + } + }, + { inCode: true } + ), // %<font-size> set the font size new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => { @@ -94,6 +110,32 @@ export class RichTextRules { }), //Create annotation to a field on the text document + new InputRule(new RegExp(/>::$/), (state, match, start, end) => { + const creator = (doc: Doc) => { + const textDoc = this.Document[DocData]; + const numInlines = NumCast(textDoc.inlineTextCount); + textDoc.inlineTextCount = numInlines + 1; + const node = (state.doc.resolve(start) as any).nodeAfter; + const newNode = schema.nodes.dashComment.create({ docId: doc[Id], reflow: false }); + const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: doc[Id], float: 'right' }); + const sm = state.storedMarks || undefined; + this.TextBox.EditorView?.dispatch( + node + ? this.TextBox.EditorView.state.tr + .insert(start, newNode) + .replaceRangeWith(start + 1, end + 2, dashDoc) + .insertText(' ', start + 2) + .setStoredMarks([...node.marks, ...(sm ? sm : [])]) + : this.TextBox.EditorView.state.tr + ); + }; + DocUtils.addDocumentCreatorMenuItems(creator, creator, 200, 200); + const cm = ContextMenu.Instance; + cm.displayMenu(200, 200, undefined, true); + + return null; + }), + //Create annotation to a field on the text document new InputRule(new RegExp(/>>$/), (state, match, start, end) => { const textDoc = this.Document[DocData]; const numInlines = NumCast(textDoc.inlineTextCount); @@ -117,7 +159,7 @@ export class RichTextRules { textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text textDoc[inlineFieldKey] = ''; // set a default value for the annotation const node = (state.doc.resolve(start) as any).nodeAfter; - const newNode = schema.nodes.dashComment.create({ docId: textDocInline[Id] }); + const newNode = schema.nodes.dashComment.create({ docId: textDocInline[Id], reflow: true }); const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: textDocInline[Id], float: 'right' }); const sm = state.storedMarks || undefined; const replaced = node @@ -265,49 +307,53 @@ export class RichTextRules { // [[fieldKey]] => show field // [[fieldKey=value]] => show field and also set its value // [[fieldKey:docTitle]] => show field of doc - new InputRule(new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => { - const fieldKey = match[1]; - const docTitle = match[3]?.replace(':', ''); - const value = match[2]?.substring(1); - const linkToDoc = (target: Doc) => { - const rstate = this.TextBox.EditorView?.state; - const selection = rstate?.selection.$from.pos; - if (rstate) { - this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); - } + new InputRule( + new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), + (state, match, start, end) => { + const fieldKey = match[1]; + const docTitle = match[3]?.replace(':', ''); + const value = match[2]?.substring(1); + const linkToDoc = (target: Doc) => { + const rstate = this.TextBox.EditorView?.state; + const selection = rstate?.selection.$from.pos; + if (rstate) { + 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, { link_relationship: '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) { - this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection)))); - } - }; - const getTitledDoc = (docTitle: string) => { - if (!DocServer.FindDocByTitle(docTitle)) { - Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true })); - } - const titledDoc = DocServer.FindDocByTitle(docTitle); - return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc; - }; - if (!fieldKey) { - if (docTitle) { - const target = getTitledDoc(docTitle); - if (target) { - setTimeout(() => linkToDoc(target)); - return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3); + const fstate = this.TextBox.EditorView?.state; + if (fstate && selection) { + this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection)))); + } + }; + const getTitledDoc = (docTitle: string) => { + if (!DocServer.FindDocByTitle(docTitle)) { + Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true })); + } + const titledDoc = DocServer.FindDocByTitle(docTitle); + return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc; + }; + if (!fieldKey) { + if (docTitle) { + const target = getTitledDoc(docTitle); + if (target) { + setTimeout(() => linkToDoc(target)); + return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3); + } } + return state.tr; } - return state.tr; - } - if (value !== '' && value !== undefined) { - const num = value.match(/^[0-9.]$/); - this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; - } - const target = getTitledDoc(docTitle); - const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false }); - return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true); - }), + if (value !== '' && value !== undefined) { + const num = value.match(/^[0-9.]$/); + this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; + } + const target = getTitledDoc(docTitle); + const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false }); + return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true); + }, + { inCode: true } + ), // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document // wiki:title diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 31f001b11..4706a97fa 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -179,6 +179,7 @@ export const nodes: { [index: string]: NodeSpec } = { dashComment: { attrs: { docId: { default: '' }, + reflow: { default: true }, }, inline: true, group: 'inline', @@ -275,6 +276,17 @@ export const nodes: { [index: string]: NodeSpec } = { }, }, + paintButton: { + inline: true, + attrs: {}, + group: 'inline', + draggable: false, + toDOM(node) { + const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` }; + return ['div', { ...node.attrs, ...attrs }]; + }, + }, + video: { inline: true, attrs: { |
