diff options
| author | Nathan-SR <144961007+Nathan-SR@users.noreply.github.com> | 2025-03-04 04:32:50 -0500 | 
|---|---|---|
| committer | Nathan-SR <144961007+Nathan-SR@users.noreply.github.com> | 2025-03-04 04:32:50 -0500 | 
| commit | 95abdada5a275fc258fa72781f7f3c40c0b306ea (patch) | |
| tree | 6d729cebe0937ae81108005de9895b5398d1f475 /src/client/views/nodes/formattedText/RichTextMenu.tsx | |
| parent | 0a8f3739cf5c30852f18751a4c05d81e0dabe928 (diff) | |
| parent | 215ad40efa2e343e290d18bffbc55884829f1a0d (diff) | |
Merge branch 'master' of https://github.com/brown-dash/Dash-Web into Merge
Diffstat (limited to 'src/client/views/nodes/formattedText/RichTextMenu.tsx')
| -rw-r--r-- | src/client/views/nodes/formattedText/RichTextMenu.tsx | 123 | 
1 files changed, 74 insertions, 49 deletions
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 88e2e4248..758b4035e 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -1,6 +1,6 @@  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';  import { Tooltip } from '@mui/material'; -import { action, computed, IReactionDisposer, makeObservable, observable, runInAction } from 'mobx'; +import { action, computed, makeObservable, observable, runInAction } from 'mobx';  import { observer } from 'mobx-react';  import { lift, toggleMark, wrapIn } from 'prosemirror-commands';  import { Mark, MarkType } from 'prosemirror-model'; @@ -32,7 +32,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {      public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable      private _linkToRef = React.createRef<HTMLInputElement>(); -    layoutDoc: Doc | undefined; +    dataDoc: Doc | undefined;      @observable public view?: EditorView & { TextView?: FormattedTextBox } = undefined;      public editorProps: FieldViewProps | AntimodeMenuProps | undefined; @@ -41,7 +41,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {      @observable private collapsed: boolean = false;      @observable private _noLinkActive: boolean = false;      @observable private _boldActive: boolean = false; -    @observable private _italicsActive: boolean = false; +    @observable private _italicActive: boolean = false;      @observable private _underlineActive: boolean = false;      @observable private _strikethroughActive: boolean = false;      @observable private _subscriptActive: boolean = false; @@ -49,6 +49,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {      @observable private _activeFontSize: string = '13px';      @observable private _activeFontFamily: string = ''; +    @observable private _activeFitBox: boolean = false;      @observable private _activeListType: string = '';      @observable private _activeAlignment: string = 'left'; @@ -64,18 +65,21 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {      @observable private currentLink: string | undefined = '';      @observable private showLinkDropdown: boolean = false; -    _reaction: IReactionDisposer | undefined;      constructor(props: AntimodeMenuProps) {          super(props);          makeObservable(this);          runInAction(() => {              RichTextMenu._instance.menu = this; -            this.updateMenu(undefined, undefined, props, this.layoutDoc); +            this.updateMenu(undefined, undefined, props, this.dataDoc);              this._canFade = false;              this.Pinned = true;          });      } +    @computed get RootSelected() { +        return this.TextView?._props.rootSelected?.() || this.TextView?._props.isContentActive(); +    } +      @computed get noAutoLink() {          return this._noLinkActive;      } @@ -85,8 +89,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {      @computed get underline() {          return this._underlineActive;      } -    @computed get italics() { -        return this._italicsActive; +    @computed get italic() { +        return this._italicActive;      }      @computed get strikeThrough() {          return this._strikethroughActive; @@ -97,6 +101,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {      @computed get fontHighlight() {          return this._activeHighlightColor;      } +    @computed get fitBox() { +        return this._activeFitBox; +    }      @computed get fontFamily() {          return this._activeFontFamily;      } @@ -110,26 +117,16 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {          return this._activeAlignment;      }      @computed get textVcenter() { -        return BoolCast(this.layoutDoc?._layout_centered); -    } -    _disposer: IReactionDisposer | undefined; -    componentDidMount() { -        // this._disposer = reaction( -        //     () => DocumentView.Selected().slice(), -        //     () => this.updateMenu(undefined, undefined, undefined, undefined) -        // ); -    } -    componentWillUnmount() { -        this._disposer?.(); +        return BoolCast(this.dataDoc?._layout_centered, BoolCast(Doc.UserDoc().layout_centered));      }      @action -    public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: FormattedTextBoxProps | AntimodeMenuProps | undefined, layoutDoc: Doc | undefined) { +    public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: FormattedTextBoxProps | AntimodeMenuProps | undefined, dataDoc: Doc | undefined) {          if (this._linkToRef.current?.getBoundingClientRect().width) {              return;          }          this.view = view; -        this.layoutDoc = layoutDoc; +        this.dataDoc = dataDoc;          props && (this.editorProps = props);          // Don't do anything if the document/selection didn't change @@ -143,14 +140,19 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {          const { activeSizes } = active;          const { activeColors } = active;          const { activeHighlights } = active; -        const refDoc = DocumentView.Selected().lastElement()?.layoutDoc ?? Doc.UserDoc(); +        const refDoc = DocumentView.Selected().lastElement()?.dataDoc ?? Doc.UserDoc();          const refField = (pfx => (pfx ? pfx + '_' : ''))(DocumentView.Selected().lastElement()?.LayoutFieldKey);          const refVal = (field: string, dflt: string) => StrCast(refDoc[refField + field], StrCast(Doc.UserDoc()[field], dflt));          this._activeListType = this.getActiveListStyle();          this._activeAlignment = this.getActiveAlignment(); -        this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, refVal('fontFamily', 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; -        this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, refVal('fontSize', '10px')) : activeSizes[0]; +        this._activeFitBox = BoolCast(refDoc[refField + 'fitBox'], BoolCast(Doc.UserDoc().fitBox)); +        this._activeFontFamily = !activeFamilies.length +            ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(this.dataDoc?.[Doc.LayoutFieldKey(this.dataDoc) + '_fontFamily'], refVal('fontFamily', 'Arial'))) +            : activeFamilies.length === 1 +              ? String(activeFamilies[0]) +              : 'various'; +        this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(this.dataDoc?.[Doc.LayoutFieldKey(this.dataDoc) + '_fontSize'], refVal('fontSize', '10px'))) : activeSizes[0];          this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, refVal('fontColor', 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';          this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...'; @@ -177,13 +179,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {                      toggleMark(mark.type, mark.attrs)(state, dispatch);                  }              } -            //    this.updateMenu(this.view, undefined, undefined, this.layoutDoc);          } +        this.setActiveMarkButtons(this.getActiveMarksOnSelection());      };      // finds font sizes and families in selection      getActiveAlignment = () => { -        if (this.view && this.TextView?._props.rootSelected?.()) { +        if (this.view && this.RootSelected) {              const from = this.view.state.selection.$from;              for (let i = from.depth; i >= 0; i--) {                  const node = from.node(i); @@ -191,8 +193,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {                      return node.attrs.align || 'left';                  }              } +        } else if (this.dataDoc) { +            return StrCast(this.dataDoc.text_align) || 'left';          } -        return 'left'; +        return StrCast(Doc.UserDoc().textAlign) || 'left';      };      // finds font sizes and families in selection @@ -216,7 +220,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {          const activeSizes = new Set<string>();          const activeColors = new Set<string>();          const activeHighlights = new Set<string>(); -        if (this.view && this.TextView?._props.rootSelected?.()) { +        if (this.view && this.RootSelected) {              const { state } = this.view;              const pos = this.view.state.selection.$from;              let marks: Mark[] = [...(state.storedMarks ?? [])]; @@ -252,7 +256,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {      // finds all active marks on selection in given group      getActiveMarksOnSelection() { -        if (!this.view || !this.TextView?._props.rootSelected?.()) return [] as MarkType[]; +        if (!this.view || !this.RootSelected) return [] as MarkType[];          const { state } = this.view;          let marks: Mark[] = [...(state.storedMarks ?? [])]; @@ -281,7 +285,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {          this._noLinkActive = false;          this._boldActive = false; -        this._italicsActive = false; +        this._italicActive = false;          this._underlineActive = false;          this._strikethroughActive = false;          this._subscriptActive = false; @@ -291,7 +295,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {              switch (mark.name) {                  case 'noAutoLinkAnchor': this._noLinkActive = true; break;                  case 'strong':           this._boldActive = true; break; -                case 'em':               this._italicsActive = true; break; +                case 'em':               this._italicActive = true; break;                  case 'underline':        this._underlineActive = true; break;                  case 'strikethrough':    this._strikethroughActive = true; break;                  case 'subscript':        this._subscriptActive = true; break; @@ -326,6 +330,17 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {              this.view.focus();          }      }; +    toggleFitBox = () => { +        if (this.dataDoc) { +            const doc = this.dataDoc; +            (document.activeElement as HTMLElement)?.blur(); +            doc.text_fitBox = !doc.text_fitBox; +        } else { +            Doc.UserDoc().fitBox = !Doc.UserDoc().fitBox; +            Doc.UserDoc().textAlign = Doc.UserDoc().fitBox ? 'center' : undefined; +        } +        this.updateMenu(undefined, undefined, undefined, this.dataDoc); +    };      toggleBold = () => {          if (this.view) {              const mark = this.view.state.schema.mark(this.view.state.schema.marks.strong); @@ -342,7 +357,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {          }      }; -    toggleItalics = () => { +    toggleItalic = () => {          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); @@ -350,22 +365,27 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {          }      }; -    setFontField = (value: string, fontField: 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => { -        if (this.TextView && this.view) { -            const { text, paragraph } = this.view.state.schema.nodes; -            const selNode = this.view.state.selection.$anchor.node(); -            if (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) { -                this.TextView.dataDoc[this.TextView.fieldKey + `_${fontField}`] = value; -                this.view.focus(); +    setFontField = (value: string, fontField: 'fitBox' | 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => { +        if (this.TextView && this.view && fontField !== 'fitBox') { +            if (this.view.hasFocus()) { +                const attrs: { [key: string]: string } = {}; +                attrs[fontField] = value; +                const fmark = this.view.state.schema.marks['pF' + fontField.substring(1)].create(attrs); +                this.setMark(fmark, this.view.state, (tx: Transaction) => this.view?.dispatch(tx.addStoredMark(fmark)), true); +            } else { +                Array.from(new Set([...DocumentView.Selected(), this.TextView.DocumentView?.()])) +                    .filter(v => v?.ComponentView instanceof FormattedTextBox && v.ComponentView.EditorView?.TextView) +                    .map(v => v!.ComponentView as FormattedTextBox) +                    .forEach(view => { +                        view.EditorView!.TextView!.dataDoc[(view.EditorView!.TextView!.fieldKey ?? 'text') + `_${fontField}`] = value; +                    });              } -            const attrs: { [key: string]: string } = {}; -            attrs[fontField] = value; -            const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs); -            this.setMark(fmark, this.view.state, (tx: Transaction) => this.view!.dispatch(tx.addStoredMark(fmark)), true); -            this.view.focus(); +        } else if (this.dataDoc) { +            this.dataDoc[`${Doc.LayoutFieldKey(this.dataDoc)}_${fontField}`] = value; +            this.updateMenu(undefined, undefined, undefined, this.dataDoc);          } else {              Doc.UserDoc()[fontField] = value; -            // this.updateMenu(this.view, undefined, this.props, this.layoutDoc); +            this.updateMenu(undefined, undefined, undefined, this.dataDoc);          }      }; @@ -391,7 +411,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {                  this.view!.dispatch(tx3);              });          this.view.focus(); -        // this.updateMenu(this.view, undefined, this.props, this.layoutDoc);      };      insertSummarizer(state: EditorState, dispatch: (tr: Transaction) => void) { @@ -406,10 +425,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {      }      vcenterToggle = () => { -        this.layoutDoc && (this.layoutDoc._layout_centered = !this.layoutDoc._layout_centered); +        if (this.dataDoc) this.dataDoc._layout_centered = !this.dataDoc._layout_centered; +        else Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered;      }; -    align = (view: EditorView, dispatch: (tr: Transaction) => void, alignment: 'left' | 'right' | 'center') => { -        if (this.TextView?._props.rootSelected?.()) { +    align = (view: EditorView | undefined, dispatch: undefined | ((tr: Transaction) => void), alignment: 'left' | 'right' | 'center') => { +        if (view && dispatch && this.RootSelected) {              let { tr } = view.state;              view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos) => {                  if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) { @@ -421,6 +441,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {              });              view.focus();              dispatch?.(tr); +        } else { +            if (this.dataDoc) { +                this.dataDoc.text_align = alignment; +            } else Doc.UserDoc().textAlign = alignment; +            this.updateMenu(undefined, undefined, undefined, this.dataDoc);          }      }; @@ -698,7 +723,7 @@ interface RichTextMenuPluginProps {  }  export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> {      update(view: EditorView & { TextView?: FormattedTextBox }, lastState: EditorState | undefined) { -        RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, view.TextView?.layoutDoc); +        RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, view.TextView?.dataDoc);      }      render() {          return null;  | 
