diff options
Diffstat (limited to 'src/client/util')
| -rw-r--r-- | src/client/util/DictationManager.ts | 7 | ||||
| -rw-r--r-- | src/client/util/DragManager.ts | 6 | ||||
| -rw-r--r-- | src/client/util/DropConverter.ts | 30 | ||||
| -rw-r--r-- | src/client/util/Import & Export/DirectoryImportBox.tsx | 8 | ||||
| -rw-r--r-- | src/client/util/Import & Export/ImageUtils.ts | 12 | ||||
| -rw-r--r-- | src/client/util/InteractionUtils.tsx | 5 | ||||
| -rw-r--r-- | src/client/util/ProseMirrorEditorView.tsx | 74 | ||||
| -rw-r--r-- | src/client/util/RichTextMenu.tsx | 88 | ||||
| -rw-r--r-- | src/client/util/RichTextRules.ts | 108 | ||||
| -rw-r--r-- | src/client/util/RichTextSchema.tsx | 198 | ||||
| -rw-r--r-- | src/client/util/request-image-size.js | 2 | ||||
| -rw-r--r-- | src/client/util/type_decls.d | 1 |
12 files changed, 283 insertions, 256 deletions
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 3d8f2d234..3394cb93d 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -325,15 +325,14 @@ export namespace DictationManager { ["open fields", { action: (target: DocumentView) => { - const kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); + const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 }); target.props.addDocTab(kvp, target.props.DataDoc, "onRight"); } }], ["new outline", { action: (target: DocumentView) => { - const newBox = Docs.Create.TextDocument({ width: 400, height: 200, title: "My Outline" }); - newBox.autoHeight = true; + const newBox = Docs.Create.TextDocument("", { _width: 400, _height: 200, title: "My Outline", _autoHeight: true }); const proto = newBox.proto!; const prompt = "Press alt + r to start dictating here..."; const head = 3; @@ -379,7 +378,7 @@ export namespace DictationManager { expression: /view as (freeform|stacking|masonry|schema|tree)/g, action: (target: DocumentView, matches: RegExpExecArray) => { const mode = CollectionViewType.valueOf(matches[1]); - mode && (target.props.Document.viewType = mode); + mode && (target.props.Document._viewType = mode); }, restrictTo: [DocumentType.COL] } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index df2f5fe3c..c05a2de96 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -94,6 +94,7 @@ export namespace DragManager { readonly x: number, readonly y: number, readonly complete: DragCompleteEvent, + readonly shiftKey: boolean, readonly altKey: boolean, readonly metaKey: boolean, readonly ctrlKey: boolean @@ -205,7 +206,7 @@ export namespace DragManager { // drag a button template and drop a new button export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) { const finishDrag = (e: DragCompleteEvent) => { - const bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: title }); + const bd = Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: title }); bd.onClick = ScriptField.MakeScript(script); params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); initialize && initialize(bd); @@ -340,7 +341,7 @@ export namespace DragManager { if (dragData instanceof DocumentDragData) { dragData.userDropAction = e.ctrlKey ? "alias" : undefined; } - if (e.shiftKey && CollectionDockingView.Instance) { + if (e.shiftKey && CollectionDockingView.Instance && dragData.droppedDocuments.length === 1) { AbortDrag(); finishDrag?.(new DragCompleteEvent(true, dragData)); CollectionDockingView.Instance.StartOtherDrag({ @@ -409,6 +410,7 @@ export namespace DragManager { x: e.x, y: e.y, complete: complete, + shiftKey: e.shiftKey, altKey: e.altKey, metaKey: e.metaKey, ctrlKey: e.ctrlKey diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index ff0e19347..d0f1d86cb 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -1,26 +1,31 @@ import { DragManager } from "./DragManager"; -import { CollectionViewType } from "../views/collections/CollectionView"; import { Doc, DocListCast } from "../../new_fields/Doc"; import { DocumentType } from "../documents/DocumentTypes"; import { ObjectField } from "../../new_fields/ObjectField"; import { StrCast } from "../../new_fields/Types"; import { Docs } from "../documents/Documents"; -import { ScriptField } from "../../new_fields/ScriptField"; +import { ScriptField, ComputedField } from "../../new_fields/ScriptField"; +import { RichTextField } from "../../new_fields/RichTextField"; +import { ImageField } from "../../new_fields/URLField"; -export function makeTemplate(doc: Doc, suppressTitle = false): boolean { - const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc; +export function makeTemplate(doc: Doc): boolean { + const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)![0]; const fieldKey = layout.replace("fieldKey={'", "").replace(/'}$/, ""); const docs = DocListCast(layoutDoc[fieldKey]); let any = false; docs.forEach(d => { if (!StrCast(d.title).startsWith("-")) { - any = true; - Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc), suppressTitle); - } else if (d.type === DocumentType.COL) { + any = Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)) || any; + } else if (d.type === DocumentType.COL || d.data instanceof RichTextField) { any = makeTemplate(d) || any; } }); + if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) { + if (!StrCast(layoutDoc.title).startsWith("-")) { + any = Doc.MakeMetadataFieldTemplate(layoutDoc, Doc.GetProto(layoutDoc)); + } + } return any; } export function convertDropDataToButtons(data: DragManager.DocumentDragData) { @@ -28,13 +33,14 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) { let dbox = doc; // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant if (!doc.onDragStart && !doc.onClick && !doc.isButtonBar) { - const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc; - if (layoutDoc.type === DocumentType.COL) { - layoutDoc.isTemplateDoc = makeTemplate(layoutDoc); + const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; + if (layoutDoc.type === DocumentType.COL || layoutDoc.type === DocumentType.TEXT || layoutDoc.type === DocumentType.IMG) { + makeTemplate(layoutDoc); } else { - layoutDoc.isTemplateDoc = (layoutDoc.type === DocumentType.TEXT || layoutDoc.layout instanceof Doc) && !data.userDropAction; + (layoutDoc.layout instanceof Doc) && !data.userDropAction; } - dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: layoutDoc.isTemplateDoc ? "font" : "bolt" }); + layoutDoc.isTemplateDoc = true; + dbox = Docs.Create.FontIconDocument({ _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: layoutDoc.isTemplateDoc ? "font" : "bolt" }); dbox.dragFactory = layoutDoc; dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined; dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 5b5bffd8c..071015193 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -116,13 +116,13 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps> formData.append(Utils.GenerateGuid(), file); }); - collector.push(...(await Networking.PostFormDataToServer("/upload", formData))); + collector.push(...(await Networking.PostFormDataToServer("/uploadFormData", formData))); runInAction(() => this.completed += batch.length); }); await Promise.all(uploads.map(async ({ name, type, clientAccessPath, exifData }) => { const path = Utils.prepend(clientAccessPath); - const document = await Docs.Get.DocumentFromType(type, path, { width: 300, title: name }); + const document = await Docs.Get.DocumentFromType(type, path, { _width: 300, title: name }); const { data, error } = exifData; if (document) { Doc.GetProto(document).exif = error || Docs.Get.DocumentHierarchyFromJson(data); @@ -145,8 +145,8 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps> const offset: number = this.persistent ? (height === 0 ? 0 : height + 30) : 0; const options: DocumentOptions = { title: `Import of ${directory}`, - width: 1105, - height: 500, + _width: 1105, + _height: 500, x: NumCast(doc.x), y: NumCast(doc.y) + offset }; diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts index 6a9486f83..ff909cc6b 100644 --- a/src/client/util/Import & Export/ImageUtils.ts +++ b/src/client/util/Import & Export/ImageUtils.ts @@ -14,9 +14,17 @@ export namespace ImageUtils { return false; } const source = field.url.href; - const response = await Networking.PostToServer("/inspectImage", { source }); - const { error, data } = response.exifData; + const { + contentSize, + nativeWidth, + nativeHeight, + exifData: { error, data } + } = await Networking.PostToServer("/inspectImage", { source }); document.exif = error || Docs.Get.DocumentHierarchyFromJson(data); + const proto = Doc.GetProto(document); + proto["data-nativeWidth"] = nativeWidth; + proto["data-nativeHeight"] = nativeHeight; + proto.contentSize = contentSize; return data !== undefined; }; diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 1fe95474c..7194feb2e 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -63,7 +63,7 @@ export namespace InteractionUtils { export function GetMyTargetTouches(mte: InteractionUtils.MultiTouchEvent<React.TouchEvent | TouchEvent>, prevPoints: Map<number, React.Touch>, ignorePen: boolean): React.Touch[] { const myTouches = new Array<React.Touch>(); for (const pt of mte.touches) { - if (!ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) { + if (!ignorePen || ((pt as any).radiusX > 1 && (pt as any).radiusY > 1)) { for (const tPt of mte.targetTouches) { if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) { if (pt && prevPoints.has(pt.identifier)) { @@ -73,6 +73,9 @@ export namespace InteractionUtils { } } } + // if (mte.touches.length !== myTouches.length) { + // throw Error("opo") + // } return myTouches; } diff --git a/src/client/util/ProseMirrorEditorView.tsx b/src/client/util/ProseMirrorEditorView.tsx deleted file mode 100644 index b42adfbb4..000000000 --- a/src/client/util/ProseMirrorEditorView.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React from "react"; -import { EditorView } from "prosemirror-view"; -import { EditorState } from "prosemirror-state"; - -export interface ProseMirrorEditorViewProps { - /* EditorState instance to use. */ - editorState: EditorState; - /* Called when EditorView produces new EditorState. */ - onEditorState: (editorState: EditorState) => any; -} - -/** - * This wraps ProseMirror's EditorView into React component. - * This code was found on https://discuss.prosemirror.net/t/using-with-react/904 - */ -export class ProseMirrorEditorView extends React.Component<ProseMirrorEditorViewProps> { - - private _editorView?: EditorView; - - _createEditorView = (element: HTMLDivElement | null) => { - if (element !== null) { - this._editorView = new EditorView(element, { - state: this.props.editorState, - dispatchTransaction: this.dispatchTransaction, - }); - } - } - - dispatchTransaction = (tx: any) => { - // In case EditorView makes any modification to a state we funnel those - // modifications up to the parent and apply to the EditorView itself. - const editorState = this.props.editorState.apply(tx); - if (this._editorView) { - this._editorView.updateState(editorState); - } - this.props.onEditorState(editorState); - } - - focus() { - if (this._editorView) { - this._editorView.focus(); - } - } - - componentWillReceiveProps(nextProps: { editorState: EditorState<any>; }) { - // In case we receive new EditorState through props — we apply it to the - // EditorView instance. - if (this._editorView) { - if (nextProps.editorState !== this.props.editorState) { - this._editorView.updateState(nextProps.editorState); - } - } - } - - componentWillUnmount() { - if (this._editorView) { - this._editorView.destroy(); - } - } - - shouldComponentUpdate() { - // Note that EditorView manages its DOM itself so we'd ratrher don't mess - // with it. - return false; - } - - render() { - // Render just an empty div which is then used as a container for an - // EditorView instance. - return ( - <div ref={this._createEditorView} /> - ); - } -}
\ No newline at end of file diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx index f070589ae..e07efe056 100644 --- a/src/client/util/RichTextMenu.tsx +++ b/src/client/util/RichTextMenu.tsx @@ -73,7 +73,7 @@ export default class RichTextMenu extends AntimodeMenu { this.fontSizeOptions = [ { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize }, { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "8pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9pt", command: this.changeFontSize }, { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize }, { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize }, { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize }, @@ -312,22 +312,22 @@ export default class RichTextMenu extends AntimodeMenu { } return ( - <button className={"antimodeMenu-button" + (isActive ? " active" : "")} title={title} onPointerDown={onClick}> + <button className={"antimodeMenu-button" + (isActive ? " active" : "")} key={title} title={title} onPointerDown={onClick}> <FontAwesomeIcon icon={faIcon as IconProp} size="lg" /> </button> ); } - createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[]): JSX.Element { + createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element { const items = options.map(({ title, label, hidden, style }) => { if (hidden) { return label === activeOption ? - <option value={label} title={title} style={style ? style : {}} selected hidden>{label}</option> : - <option value={label} title={title} style={style ? style : {}} hidden>{label}</option>; + <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> : + <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>; } return label === activeOption ? - <option value={label} title={title} style={style ? style : {}} selected>{label}</option> : - <option value={label} title={title} style={style ? style : {}}>{label}</option>; + <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> : + <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>; }); const self = this; @@ -340,19 +340,19 @@ export default class RichTextMenu extends AntimodeMenu { } }); } - return <select onChange={onChange}>{items}</select>; + return <select onChange={onChange} key={key}>{items}</select>; } - createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[]): JSX.Element { + createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element { const items = options.map(({ title, label, hidden, style }) => { if (hidden) { return label === activeOption ? - <option value={label} title={title} style={style ? style : {}} selected hidden>{label}</option> : - <option value={label} title={title} style={style ? style : {}} hidden>{label}</option>; + <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> : + <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>; } return label === activeOption ? - <option value={label} title={title} style={style ? style : {}} selected>{label}</option> : - <option value={label} title={title} style={style ? style : {}}>{label}</option>; + <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> : + <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>; }); const self = this; @@ -363,31 +363,15 @@ export default class RichTextMenu extends AntimodeMenu { } }); } - return <select onChange={e => onChange(e.target.value)}>{items}</select>; + return <select onChange={e => onChange(e.target.value)} key={key}>{items}</select>; } changeFontSize = (mark: Mark, view: EditorView) => { - const size = mark.attrs.fontSize; - if (this.editorProps) { - const ruleProvider = this.editorProps.ruleProvider; - const heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleSize_" + heading] = size; - } - } - this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: size }), view.state, view.dispatch); + this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch); } changeFontFamily = (mark: Mark, view: EditorView) => { - const fontName = mark.attrs.family; - if (this.editorProps) { - const ruleProvider = this.editorProps.ruleProvider; - const heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleFont_" + heading] = fontName; - } - } - this.setMark(view.state.schema.marks.pFontFamily.create({ family: fontName }), view.state, view.dispatch); + this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch); } // TODO: remove doesn't work @@ -472,7 +456,7 @@ export default class RichTextMenu extends AntimodeMenu { </div>; return ( - <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} /> + <ButtonDropdown view={this.view} key={"brush dropdown"} button={button} dropdownContent={dropdownContent} /> ); } @@ -535,21 +519,21 @@ export default class RichTextMenu extends AntimodeMenu { </button>; const dropdownContent = - <div className="dropdown"> + <div className="dropdown" > <p>Change font color:</p> <div className="color-wrapper"> {this.fontColors.map(color => { if (color) { return this.activeFontColor === color ? - <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> : - <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>; + <button className="color-button active" key={"active" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> : + <button className="color-button" key={"other" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>; } })} </div> </div>; return ( - <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} /> + <ButtonDropdown view={this.view} key={"color dropdown"} button={button} dropdownContent={dropdownContent} /> ); } @@ -582,7 +566,7 @@ export default class RichTextMenu extends AntimodeMenu { } const button = - <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onHighlightClick}> + <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" onPointerDown={onHighlightClick}> <FontAwesomeIcon icon="highlighter" size="lg" /> <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div> </button>; @@ -594,15 +578,15 @@ export default class RichTextMenu extends AntimodeMenu { {this.highlightColors.map(color => { if (color) { return this.activeHighlightColor === color ? - <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> : - <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>; + <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> : + <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>; } })} </div> </div>; return ( - <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} /> + <ButtonDropdown view={this.view} key={"highlighter"} button={button} dropdownContent={dropdownContent} /> ); } @@ -635,7 +619,7 @@ export default class RichTextMenu extends AntimodeMenu { </div>; return ( - <ButtonDropdown view={this.view} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} /> + <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} /> ); } @@ -773,7 +757,7 @@ export default class RichTextMenu extends AntimodeMenu { render() { - const row1 = <div className="antimodeMenu-row">{[ + const row1 = <div className="antimodeMenu-row" key="row1">{[ this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)), this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)), this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)), @@ -787,14 +771,14 @@ export default class RichTextMenu extends AntimodeMenu { this.createButton("indent", "Summarize", undefined, this.insertSummarizer), ]}</div>; - const row2 = <div className="antimodeMenu-row row-2"> - <div> - {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions), - this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions), - this.createNodesDropdown(this.activeListType, this.listTypeOptions)]} + const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2"> + <div key="row"> + {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"), + this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"), + this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes")]} </div> - <div> - <button className="antimodeMenu-button" title="Pin menu" onClick={this.toggleMenuPin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}> + <div key="button"> + <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}> <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /> </button> {this.getDragger()} @@ -864,12 +848,12 @@ class ButtonDropdown extends React.Component<ButtonDropdownProps> { </button> : <> {this.props.button} - <button className="dropdown-button antimodeMenu-button" onPointerDown={this.onDropdownClick}> + <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}> <FontAwesomeIcon icon="caret-down" size="sm" /> </button> </>} - {this.showDropdown ? this.props.dropdownContent : <></>} + {this.showDropdown ? this.props.dropdownContent : (null)} </div> ); } diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index e20abb04c..8411cc6ee 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -2,14 +2,16 @@ import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from import { schema } from "./RichTextSchema"; import { wrappingInputRule } from "./prosemirrorPatches"; import { NodeSelection, TextSelection } from "prosemirror-state"; -import { NumCast, Cast } from "../../new_fields/Types"; -import { Doc } from "../../new_fields/Doc"; +import { StrCast, Cast, NumCast } from "../../new_fields/Types"; +import { Doc, DataSym } from "../../new_fields/Doc"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { Docs, DocUtils } from "../documents/Documents"; import { Id } from "../../new_fields/FieldSymbols"; import { DocServer } from "../DocServer"; import { returnFalse, Utils } from "../../Utils"; import RichTextMenu from "./RichTextMenu"; +import { RichTextField } from "../../new_fields/RichTextField"; +import { ComputedField } from "../../new_fields/ScriptField"; export const inpRules = { rules: [ @@ -64,35 +66,69 @@ export const inpRules = { // set the font size using #<font-size> new InputRule( - new RegExp(/^%([0-9]+)\s$/), + new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => { const size = Number(match[1]); - const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider; - const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading); - if (ruleProvider && heading) { - (Cast(FormattedTextBox.FocusedBox!.props.Document, Doc) as Doc).heading = size; - return state.tr.deleteRange(start, end); - } return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size })); }), - // make current selection a hyperlink portal (assumes % was used to initiate an EnteringStyle mode) + // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document new InputRule( - new RegExp(/@$/), + new RegExp(/\[\[([a-zA-Z_ \-0-9]*)(:[a-zA-Z_ \-0-9]+)?\]\]$/), (state, match, start, end) => { - if (state.selection.to === state.selection.from || !(schema as any).EnteringStyle) return null; - - const value = state.doc.textBetween(start, end); - if (value) { - DocServer.GetRefField(value).then(docx => { - const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500, }, value); - DocUtils.Publish(target, value, returnFalse, returnFalse); + if (!match[2]) { + const docId = match[1]; + DocServer.GetRefField(docId).then(docx => { + const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: docId, _width: 500, _height: 500, _LODdisable: true, }, docId); + DocUtils.Publish(target, docId, returnFalse, returnFalse); DocUtils.MakeLink({ doc: (schema as any).Document }, { doc: target }, "portal link", ""); }); - const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value, targetId: value }); - return state.tr.addMark(start, end, link); + const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docId), location: "onRight", title: docId, targetId: docId }); + return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link); } - return state.tr; + const fieldView = state.schema.nodes.dashField.create({ fieldKey: match[2]?.substring(1), docid: match[1] }); + return state.tr.deleteRange(start, end).insert(start, fieldView); + }), + // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document + new InputRule( + new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(:[a-zA-Z_ \-0-9]+)?\}\}$/), + (state, match, start, end) => { + const docId = match[1]; + DocServer.GetRefField(docId).then(docx => { + if (!(docx instanceof Doc && docx)) { + const docx = Docs.Create.FreeformDocument([], { title: docId, _width: 500, _height: 500, _LODdisable: true }, docId); + DocUtils.Publish(docx, docId, returnFalse, returnFalse); + } + }); + const node = (state.doc.resolve(start) as any).nodeAfter; + const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid: docId, float: "right", fieldKey: match[2]?.substring(1), alias: Utils.GenerateGuid() }); + const sm = state.storedMarks || undefined; + return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + }), + new InputRule( + new RegExp(/##$/), + (state, match, start, end) => { + const schemaDoc = Doc.GetDataDoc((schema as any).Document); + const textDoc = Doc.GetProto(Cast(schemaDoc[DataSym], Doc, null)!); + 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, _autoHeight: true, fontSize: 9, title: "inline comment" }); + textDocInline.title = inlineFieldKey; // give the annotation its own title + textDocInline.customTitle = 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(this.${inlineFieldKey})`, { this: Doc.name }); + 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 dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: textDocInline[Id], float: "right" }); + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).insertText(" ", start + 2).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + return replaced; }), // stop using active style new InputRule( @@ -175,12 +211,6 @@ export const inpRules = { (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks || undefined; - const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider; - const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleAlign_" + heading] = "center"; - return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; - } const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); @@ -191,12 +221,6 @@ export const inpRules = { (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks || undefined; - const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider; - const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleAlign_" + heading] = "left"; - return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; - } const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); @@ -207,29 +231,11 @@ export const inpRules = { (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks || undefined; - const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider; - const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleAlign_" + heading] = "right"; - return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; - } const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); }), new InputRule( - new RegExp(/%#$/), - (state, match, start, end) => { - const target = Docs.Create.TextDocument({ width: 75, height: 35, backgroundColor: "yellow", annotationOn: FormattedTextBox.FocusedBox!.dataDoc, autoHeight: true, fontSize: 9, title: "inline comment" }); - const node = (state.doc.resolve(start) as any).nodeAfter; - const newNode = schema.nodes.dashComment.create({ docid: target[Id] }); - const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: target[Id], float: "right" }); - const sm = state.storedMarks || undefined; - const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).insertText(" ", start + 2).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : - state.tr; - return replaced;//.setSelection(new NodeSelection(replaced.doc.resolve(end))); - }), - new InputRule( new RegExp(/%\(/), (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 1f70cafc4..287a1049b 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -1,4 +1,4 @@ -import { reaction, IReactionDisposer } from "mobx"; +import { reaction, IReactionDisposer, observable, runInAction } from "mobx"; import { baseKeymap, toggleMark } from "prosemirror-commands"; import { redo, undo } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; @@ -8,7 +8,7 @@ import { EditorState, NodeSelection, TextSelection, Plugin } from "prosemirror-s import { StepMap } from "prosemirror-transform"; import { EditorView } from "prosemirror-view"; import * as ReactDOM from 'react-dom'; -import { Doc, WidthSym, HeightSym } from "../../new_fields/Doc"; +import { Doc, WidthSym, HeightSym, DataSym, Field } from "../../new_fields/Doc"; import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { DocumentView } from "../views/nodes/DocumentView"; @@ -16,8 +16,12 @@ import { DocumentManager } from "./DocumentManager"; import ParagraphNodeSpec from "./ParagraphNodeSpec"; import { Transform } from "./Transform"; import React = require("react"); -import { BoolCast, NumCast, Cast } from "../../new_fields/Types"; +import { BoolCast, NumCast, StrCast } from "../../new_fields/Types"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; +import { ObjectField } from "../../new_fields/ObjectField"; +import { ComputedField } from "../../new_fields/ScriptField"; +import { observer } from "mobx-react"; +import { Id } from "../../new_fields/FieldSymbols"; const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -165,7 +169,23 @@ export const nodes: { [index: string]: NodeSpec } = { float: { default: "right" }, location: { default: "onRight" }, hidden: { default: false }, + fieldKey: { default: "" }, docid: { default: "" }, + alias: { default: "" } + }, + group: "inline", + draggable: false, + toDOM(node) { + const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` }; + return ["div", { ...node.attrs, ...attrs }]; + } + }, + + dashField: { + inline: true, + attrs: { + fieldKey: { default: "" }, + docid: { default: "" } }, group: "inline", draggable: false, @@ -306,7 +326,7 @@ export const marks: { [index: string]: MarkSpec } = { } }], toDOM(node: any) { - return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', { style: 'color: black' }]; + return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]; } }, @@ -669,7 +689,7 @@ export class DashDocCommentView { if (target) { const expand = target.hidden; const tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true }); - view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs + view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs setTimeout(() => { expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))); } catch (e) { } @@ -678,7 +698,7 @@ export class DashDocCommentView { e.stopPropagation(); }; this._collapsed.onpointerenter = (e: any) => { - DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); + DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); e.preventDefault(); e.stopPropagation(); }; @@ -703,7 +723,7 @@ export class DashDocView { const { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer); return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale); } - contentScaling = () => NumCast(this._dashDoc!.nativeWidth) > 0 && !this._dashDoc!.ignoreAspect ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!.nativeWidth) : 1; + contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 && !this._dashDoc!.ignoreAspect ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1; outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { this._textBox = tbox; @@ -721,12 +741,6 @@ export class DashDocView { this._dashSpan.style.height = node.attrs.height; this._dashSpan.style.position = "absolute"; this._dashSpan.style.display = "inline-block"; - const removeDoc = () => { - const pos = getPos(); - const ns = new NodeSelection(view.state.doc.resolve(pos)); - view.dispatch(view.state.tr.setSelection(ns).deleteSelection()); - return true; - }; this._dashSpan.onpointerleave = () => { const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid); if (ele) { @@ -739,47 +753,26 @@ export class DashDocView { (ele as HTMLDivElement).style.backgroundColor = "orange"; } }; - DocServer.GetRefField(node.attrs.docid).then(async dashDoc => { - if (dashDoc instanceof Doc) { - self._dashDoc = dashDoc; - dashDoc.hideSidebar = true; - if (node.attrs.width !== dashDoc.width + "px" || node.attrs.height !== dashDoc.height + "px") { - try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made - view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc.width + "px", height: dashDoc.height + "px" })); - } catch (e) { - console.log(e); + const removeDoc = () => { + const pos = getPos(); + const ns = new NodeSelection(view.state.doc.resolve(pos)); + view.dispatch(view.state.tr.setSelection(ns).deleteSelection()); + return true; + }; + const alias = node.attrs.alias; + + const docid = node.attrs.docid || tbox.props.DataDoc?.[Id] || tbox.dataDoc?.[Id]; + DocServer.GetRefField(docid + alias).then(async dashDoc => { + if (!(dashDoc instanceof Doc)) { + alias && DocServer.GetRefField(docid).then(async dashDocBase => { + if (dashDocBase instanceof Doc) { + const aliasedDoc = Doc.MakeDelegate(dashDocBase, docid + alias); + aliasedDoc.layoutKey = "layout_" + node.attrs.fieldKey; + self.doRender(aliasedDoc, removeDoc, node, view, getPos); } - } - this._reactionDisposer && this._reactionDisposer(); - this._reactionDisposer = reaction(() => dashDoc[HeightSym]() + dashDoc[WidthSym](), () => { - this._dashSpan.style.height = this._outer.style.height = dashDoc[HeightSym]() + "px"; - this._dashSpan.style.width = this._outer.style.width = dashDoc[WidthSym]() + "px"; }); - ReactDOM.render(<DocumentView - Document={dashDoc} - LibraryPath={tbox.props.LibraryPath} - fitToBox={BoolCast(dashDoc.fitToBox)} - addDocument={returnFalse} - removeDocument={removeDoc} - ruleProvider={undefined} - ScreenToLocalTransform={this.getDocTransform} - addDocTab={self._textBox.props.addDocTab} - pinToPres={returnFalse} - renderDepth={1} - PanelWidth={self._dashDoc[WidthSym]} - PanelHeight={self._dashDoc[HeightSym]} - focus={self.outerFocus} - backgroundColor={returnEmptyString} - parentActive={returnFalse} - whenActiveChanged={returnFalse} - bringToFront={emptyFunction} - zoomToScale={emptyFunction} - getScale={returnOne} - dontRegisterView={false} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - ContentScaling={this.contentScaling} - />, this._dashSpan); + } else { + self.doRender(dashDoc, removeDoc, node, view, getPos); } }); const self = this; @@ -795,11 +788,110 @@ export class DashDocView { this._outer.appendChild(this._dashSpan); (this as any).dom = this._outer; } + doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) { + this._dashDoc = dashDoc; + dashDoc._hideSidebar = true; + if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") { + try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made + view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" })); + } catch (e) { + console.log(e); + } + } + const self = this; + const finalLayout = Doc.expandTemplateLayout(dashDoc, !Doc.AreProtosEqual(this._textBox.dataDoc, this._textBox.Document) ? this._textBox.dataDoc : undefined); + if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0); + else { + const layoutKey = StrCast(finalLayout.layoutKey); + const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1]; + if (finalLayout !== dashDoc && finalKey) { + const finalLayoutField = finalLayout[finalKey]; + if (finalLayoutField instanceof ObjectField) { + finalLayout._textTemplate = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name }); + } + } + this._reactionDisposer && this._reactionDisposer(); + this._reactionDisposer = reaction(() => [finalLayout[WidthSym](), finalLayout[HeightSym]()], (dim) => { + this._dashSpan.style.width = this._outer.style.width = dim[0] + "px"; + this._dashSpan.style.height = this._outer.style.height = dim[1] + "px"; + }, { fireImmediately: true }); + ReactDOM.render(<DocumentView + Document={finalLayout} + DataDoc={!node.attrs.docid ? this._textBox.dataDoc : undefined} + LibraryPath={this._textBox.props.LibraryPath} + fitToBox={BoolCast(dashDoc._fitToBox)} + addDocument={returnFalse} + removeDocument={removeDoc} + ScreenToLocalTransform={this.getDocTransform} + addDocTab={this._textBox.props.addDocTab} + pinToPres={returnFalse} + renderDepth={1} + PanelWidth={finalLayout[WidthSym]} + PanelHeight={finalLayout[HeightSym]} + focus={this.outerFocus} + backgroundColor={returnEmptyString} + parentActive={returnFalse} + whenActiveChanged={returnFalse} + bringToFront={emptyFunction} + zoomToScale={emptyFunction} + getScale={returnOne} + dontRegisterView={false} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + ContentScaling={this.contentScaling} + />, this._dashSpan); + } + } destroy() { this._reactionDisposer && this._reactionDisposer(); } } + +export class DashFieldView { + _fieldWrapper: HTMLDivElement; + _labelSpan: HTMLSpanElement; + _fieldSpan: HTMLSpanElement; + _reactionDisposer: IReactionDisposer | undefined; + _textBoxDoc: Doc; + @observable _dashDoc: Doc | undefined; + + constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + this._textBoxDoc = tbox.props.Document; + this._fieldWrapper = document.createElement("div"); + this._fieldWrapper.style.width = node.attrs.width; + this._fieldWrapper.style.height = node.attrs.height; + this._fieldWrapper.style.position = "relative"; + this._fieldWrapper.style.display = "inline"; + + this._fieldSpan = document.createElement("span"); + this._fieldSpan.style.position = "relative"; + this._fieldSpan.style.display = "inline"; + + this._labelSpan = document.createElement("span"); + this._labelSpan.style.position = "relative"; + this._labelSpan.style.display = "inline"; + this._labelSpan.style.fontWeight = "bold"; + this._labelSpan.style.fontSize = "larger"; + this._labelSpan.innerHTML = `${node.attrs.fieldKey}: `; + if (node.attrs.docid) { + const self = this; + DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && runInAction(() => self._dashDoc = dashDoc)); + } else { + this._dashDoc = tbox.props.DataDoc || tbox.dataDoc; + } + this._reactionDisposer?.(); + this._reactionDisposer = reaction(() => this._dashDoc?.[node.attrs.fieldKey], fval => this._fieldSpan.innerHTML = Field.toString(fval as Field), { fireImmediately: true }); + + this._fieldWrapper.appendChild(this._labelSpan); + this._fieldWrapper.appendChild(this._fieldSpan); + (this as any).dom = this._fieldWrapper; + } + destroy() { + this._reactionDisposer?.(); + } +} + export class OrderedListView { update(node: any) { return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update @@ -964,7 +1056,7 @@ export class SummaryView { view.dispatch(view.state.tr. setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed) replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it - setNodeMarkup(getPos(), undefined, attrs)); // update the attrs + setNodeMarkup(getPos(), undefined, attrs)); // update the attrs e.preventDefault(); e.stopPropagation(); this._collapsed.className = this.className(visible); diff --git a/src/client/util/request-image-size.js b/src/client/util/request-image-size.js index 27605d167..beb030635 100644 --- a/src/client/util/request-image-size.js +++ b/src/client/util/request-image-size.js @@ -38,7 +38,7 @@ module.exports = function requestImageSize(options) { return reject(new HttpError(res.statusCode, res.statusMessage)); } - let buffer = new Buffer([]); + let buffer = new Buffer.from([]); let size; let imageSizeError; diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d index 622e10960..127f7b798 100644 --- a/src/client/util/type_decls.d +++ b/src/client/util/type_decls.d @@ -131,6 +131,7 @@ interface Promise<T> { declare const Update: unique symbol; declare const Self: unique symbol; declare const SelfProxy: unique symbol; +declare const DataSym: unique symbol; declare const HandleUpdate: unique symbol; declare const Id: unique symbol; declare const OnUpdate: unique symbol; |
