diff options
Diffstat (limited to 'src/client/views/nodes')
| -rw-r--r-- | src/client/views/nodes/DocumentView.scss | 1 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 17 | ||||
| -rw-r--r-- | src/client/views/nodes/button/FontIconBox.tsx | 216 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/DashDocView.tsx | 3 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 22 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/RichTextMenu.tsx | 194 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/marks_rts.ts | 6 |
7 files changed, 218 insertions, 241 deletions
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 7f164ca48..1ec7bf72a 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -175,6 +175,7 @@ position: absolute; bottom: 0; width: 100%; + overflow-y: scroll; transform-origin: bottom left; opacity: 0.1; transition: opacity 0.5s; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6e7590a78..b44b32832 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -25,7 +25,6 @@ import { InteractionUtils } from '../../util/InteractionUtils'; import { LinkManager } from '../../util/LinkManager'; import { Scripting } from '../../util/Scripting'; import { SelectionManager } from "../../util/SelectionManager"; -import { ColorScheme } from "../../util/SettingsManager"; import { SharingManager } from '../../util/SharingManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from "../../util/Transform"; @@ -50,6 +49,7 @@ import { ScriptingBox } from "./ScriptingBox"; import { PresBox } from './trails/PresBox'; import React = require("react"); import { IconProp } from "@fortawesome/fontawesome-svg-core"; +import { ColorScheme } from "../../util/SettingsManager"; const { Howl } = require('howler'); interface Window { @@ -973,19 +973,26 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ":caption"); @computed get innards() { TraceMobx(); + const ffscale = (this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1); const showTitle = this.ShowTitle?.split(":")[0]; const showTitleHover = this.ShowTitle?.includes(":hover"); const showCaption = !this.props.hideCaptions && this.Document._viewType !== CollectionViewType.Carousel ? StrCast(this.layoutDoc._showCaption) : undefined; const captionView = !showCaption ? (null) : <div className="documentView-captionWrapper" - style={{ pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined, }}> + style={{ + pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined, + minWidth: 50 * ffscale, + maxHeight: `max(100%, ${20 * ffscale}px)` + }}> <FormattedTextBox {...OmitKeys(this.props, ['children']).omit} yPadding={10} xPadding={10} fieldKey={showCaption} - fontSize={Math.min(32, 12 * this.props.ScreenToLocalTransform().Scale)} + fontSize={12 * Math.max(1, 2 * ffscale / 3)} styleProvider={this.captionStyleProvider} dontRegisterView={true} + noSidebar={true} + dontScale={true} isContentActive={this.isContentActive} onClick={this.onClickFunc} /> @@ -1055,9 +1062,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps render() { TraceMobx(); const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : 0) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString - const highlightColor = (Doc.UserDoc().colorScheme === ColorScheme.Dark ? - ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] : - ["transparent", "#4476F7", "#4476F7", "yellow", "magenta", "cyan", "orange"])[highlightIndex]; + const highlightColor = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "yellow", "magenta", "cyan", "orange"][highlightIndex]; const highlightStyle = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"][highlightIndex]; const excludeTypes = !this.props.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON]; let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear; diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 9d1ef937f..511df8786 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -5,7 +5,7 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { ColorState, SketchPicker } from 'react-color'; -import { Doc, StrListCast } from '../../../../fields/Doc'; +import { Doc, StrListCast, WidthSym, HeightSym } from '../../../../fields/Doc'; import { InkTool } from '../../../../fields/InkField'; import { createSchema, makeInterface } from '../../../../fields/Schema'; import { ScriptField } from '../../../../fields/ScriptField'; @@ -14,7 +14,6 @@ import { WebField } from '../../../../fields/URLField'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Scripting } from "../../../util/Scripting"; import { SelectionManager } from '../../../util/SelectionManager'; -import { ColorScheme } from '../../../util/SettingsManager'; import { UndoManager, undoBatch } from '../../../util/UndoManager'; import { CollectionViewType } from '../../collections/CollectionView'; import { ContextMenu } from '../../ContextMenu'; @@ -114,7 +113,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon // Script for checking the outcome of the toggle const checkScript: string = StrCast(this.rootDoc.script) + "(0, true)"; - const checkResult: number = ScriptField.MakeScript(checkScript)?.script.run().result; + const checkResult: number = ScriptField.MakeScript(checkScript)?.script.run().result || 0; if (numBtnType === NumButtonType.Slider) { @@ -159,7 +158,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon <div className={`menuButton ${this.type} ${numBtnType}`} > - <div className={`button`} onClick={action((e) => setValue(checkResult - 1))}> + <div className={`button`} onClick={action((e) => setValue(Number(checkResult) - 1))}> <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"minus"} /> </div> <div @@ -178,7 +177,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon onChange={action((e) => setValue(Number(e.target.value)))} /> </div> - <div className={`button`} onClick={action((e) => setValue(checkResult + 1))}> + <div className={`button`} onClick={action((e) => setValue(Number(checkResult) + 1))}> <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"plus"} /> </div> @@ -262,8 +261,8 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon } noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking]; } else if (script === 'setFont') { - const selected = SelectionManager.Docs().lastElement(); - text = StrCast((selected?.type === DocumentType.RTF ? selected : Doc.UserDoc())._fontFamily); + const editorView = RichTextMenu.Instance?.TextView?.EditorView; + text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]; } @@ -319,29 +318,35 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon ); } + @observable colorPickerClosed: boolean = true; + @computed get colorScript() { + const script = StrCast(this.rootDoc.script); + return ScriptField.MakeScript(script + '(colValue, checkResult)', { colValue: "string", checkResult: "boolean" }); + } + + colorPicker = (curColor: string) => { + const change = (value: ColorState) => { + const s = this.colorScript; + s && undoBatch(() => s.script.run({ colValue: Utils.colorString(value), checkResult: false }).result)(); + }; + const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', + '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', + '#FFFFFF', '#f1efeb', "transparent"]; + return <SketchPicker + onChange={change} + color={curColor} + presetColors={presets} /> + } /** * Color button */ @computed get colorButton() { - const active: string = StrCast(this.rootDoc.dropDownOpen); const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + const curColor = this.colorScript?.script.run({ colValue: undefined, checkResult: true }).result ?? "transparent"; - const script: string = StrCast(this.rootDoc.script); - const scriptCheck: string = script + "(undefined, true)"; - const boolResult = ScriptField.MakeScript(scriptCheck)?.script.run().result; - - const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', - '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', - '#FFFFFF', '#f1efeb', "transparent"]; - - const colorBox = (func: (color: ColorState) => void) => <SketchPicker - disableAlpha={false} - onChange={func} - color={boolResult ? boolResult : "#FFFFFF"} - presetColors={colorOptions} />; const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : - <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}> + <div className="fontIconBox-label" style={{ color, backgroundColor, position: "absolute" }}> {this.label} </div>; @@ -350,30 +355,27 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}> <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" /> </div>; - - const click = (value: ColorState) => { - const s = ScriptField.MakeScript(script + '("' + Utils.colorString(value) + '", false)'); - s && undoBatch(() => s.script.run().result)(); - }; + setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView return ( - <div className={`menuButton ${this.type} ${active}`} + <div className={`menuButton ${this.type} ${this.colorPickerClosed}`} style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} - onClick={() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen} + onClick={action(() => this.colorPickerClosed = !this.colorPickerClosed)} onPointerDown={e => e.stopPropagation()}> <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} /> - <div className="colorButton-color" style={{ backgroundColor: boolResult ? boolResult : "#FFFFFF" }} /> + <div className="colorButton-color" style={{ backgroundColor: curColor }} /> {label} {/* {dropdownCaret} */} - {this.rootDoc.dropDownOpen ? + {this.colorPickerClosed ? (null) : <div> <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()} onClick={e => e.stopPropagation()}> - {colorBox(click)} + {this.colorPicker(curColor)} </div> - <div className="dropbox-background" onClick={(e) => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} /> - </div> - : null} + <div className="dropbox-background" onClick={action((e) => { + e.stopPropagation(); this.colorPickerClosed = true; + })} /> + </div>} </div> ); } @@ -388,7 +390,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon // Button label const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : - <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}> + <div className="fontIconBox-label" style={{ color, backgroundColor, position: "absolute" }}> {this.label} </div>; @@ -407,7 +409,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon } else { return ( <div className={`menuButton ${this.type}`} - style={{ opacity: 1, backgroundColor: backgroundColor, color: color }}> + style={{ opacity: 1, backgroundColor, color }}> <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} /> {label} </div> @@ -463,7 +465,6 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon render() { const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const dark: boolean = Doc.UserDoc().colorScheme === ColorScheme.Dark; const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}> @@ -573,15 +574,9 @@ Scripting.addGlobal(function setView(view: string) { Scripting.addGlobal(function setBackgroundColor(color?: string, checkResult?: boolean) { const selected = SelectionManager.Docs().lastElement(); if (checkResult) { - if (selected) { - return selected._backgroundColor; - } else { - return "#FFFFFF"; - } + return selected?._backgroundColor ?? "transparent"; } - if (selected?.type === DocumentType.INK) selected.fillColor = color; if (selected) selected._backgroundColor = color; - Doc.UserDoc()._fontColor = color; }); // toggle: Set overlay status of selected document @@ -599,7 +594,7 @@ Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) { const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; if (checkResult && selected) { if (NumCast(selected.Document.z) >= 1) return Colors.MEDIUM_BLUE; - else return "transparent"; + return "transparent"; } selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("[FontIconBox.tsx] toggleOverlay failed"); }); @@ -620,16 +615,18 @@ Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) { Scripting.addGlobal(function setFont(font: string, checkResult?: boolean) { SelectionManager.Docs().map(doc => doc._fontFamily = font); const editorView = RichTextMenu.Instance.TextView?.EditorView; - editorView?.state && RichTextMenu.Instance.setFontFamily(font, editorView); - Doc.UserDoc()._fontFamily = font; - return Doc.UserDoc()._fontFamily; + if (checkResult) { + return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); + } + if (editorView) RichTextMenu.Instance.setFontFamily(font); + else Doc.UserDoc().fontFamily = font; }); Scripting.addGlobal(function getActiveTextInfo(info: "family" | "size" | "color" | "highlight") { const editorView = RichTextMenu.Instance.TextView?.EditorView; const style = editorView?.state && RichTextMenu.Instance.getActiveFontStylesOnSelection(); switch (info) { - case "family": return style?.activeColors[0]; + case "family": return style?.activeFamilies[0]; case "size": return style?.activeSizes[0]; case "color": return style?.activeColors[0]; case "highlight": return style?.activeHighlights[0]; @@ -646,7 +643,7 @@ Scripting.addGlobal(function setAlignment(align: "left" | "right" | "center", ch active = StrCast(Doc.UserDoc().textAlign); } if (active === align) return Colors.MEDIUM_BLUE; - else return "transparent"; + return "transparent"; } SelectionManager.Docs().map(doc => doc.textAlign = align); switch (align) { @@ -670,38 +667,25 @@ Scripting.addGlobal(function setBulletList(mapStyle: "bullet" | "decimal", check if (checkResult) { const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle(); if (active === mapStyle) return Colors.MEDIUM_BLUE; - else return "transparent"; + return "transparent"; } if (editorView) { const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle(); - if (active === mapStyle) { - editorView?.state && RichTextMenu.Instance.changeListType(editorView.state.schema.nodes.ordered_list.create({ mapStyle: "" })); - } else { - editorView?.state && RichTextMenu.Instance.changeListType(editorView.state.schema.nodes.ordered_list.create({ mapStyle: mapStyle })); - } + editorView?.state && RichTextMenu.Instance.changeListType( + editorView.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle })); } }); // toggle: Set overlay status of selected document Scripting.addGlobal(function setFontColor(color?: string, checkResult?: boolean) { - const selected = SelectionManager.Docs().lastElement(); const editorView = RichTextMenu.Instance.TextView?.EditorView; if (checkResult) { - if (selected) { - return selected._fontColor; - } else { - return Doc.UserDoc()._fontColor; - } + return editorView ? RichTextMenu.Instance.fontColor : Doc.UserDoc().fontColor; } - if (selected) { - selected._fontColor = color; - if (color) { - editorView?.state && RichTextMenu.Instance.setColor(color, editorView, editorView?.dispatch); - } - } - Doc.UserDoc()._fontColor = color; + if (editorView) color && RichTextMenu.Instance.setColor(color, editorView, editorView?.dispatch); + else Doc.UserDoc().fontColor = color; }); // toggle: Set overlay status of selected document @@ -710,11 +694,7 @@ Scripting.addGlobal(function setFontHighlight(color?: string, checkResult?: bool const editorView = RichTextMenu.Instance.TextView?.EditorView; if (checkResult) { - if (selected) { - return selected._fontHighlight; - } else { - return Doc.UserDoc()._fontHighlight; - } + return (selected ?? Doc.UserDoc())._fontHighlight; } if (selected) { selected._fontColor = color; @@ -725,62 +705,43 @@ Scripting.addGlobal(function setFontHighlight(color?: string, checkResult?: bool Doc.UserDoc()._fontHighlight = color; }); - - // toggle: Set overlay status of selected document -Scripting.addGlobal(function setFontSize(size: string, checkResult?: boolean) { +Scripting.addGlobal(function setFontSize(size: string | number, checkResult?: boolean) { + if (typeof size === "number") size = size.toString(); + if (size && Number(size).toString() === size) size += "px"; + const editorView = RichTextMenu.Instance.TextView?.EditorView; if (checkResult) { - const size: number = parseInt(StrCast(Doc.UserDoc()._fontSize), 10); - return size; + return (editorView ? RichTextMenu.Instance.fontSize : StrCast(Doc.UserDoc().fontSize, "10px")).replace("px", ""); } - const editorView = RichTextMenu.Instance.TextView?.EditorView; - editorView?.state && RichTextMenu.Instance.setFontSize(Number(size), editorView); - Doc.UserDoc()._fontSize = size + "px"; + if (editorView) RichTextMenu.Instance.setFontSize(size); + else Doc.UserDoc()._fontSize = size; }); Scripting.addGlobal(function toggleBold(checkResult?: boolean) { + const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { - const editorView = RichTextMenu.Instance.TextView?.EditorView; - if (editorView) { - const bold: boolean = editorView?.state && RichTextMenu.Instance.getBoldStatus(); - if (bold) return Colors.MEDIUM_BLUE; - else return "transparent"; - } - else return "transparent"; + return (editorView ? RichTextMenu.Instance.bold : Doc.UserDoc().fontWeight === "bold") ? Colors.MEDIUM_BLUE : "transparent"; } - const editorView = RichTextMenu.Instance.TextView?.EditorView; - if (editorView) { - editorView?.state && RichTextMenu.Instance.toggleBold(editorView, true); - } - SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.RTF).map(doc => doc.bold = !doc.bold); - Doc.UserDoc().bold = !Doc.UserDoc().bold; - return Doc.UserDoc().bold; + if (editorView) RichTextMenu.Instance?.toggleBold(); + else Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === "bold" ? undefined : "bold"; }); Scripting.addGlobal(function toggleUnderline(checkResult?: boolean) { + const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { - return "transparent"; + return (editorView ? RichTextMenu.Instance.underline : Doc.UserDoc().textDecoration === "underline") ? Colors.MEDIUM_BLUE : "transparent"; } - const editorView = RichTextMenu.Instance.TextView?.EditorView; - if (editorView) { - editorView?.state && RichTextMenu.Instance.toggleUnderline(editorView, true); - } - SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.RTF).map(doc => doc.underline = !doc.underline); - Doc.UserDoc().underline = !Doc.UserDoc().underline; - return Doc.UserDoc().underline; + if (editorView) RichTextMenu.Instance?.toggleUnderline(); + else Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === "underline" ? undefined : "underline"; }); Scripting.addGlobal(function toggleItalic(checkResult?: boolean) { + const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { - return "transparent"; + return (editorView ? RichTextMenu.Instance.italics : Doc.UserDoc().fontStyle === "italics") ? Colors.MEDIUM_BLUE : "transparent"; } - const editorView = RichTextMenu.Instance.TextView?.EditorView; - if (editorView) { - editorView?.state && RichTextMenu.Instance.toggleItalic(editorView, true); - } - SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.RTF).map(doc => doc.italic = !doc.italic); - Doc.UserDoc().italic = !Doc.UserDoc().italic; - return Doc.UserDoc().italic; + if (editorView) RichTextMenu.Instance?.toggleItalics(); + else Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === "italics" ? undefined : "italics"; }); @@ -794,24 +755,23 @@ Scripting.addGlobal(function toggleItalic(checkResult?: boolean) { Scripting.addGlobal(function setActiveInkTool(tool: string, checkResult?: boolean) { if (checkResult) { - if (Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance.InkShape === "" || GestureOverlay.Instance.InkShape === tool) return Colors.MEDIUM_BLUE; - else return "transparent"; + return (Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance?.InkShape === "" || GestureOverlay.Instance?.InkShape === tool) ? + Colors.MEDIUM_BLUE : "transparent"; } - if (tool === "circle") { - Doc.UserDoc().activeInkTool = "pen"; - GestureOverlay.Instance.InkShape = tool; - } else if (tool === "square") { - Doc.UserDoc().activeInkTool = "pen"; - GestureOverlay.Instance.InkShape = tool; - } else if (tool === "line") { - Doc.UserDoc().activeInkTool = "pen"; - GestureOverlay.Instance.InkShape = tool; - } else if (tool) { - if (Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance.InkShape === "" || GestureOverlay.Instance.InkShape === tool) { - GestureOverlay.Instance.InkShape = ""; + if (["circle", "square", "line"].includes(tool)) { + if (GestureOverlay.Instance.InkShape === tool) { + Doc.UserDoc().activeInkTool = InkTool.None; + GestureOverlay.Instance.InkShape = InkTool.None; + } else { + Doc.UserDoc().activeInkTool = InkTool.Pen; + GestureOverlay.Instance.InkShape = tool; + } + } else if (tool) { // pen + if (Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance.InkShape) { Doc.UserDoc().activeInkTool = InkTool.None; } else { Doc.UserDoc().activeInkTool = tool; + GestureOverlay.Instance.InkShape = ""; } } else { Doc.UserDoc().activeInkTool = InkTool.None; @@ -823,7 +783,7 @@ Scripting.addGlobal(function setFillColor(color?: string, checkResult?: boolean) const selected = SelectionManager.Docs().lastElement(); if (checkResult) { if (selected?.type === DocumentType.INK) { - return StrCast(selected._backgroundColor); + return StrCast(selected.fillColor); } return ActiveFillColor(); } diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index e519de1c5..149836e93 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -12,6 +12,7 @@ import { FormattedTextBox } from "./FormattedTextBox"; import React = require("react"); import * as ReactDOM from 'react-dom'; import { observer } from "mobx-react"; +import { ColorScheme } from "../../../util/SettingsManager"; export class DashDocView { _fieldWrapper: HTMLSpanElement; // container for label and value @@ -20,7 +21,7 @@ export class DashDocView { this._fieldWrapper = document.createElement("span"); this._fieldWrapper.style.position = "relative"; this._fieldWrapper.style.textIndent = "0"; - this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray")); + this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimGray" : "lightGray")); this._fieldWrapper.style.width = node.attrs.width; this._fieldWrapper.style.height = node.attrs.height; this._fieldWrapper.style.display = node.attrs.hidden ? "none" : "inline-block"; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ebd509669..7afa54d5f 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -279,8 +279,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); if ((!curTemp && !curProto) || curText || json.includes("dash")) { // 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)) { - !curText && tx.storedMarks?.filter(m => m.type.name === "pFontSize").map(m => Doc.UserDoc().fontSize = this.layoutDoc._fontSize = (m.attrs.fontSize + "px")); - !curText && tx.storedMarks?.filter(m => m.type.name === "pFontFamily").map(m => Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily); this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText); this.dataDoc[this.props.fieldKey + "-noTemplate"] = true;//(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); @@ -833,7 +831,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp 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 }) => { - autoHeight && this.props.setHeight(marginsHeight + Math.max(sidebarHeight, textHeight)); + autoHeight && this.props.setHeight?.(marginsHeight + Math.max(sidebarHeight, textHeight)); }, { fireImmediately: true }); this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks newLinks => { @@ -894,11 +892,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false }); this._disposers.selected = reaction(() => this.props.isSelected(), - action((selected) => { + action(selected => { if (RichTextMenu.Instance?.view === this._editorView && !selected) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined); } - })); + if (this._editorView && selected) { + RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); + } + }), { fireImmediately: true }); if (!this.props.dontRegisterView) { this._disposers.record = reaction(() => this._recording, @@ -1168,7 +1169,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp selectOnLoad && this._editorView!.focus(); // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. if (this._editorView && !this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) { - this._editorView.state.storedMarks = [...(this._editorView.state.storedMarks ?? []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })]; + this._editorView.state.storedMarks = [...(this._editorView.state.storedMarks ?? []), + 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: StrCast(Doc.UserDoc().fontFamily) })] : []), + ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: StrCast(Doc.UserDoc().fontSize, "") })] : []), + ...(Doc.UserDoc().fontWeight === "bold" ? [schema.mark(schema.marks.strong)] : [])]; } } @@ -1596,7 +1604,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp background: this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor), color: this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color), fontSize: this.props.fontSize ? this.props.fontSize : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize), - fontWeight: Cast(this.layoutDoc._fontWeight, "number", null), + fontWeight: Cast(this.layoutDoc._fontWeight, "string", null) as any, fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"), pointerEvents: interactive ? undefined : "none", }} diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 9904a7939..bd05af977 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -1,8 +1,7 @@ import React = require("react"); -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; -import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { action, IReactionDisposer, observable, reaction, runInAction, computed } from "mobx"; import { observer } from "mobx-react"; import { lift, wrapIn } from "prosemirror-commands"; import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model"; @@ -10,10 +9,7 @@ import { wrapInList } from "prosemirror-schema-list"; import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { Doc } from "../../../../fields/Doc"; -import { DarkPastelSchemaPalette, PastelSchemaPalette } from '../../../../fields/SchemaHeaderField'; import { Cast, StrCast } from "../../../../fields/Types"; -import { TraceMobx } from "../../../../fields/util"; -import { unimplementedFunction, Utils } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { LinkManager } from "../../../util/LinkManager"; import { SelectionManager } from "../../../util/SelectionManager"; @@ -29,7 +25,7 @@ const { toggleMark } = require("prosemirror-commands"); @observer export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { - static Instance: RichTextMenu; + @observable static Instance: RichTextMenu; public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable private _linkToRef = React.createRef<HTMLInputElement>(); @@ -39,22 +35,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { public _brushMap: Map<string, Set<Mark>> = new Map(); @observable private collapsed: boolean = false; - @observable private boldActive: boolean = false; - @observable private italicsActive: boolean = false; - @observable private underlineActive: boolean = false; - @observable private strikethroughActive: boolean = false; - @observable private subscriptActive: boolean = false; - @observable private superscriptActive: boolean = false; - - @observable private activeFontSize: string = ""; - @observable private activeFontFamily: string = ""; + @observable private _boldActive: boolean = false; + @observable private _italicsActive: boolean = false; + @observable private _underlineActive: boolean = false; + @observable private _strikethroughActive: boolean = false; + @observable private _subscriptActive: boolean = false; + @observable private _superscriptActive: boolean = false; + + @observable private _activeFontSize: string = "13px"; + @observable private _activeFontFamily: string = ""; @observable private activeListType: string = ""; @observable private activeAlignment: string = "left"; @observable private brushMarks: Set<Mark> = new Set(); @observable private showBrushDropdown: boolean = false; - @observable private activeFontColor: string = "black"; + @observable private _activeFontColor: string = "black"; @observable private showColorDropdown: boolean = false; @observable private activeHighlightColor: string = "transparent"; @@ -67,10 +63,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { _delayHide = false; constructor(props: Readonly<{}>) { super(props); - RichTextMenu.Instance = this; - this._canFade = false; - //this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]); - runInAction(() => this.Pinned = true); + runInAction(() => { + RichTextMenu.Instance = this; + this._canFade = false; + //this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]); + this.Pinned = true; + }); } componentDidMount() { @@ -81,6 +79,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this._reaction?.(); } + @computed get bold() { return this._boldActive; } + @computed get underline() { return this._underlineActive; } + @computed get italics() { return this._italicsActive; } + @computed get strikeThrough() { return this._strikethroughActive; } + @computed get fontColor() { return this._activeFontColor; } + @computed get fontFamily() { return this._activeFontFamily; } + @computed get fontSize() { return this._activeFontSize; } + public delayHide = () => this._delayHide = true; @action @@ -110,10 +116,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this.activeListType = this.getActiveListStyle(); this.activeAlignment = this.getActiveAlignment(); - this.activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various"; - this.activeFontSize = !activeSizes.length ? "13px" : activeSizes.length === 1 ? String(activeSizes[0]) : "..."; - this.activeFontColor = !activeColors.length ? "black" : activeColors.length === 1 ? String(activeColors[0]) : "..."; - this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length === 1 ? String(activeHighlights[0]) : "..."; + this._activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various"; + this._activeFontSize = !activeSizes.length ? "13px" : activeSizes[0]; + this._activeFontColor = !activeColors.length ? "black" : activeColors.length > 0 ? String(activeColors[0]) : "..."; + this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length > 0 ? String(activeHighlights[0]) : "..."; // update link in current selection this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle)); @@ -125,7 +131,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { if (node?.type === schema.nodes.ordered_list) { let attrs = node.attrs; if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, fontFamily: mark.attrs.family }; - if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, fontSize: `${mark.attrs.fontSize}px` }; + if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, fontSize: mark.attrs.fontSize }; if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, fontColor: mark.attrs.color }; const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema); dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from)))); @@ -142,17 +148,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } } - getBoldStatus() { - if (this.view && this.TextView.props.isSelected(true)) { - const path = (this.view.state.selection.$from as any).path; - for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) { - if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) { - return path[i].attrs.strong; - } - } - } - } - // finds font sizes and families in selection getActiveAlignment() { if (this.view && this.TextView.props.isSelected(true)) { @@ -193,25 +188,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { if (this.TextView.props.isSelected(true)) { const state = this.view.state; const pos = this.view.state.selection.$from; - const ref_node = this.reference_node(pos); - if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { - const marks = Array.from(ref_node.marks); - marks.push(...(this.view.state.storedMarks as any)); - marks.forEach(m => { - m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); - m.type === state.schema.marks.pFontColor && activeColors.push(m.attrs.color); - m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "px"); - m.type === state.schema.marks.marker && activeHighlights.push(String(m.attrs.highlight)); + const marks: Mark<any>[] = [...(state.storedMarks ?? [])]; + if (state.selection.empty) { + const ref_node = this.reference_node(pos); + marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : [])); + } else { + state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { + node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark)); }); } - !activeFamilies.length && (activeFamilies.push(StrCast(this.TextView.layoutDoc._fontFamily, StrCast(Doc.UserDoc().fontFamily)))); - !activeSizes.length && (activeSizes.push(StrCast(this.TextView.layoutDoc._fontSize, StrCast(Doc.UserDoc().fontSize)))); - !activeColors.length && (activeColors.push(StrCast(this.TextView.layoutDoc.color, StrCast(Doc.UserDoc().fontColor)))); + marks.forEach(m => { + m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); + m.type === state.schema.marks.pFontColor && activeColors.push(m.attrs.color); + m.type === state.schema.marks.pFontSize && activeSizes.push(m.attrs.fontSize); + m.type === state.schema.marks.marker && activeHighlights.push(String(m.attrs.highlight)); + }); } - !activeFamilies.length && (activeFamilies.push(StrCast(Doc.UserDoc().fontFamily))); - !activeSizes.length && (activeSizes.push(StrCast(Doc.UserDoc().fontSize))); - !activeColors.length && (activeColors.push(StrCast(Doc.UserDoc().fontColor, "black"))); - !activeHighlights.length && (activeHighlights.push(StrCast(Doc.UserDoc().fontHighlight, ""))); return { activeFamilies, activeSizes, activeColors, activeHighlights }; } @@ -251,11 +243,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { return []; } activeMarks = markGroup.filter(mark_type => { - if (mark_type === state.schema.marks.pFontSize) { - return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); - } + // if (mark_type === state.schema.marks.pFontSize) { + // return mark.isINSet + // ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); + // } const mark = state.schema.mark(mark_type); - return ref_node.marks.includes(mark); + return mark.isInSet(ref_node.marks); }); } } @@ -270,56 +263,66 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { setActiveMarkButtons(activeMarks: MarkType[] | undefined) { if (!activeMarks) return; - this.boldActive = false; - this.italicsActive = false; - this.underlineActive = false; - this.strikethroughActive = false; - this.subscriptActive = false; - this.superscriptActive = false; + this._boldActive = false; + this._italicsActive = false; + this._underlineActive = false; + this._strikethroughActive = false; + this._subscriptActive = false; + this._superscriptActive = false; activeMarks.forEach(mark => { switch (mark.name) { - case "strong": this.boldActive = true; break; - case "em": this.italicsActive = true; break; - case "underline": this.underlineActive = true; break; - case "strikethrough": this.strikethroughActive = true; break; - case "subscript": this.subscriptActive = true; break; - case "superscript": this.superscriptActive = true; break; + case "strong": this._boldActive = true; break; + case "em": this._italicsActive = true; break; + case "underline": this._underlineActive = true; break; + case "strikethrough": this._strikethroughActive = true; break; + case "subscript": this._subscriptActive = true; break; + case "superscript": this._superscriptActive = true; break; } }); } - toggleBold = (view: EditorView, forceBool?: boolean) => { - const mark = view.state.schema.mark(view.state.schema.marks.strong, { strong: forceBool }); - this.setMark(mark, view.state, view.dispatch, false); - view.focus(); + toggleBold = () => { + if (this.view) { + const mark = this.view.state.schema.mark(this.view.state.schema.marks.strong); + this.setMark(mark, this.view.state, this.view.dispatch, false); + this.view.focus(); + } } - toggleUnderline = (view: EditorView, forceBool?: boolean) => { - const mark = view.state.schema.mark(view.state.schema.marks.underline, { underline: forceBool }); - this.setMark(mark, view.state, view.dispatch, false); - view.focus(); + toggleUnderline = () => { + if (this.view) { + const mark = this.view.state.schema.mark(this.view.state.schema.marks.underline); + this.setMark(mark, this.view.state, this.view.dispatch, false); + this.view.focus(); + } } - toggleItalic = (view: EditorView, forceBool?: boolean) => { - const mark = view.state.schema.mark(view.state.schema.marks.em, { em: forceBool }); - this.setMark(mark, view.state, view.dispatch, false); - view.focus(); + toggleItalics = () => { + if (this.view) { + const mark = this.view.state.schema.mark(this.view.state.schema.marks.em); + this.setMark(mark, this.view.state, this.view.dispatch, false); + this.view.focus(); + } } - setFontSize = (size: number, view: EditorView) => { - const fmark = view.state.schema.marks.pFontSize.create({ fontSize: size }); - this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true); - view.focus(); - this.updateMenu(view, undefined, this.props); + setFontSize = (fontSize: string) => { + if (this.view) { + const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize }); + this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true); + this.view.focus(); + this.updateMenu(this.view, undefined, this.props); + } } - setFontFamily = (family: string, view: EditorView) => { - const fmark = view.state.schema.marks.pFontFamily.create({ family: family }); - this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true); - view.focus(); - this.updateMenu(view, undefined, this.props); + setFontFamily = (family: string) => { + if (this.view) { + 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(); + this.updateMenu(this.view, undefined, this.props); + } } setHighlight(color: String, view: EditorView, dispatch: any) { @@ -330,13 +333,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } setColor(color: String, view: EditorView, dispatch: any) { - const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color }); - if (view.state.selection.empty) { - dispatch(view.state.tr.addStoredMark(colorMark)); - return false; + if (this.view) { + const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color }); + this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true); + view.focus(); + this.updateMenu(this.view, undefined, this.props); } - this.setMark(colorMark, view.state, dispatch, true); - view.focus(); } // TODO: remove doesn't work diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 655ee7e44..6103a28d6 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -61,13 +61,13 @@ export const marks: { [index: string]: MarkSpec } = { /** FONT SIZES */ pFontSize: { - attrs: { fontSize: { default: 10 } }, + attrs: { fontSize: { default: "10px" } }, parseDOM: [{ tag: "span", getAttrs(dom: any) { - return { fontSize: dom.style.fontSize ? Number(dom.style.fontSize.replace("px", "")) : "" }; + return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : "" }; } }], - toDOM: (node) => node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize}px;` }] : ['span', 0] + toDOM: (node) => node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize};` }] : ['span', 0] }, /* FONTS */ |
