From 782271987bc2585fd92659dfe09dcb7270535b20 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 17 Apr 2023 11:08:03 -0400
Subject: used stats to detect unnecessary server message when changing css
 style for text notes

---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 72 +++++++++++-----------
 1 file changed, 37 insertions(+), 35 deletions(-)

(limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 5719ea83c..f5826ef95 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,7 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { isEqual } from 'lodash';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { baseKeymap, selectAll } from 'prosemirror-commands';
 import { history } from 'prosemirror-history';
@@ -11,13 +11,13 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
 import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
 import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, Doc, DocListCast, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, CssSym, Doc, DocListCast, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
-import { ComputedField, ScriptField } from '../../../../fields/ScriptField';
+import { ComputedField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
@@ -29,6 +29,7 @@ import { DictationManager } from '../../../util/DictationManager';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { MakeTemplate } from '../../../util/DropConverter';
+import { IsFollowLinkScript } from '../../../util/LinkFollower';
 import { LinkManager } from '../../../util/LinkManager';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { SnappingManager } from '../../../util/SnappingManager';
@@ -64,7 +65,6 @@ import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
 import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
-import { IsFollowLinkScript } from '../../../util/LinkFollower';
 const translateGoogleApi = require('translate-google-api');
 
 export const GoogleRef = 'googleDocId';
@@ -79,7 +79,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
     public static Instance: FormattedTextBox;
     public static LiveTextUndo: UndoManager.Batch | undefined;
-    static _globalHighlights: string[] = ['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items'];
+    static _globalHighlightsCache: string = '';
+    static _globalHighlights = new ObservableSet<string>(['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items']);
     static _highlightStyleSheet: any = addStyleSheet();
     static _bulletStyleSheet: any = addStyleSheet();
     static _userStyleSheet: any = addStyleSheet();
@@ -193,7 +194,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     constructor(props: any) {
         super(props);
         FormattedTextBox.Instance = this;
-        this.updateHighlights();
         this._recordingStart = Date.now();
     }
 
@@ -605,45 +605,46 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return ret;
     }
 
-    updateHighlights = () => {
-        const highlights = FormattedTextBox._globalHighlights;
+    updateHighlights = (highlights: string[]) => {
+        if (Array.from(highlights).join('') === FormattedTextBox._globalHighlightsCache) return;
+        setTimeout(() => (FormattedTextBox._globalHighlightsCache = Array.from(highlights).join('')));
         clearStyleSheetRules(FormattedTextBox._userStyleSheet);
-        if (highlights.indexOf('Audio Tags') === -1) {
+        if (highlights.includes('Audio Tags')) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, '');
         }
-        if (highlights.indexOf('Text from Others') !== -1) {
+        if (highlights.includes('Text from Others')) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' });
         }
-        if (highlights.indexOf('My Text') !== -1) {
+        if (highlights.includes('My Text')) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { background: 'moccasin' });
         }
-        if (highlights.indexOf('Todo Items') !== -1) {
+        if (highlights.includes('Todo Items')) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-todo', { outline: 'black solid 1px' });
         }
-        if (highlights.indexOf('Important Items') !== -1) {
+        if (highlights.includes('Important Items')) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-important', { 'font-size': 'larger' });
         }
-        if (highlights.indexOf('Bold Text') !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror strong > span', { 'font-size': 'large' }, '');
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror :not(strong > span)', { 'font-size': '0px' }, '');
+        if (highlights.includes('Bold Text')) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner .ProseMirror strong > span', { 'font-size': 'large' }, '');
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner .ProseMirror :not(strong > span)', { 'font-size': '0px' }, '');
         }
-        if (highlights.indexOf('Disagree Items') !== -1) {
+        if (highlights.includes('Disagree Items')) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-disagree', { 'text-decoration': 'line-through' });
         }
-        if (highlights.indexOf('Ignore Items') !== -1) {
+        if (highlights.includes('Ignore Items')) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-ignore', { 'font-size': '1' });
         }
-        if (highlights.indexOf('By Recent Minute') !== -1) {
+        if (highlights.includes('By Recent Minute')) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
             const min = Math.round(Date.now() / 1000 / 60);
             numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
-            setTimeout(this.updateHighlights);
         }
-        if (highlights.indexOf('By Recent Hour') !== -1) {
+        if (highlights.includes('By Recent Hour')) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
             const hr = Math.round(Date.now() / 1000 / 60 / 60);
             numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
         }
+        this.layoutDoc[CssSym] = this.layoutDoc[CssSym] + 1; // css changes happen outside of react/mobx.  so we need to set a flag that will notify anyone intereted in layout changes triggered by css changes (eg., CollectionLinkView)
     };
 
     @observable _showSidebar = false;
@@ -781,18 +782,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const expertHighlighting = [...noviceHighlighting, 'Important Items', 'Ignore Items', 'Disagree Items', 'By Recent Minute', 'By Recent Hour'];
         (Doc.noviceMode ? noviceHighlighting : expertHighlighting).forEach(option =>
             highlighting.push({
-                description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? 'Highlight ' : 'Unhighlight ') + option,
-                event: () => {
+                description: (!FormattedTextBox._globalHighlights.has(option) ? 'Highlight ' : 'Unhighlight ') + option,
+                event: action(() => {
                     e.stopPropagation();
-                    if (FormattedTextBox._globalHighlights.indexOf(option) === -1) {
-                        FormattedTextBox._globalHighlights.push(option);
+                    if (!FormattedTextBox._globalHighlights.has(option)) {
+                        FormattedTextBox._globalHighlights.add(option);
                     } else {
-                        FormattedTextBox._globalHighlights.splice(FormattedTextBox._globalHighlights.indexOf(option), 1);
+                        FormattedTextBox._globalHighlights.delete(option);
                     }
-                    runInAction(() => (this.layoutDoc._highlights = FormattedTextBox._globalHighlights.join('')));
-                    this.updateHighlights();
-                },
-                icon: FormattedTextBox._globalHighlights.indexOf(option) === -1 ? 'highlighter' : 'remove-format',
+                }),
+                icon: !FormattedTextBox._globalHighlights.has(option) ? 'highlighter' : 'remove-format',
             })
         );
 
@@ -1031,6 +1030,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             () => this.autoHeight,
             autoHeight => autoHeight && this.tryUpdateScrollHeight()
         );
+        this._disposers.highlights = reaction(
+            () => Array.from(FormattedTextBox._globalHighlights).slice(),
+            highlights => this.updateHighlights(highlights),
+            { fireImmediately: true }
+        );
         this._disposers.width = reaction(
             () => this.props.PanelWidth(),
             width => this.tryUpdateScrollHeight()
@@ -1115,7 +1119,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         this._disposers.selected = reaction(
             () => this.props.isSelected(),
             action(selected => {
-                this.layoutDoc._highlights = selected ? FormattedTextBox._globalHighlights.join('') : '';
+                if (FormattedTextBox._globalHighlights.has('Bold Text')) {
+                    this.layoutDoc[CssSym] = this.layoutDoc[CssSym] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed
+                }
                 if (RichTextMenu.Instance?.view === this._editorView && !selected) {
                     RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
                 }
@@ -1431,7 +1437,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     }
 
     componentWillUnmount() {
-        FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined);
         Object.values(this._disposers).forEach(disposer => disposer?.());
         this.endUndoTypingBatch();
         this.unhighlightSearchTerms();
@@ -1532,11 +1537,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     @action
     onFocused = (e: React.FocusEvent): void => {
         //applyDevTools.applyDevTools(this._editorView);
-        FormattedTextBox.Focused = this;
         this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
         this.startUndoTypingBatch();
     };
-    @observable public static Focused: FormattedTextBox | undefined;
 
     onClick = (e: React.MouseEvent): void => {
         if (!this.props.isContentActive()) return;
@@ -1633,7 +1636,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 tr && this._editorView.dispatch(tr);
             }
         }
-        FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined);
         if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) {
             RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
         }
-- 
cgit v1.2.3-70-g09d2

