diff options
Diffstat (limited to 'src/client/views/nodes/formattedText')
9 files changed, 325 insertions, 273 deletions
| diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 5c3f3dcc9..212da3f3d 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -209,7 +209,7 @@ export class DashDocView extends React.Component<IDashDocView> {                  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); +                    console.log("DashDocView:" + e);                  }              } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index fc63dfbf5..38fa66d65 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "  import { ReplaceStep } from 'prosemirror-transform';  import { EditorView } from "prosemirror-view";  import { DateField } from '../../../../fields/DateField'; -import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit } from "../../../../fields/Doc";  import { documentSchema } from '../../../../fields/documentSchemas';  import applyDevTools = require("prosemirror-dev-tools");  import { removeMarkWithAttrs } from "./prosemirrorPatches"; @@ -24,7 +24,7 @@ import { RichTextField } from "../../../../fields/RichTextField";  import { RichTextUtils } from '../../../../fields/RichTextUtils';  import { createSchema, makeInterface } from "../../../../fields/Schema";  import { Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../fields/Types"; -import { TraceMobx, OVERRIDE_ACL } from '../../../../fields/util'; +import { TraceMobx, OVERRIDE_ACL, GetEffectiveAcl } from '../../../../fields/util';  import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils';  import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';  import { DocServer } from "../../../DocServer"; @@ -174,19 +174,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp      linkOnDeselect: Map<string, string> = new Map();      doLinkOnDeselect() { +          Array.from(this.linkOnDeselect.entries()).map(entry => {              const key = entry[0];              const value = entry[1]; +              const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);              DocServer.GetRefField(value).then(doc => {                  DocServer.GetRefField(id).then(linkDoc => {                      this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value);                      DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); -                    if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } -                    else DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "link to named target", id); +                    if (linkDoc) { +                        (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; +                    } else { +                        DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "portal link", "link to named target", id); +                    }                  });              });          }); +          this.linkOnDeselect.clear();      } @@ -226,32 +232,37 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null);              // the default text inherited from a prototype              const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template              const json = JSON.stringify(state.toJSON()); -            if (!this.dataDoc[AclSym]) { +            let unchanged = true; +            if (GetEffectiveAcl(this.dataDoc) === AclEdit) {                  if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {                      this._applyingChange = true;                      (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));                      if ((!curTemp && !curProto) || curText || curLayout?.Data.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 (json !== curLayout?.Data) { +                        if (json.replace(/"selection":.*/, "") !== curLayout?.Data.replace(/"selection":.*/, "")) {                              !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize));                              !curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily));                              this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);                              this.dataDoc[this.props.fieldKey + "-noTemplate"] = (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 }); +                            unchanged = false;                          }                      } else { // if we've deleted all the text in a note driven by a template, then restore the template data                          this.dataDoc[this.props.fieldKey] = undefined;                          this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data)));                          this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have +                        unchanged = false;                      }                      this._applyingChange = false; +                    if (!unchanged) { +                        this.updateTitle(); +                        this.tryUpdateHeight(); +                    }                  }              } else {                  const json = JSON.parse(Cast(this.dataDoc[this.fieldKey], RichTextField)?.Data!);                  json.selection = state.toJSON().selection;                  this._editorView.updateState(EditorState.fromJSON(this.config, json));              } -            this.updateTitle(); -            this.tryUpdateHeight();          }      } @@ -414,16 +425,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });          }          if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) { -            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" }); +            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "todo", { outline: "black solid 1px" });          }          if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) { -            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "important", { "font-size": "larger" }); +            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "important", { "font-size": "larger" });          }          if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) { -            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" }); +            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "disagree", { "text-decoration": "line-through" });          }          if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) { -            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" }); +            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "ignore", { "font-size": "1" });          }          if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) {              addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); @@ -459,9 +470,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp      specificContextMenu = (e: React.MouseEvent): void => {          const cm = ContextMenu.Instance; -        const appearance = ContextMenu.Instance.findByDescription("Appearance..."); -        const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : []; -          const changeItems: ContextMenuProps[] = [];          const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);          DocListCast(noteTypesDoc?.data).forEach(note => { @@ -473,17 +481,34 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              });          });          changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" }); -        appearanceItems.push({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" }); +        const highlighting: ContextMenuProps[] = []; +        ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => +            highlighting.push({ +                description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { +                    e.stopPropagation(); +                    if (FormattedTextBox._highlights.indexOf(option) === -1) { +                        FormattedTextBox._highlights.push(option); +                    } else { +                        FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1); +                    } +                    this.updateHighlights(); +                }, icon: "expand-arrows-alt" +            })); + +          const uicontrols: ContextMenuProps[] = []; -        uicontrols.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" }); -        uicontrols.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); -        uicontrols.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" }); +        uicontrols.push({ description: `${this.layoutDoc._showSidebar ? "Hide" : "Show"} Sidebar`, event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" }); +        uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); +        uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" });          !Doc.UserDoc().noviceMode && uicontrols.push({              description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto =>                  proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt"          }); +        cm.addItem({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" }); -        appearanceItems.push({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" }); +        const appearance = cm.findByDescription("Appearance..."); +        const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : []; +        appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" });          this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });          Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });          appearanceItems.push({ @@ -511,30 +536,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                  Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);              }, icon: "eye"          }); -        !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); - -        const funcs: ContextMenuProps[] = []; - -        //funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); -        funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); -        funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); - -        const highlighting: ContextMenuProps[] = []; -        ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => -            highlighting.push({ -                description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { -                    e.stopPropagation(); -                    if (FormattedTextBox._highlights.indexOf(option) === -1) { -                        FormattedTextBox._highlights.push(option); -                    } else { -                        FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1); -                    } -                    this.updateHighlights(); -                }, icon: "expand-arrows-alt" -            })); -        funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" }); - -        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); +        cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); + +        const options = cm.findByDescription("Options..."); +        const optionItems = options && "subitems" in options ? options.subitems : []; +        !Doc.UserDoc().noviceMode && optionItems.push({ description: this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); +        optionItems.push({ description: `${this.Document._autoHeight ? "Lock" : "Auto"} Height`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); +        optionItems.push({ description: `${!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Lock" : "Unlock"} Aspect`, event: this.toggleNativeDimensions, icon: "snowflake" }); +        !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });          this._downX = this._downY = Number.NaN;      } @@ -551,11 +560,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp      }      stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); }; -    @action -    toggleMenubar = () => { -        this.layoutDoc._chromeStatus = this.layoutDoc._chromeStatus === "disabled" ? "enabled" : "disabled"; -    } -      recordBullet = async () => {          const completedCue = "end session";          const results = await DictationManager.Controls.listen({ @@ -944,6 +948,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp              frag.forEach(node => nodes.push(marker(node)));              return Fragment.fromArray(nodes);          } + +          function addLinkMark(node: Node, title: string, linkId: string) {              if (!node.isText) {                  const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId)); @@ -1028,7 +1034,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp          this._editorView?.destroy();      } -    static _downEvent: any; +    _downEvent: any;      _downX = 0;      _downY = 0;      _break = false; @@ -1048,7 +1054,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp          this._downX = e.clientX;          this._downY = e.clientY;          this.doLinkOnDeselect(); -        FormattedTextBox._downEvent = true; +        this._downEvent = true;          FormattedTextBoxComment.textBox = this;          if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) {              e.preventDefault(); @@ -1064,8 +1070,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp      }      onPointerUp = (e: React.PointerEvent): void => { -        if (!FormattedTextBox._downEvent) return; -        FormattedTextBox._downEvent = false; +        if (!this._downEvent) return; +        this._downEvent = false;          if (!(e.nativeEvent as any).formattedHandled) {              const editor = this._editorView!;              FormattedTextBoxComment.textBox = this; @@ -1084,7 +1090,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp      onDoubleClick = (e: React.MouseEvent): void => {          this.doLinkOnDeselect(); -        FormattedTextBox._downEvent = true;          FormattedTextBoxComment.textBox = this;          if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) {              e.preventDefault(); @@ -1168,7 +1173,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                  }              } else if ([this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node?.type) &&                  node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) { -                this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos!))); +                this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos)));              }          }          if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } @@ -1236,7 +1241,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp      richTextMenuPlugin() {          return new Plugin({              view(newView) { -                RichTextMenu.Instance && RichTextMenu.Instance.changeView(newView); +                RichTextMenu.Instance?.changeView(newView);                  return RichTextMenu.Instance;              }          }); @@ -1291,9 +1296,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp          if (e.key === "Tab" || e.key === "Enter") {              e.preventDefault();          } -        const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); -        this._lastTimedMark = mark; -        // this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); +        if (e.key === " " || this._lastTimedMark?.attrs.userid !== Doc.CurrentUserEmail) { +            const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); +            this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); +        }          if (!this._undoTyping) {              this.startUndoTypingBatch(); @@ -1343,9 +1349,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp          const scale = this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1);          const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";          const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground; -        if (this.props.isSelected()) { -            setTimeout(() => this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props), 0); -        } else if (FormattedTextBoxComment.textBox === this) { +        setTimeout(() => this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props), this.props.isSelected() ? 10 : 0); // need to make sure that we update a text box that is selected after updating the one that was deselected +        if (!this.props.isSelected() && FormattedTextBoxComment.textBox === this) {              setTimeout(() => FormattedTextBoxComment.Hide(), 0);          }          const selPad = this.props.isSelected() ? -10 : 0; @@ -1368,7 +1373,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp                          opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,                          color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),                          pointerEvents: interactive ? undefined : "none", -                        fontSize: Cast(this.layoutDoc._fontSize, "number", null), +                        fontSize: Cast(this.layoutDoc._fontSize, "string", null),                          fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),                          transition: "opacity 1s"                      }} diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss index 9089e7039..6a403cb17 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss @@ -4,10 +4,74 @@      z-index: 20;      background: white;      border: 1px solid silver; -    border-radius: 2px; +    border-radius: 7px;      margin-bottom: 7px;      -webkit-transform: translateX(-50%);      transform: translateX(-50%); +    box-shadow: 3px 3px 1.5px grey; + +    .FormattedTextBoxComment { +        background-color: white; +        border: 8px solid white; + +        //display: flex; +        .FormattedTextBoxComment-info { + +            margin-bottom: 9px; + +            .FormattedTextBoxComment-title { +                padding-right: 4px; +                float: left; + +                .FormattedTextBoxComment-description { +                    text-decoration: none; +                    font-style: italic; +                    color: rgb(95, 97, 102); +                    font-size: 10px; +                    padding-bottom: 4px; +                    margin-bottom: 5px; + +                } +            } + +            .FormattedTextBoxComment-button { +                display: inline; +                padding-left: 6px; +                padding-right: 6px; +                padding-top: 2.5px; +                padding-bottom: 2.5px; +                width: 17px; +                height: 17px; +                margin: 0; +                margin-right: 3px; +                border-radius: 50%; +                pointer-events: auto; +                background-color: rgb(0, 0, 0); +                color: rgb(255, 255, 255); +                transition: transform 0.2s; +                text-align: center; +                position: relative; +                font-size: 12px; + +                &:hover { +                    background-color: rgb(77, 77, 77); +                    cursor: pointer; +                } +            } +        } + +        .FormattedTextBoxComment-preview-wrapper { +            width: 170px; +            height: 170px; +            overflow: hidden; +            //padding-top: 5px; +            margin-top: 10px; +            margin-bottom: 8px; +            align-content: center; +            justify-content: center; +            background-color: rgb(160, 160, 160); +        } +    }  }  .FormattedTextBox-tooltip:before { @@ -42,64 +106,4 @@      top: 50%;      right: 0;      transform: translateY(-50%); - -    .FormattedTextBoxComment-button { -        width: 20px; -        height: 20px; -        margin: 0; -        margin-right: 6px; -        border-radius: 50%; -        pointer-events: auto; -        background-color: rgb(38, 40, 41); -        color: rgb(178, 181, 184); -        font-size: 65%; -        transition: transform 0.2s; -        text-align: center; -        position: relative; - -        // margin-top: "auto"; -        //     margin-bottom: "auto"; -        //     background: black; -        //     color: white; -        //     display: inline-block; -        //     border-radius: 18px; -        //     font-size: 12.5px; -        //     width: 18px; -        //     height: 18px; -        //     margin-top: auto; -        //     margin-bottom: auto; -        //     margin-right: 3px; -        //     cursor: pointer; -        //     transition: transform 0.2s; - -        .FormattedTextBoxComment-fa-icon { -            margin-top: "auto"; -            margin-bottom: "auto"; -            background: black; -            color: white; -            display: inline-block; -            border-radius: 18px; -            font-size: 12.5px; -            width: 18px; -            height: 18px; -            margin-top: auto; -            margin-bottom: auto; -            margin-right: 3px; -            cursor: pointer; -            transition: transform 0.2s; -            // position: absolute; -            // top: 50%; -            // left: 50%; -            // transform: translate(-50%, -50%); -        } - -        &:last-child { -            margin-right: 0; -        } - -        &:hover { -            background: rgb(53, 146, 199); -            ; -        } -    }  }
\ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 56826e5c7..6f3984f39 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -3,7 +3,7 @@ import { EditorState, Plugin } from "prosemirror-state";  import { EditorView } from "prosemirror-view";  import * as ReactDOM from 'react-dom';  import { Doc, DocCastAsync, Opt } from "../../../../fields/Doc"; -import { Cast, FieldValue, NumCast } from "../../../../fields/Types"; +import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types";  import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne, returnEmptyFilter } from "../../../../Utils";  import { DocServer } from "../../../DocServer";  import { DocumentManager } from "../../../util/DocumentManager"; @@ -21,6 +21,8 @@ import { action } from "mobx";  import { LinkManager } from "../../../util/LinkManager";  import { LinkDocPreview } from "../LinkDocPreview";  import { DocumentLinksButton } from "../DocumentLinksButton"; +import { Tooltip } from "@material-ui/core"; +import { undoBatch } from "../../../util/UndoManager";  export let formattedTextBoxCommentPlugin = new Plugin({      view(editorView) { return new FormattedTextBoxComment(editorView); } @@ -85,13 +87,13 @@ export class FormattedTextBoxComment {              FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";              FormattedTextBoxComment.tooltip.style.pointerEvents = "all";              FormattedTextBoxComment.tooltip.style.maxWidth = "200px"; -            FormattedTextBoxComment.tooltip.style.maxHeight = "206px"; +            FormattedTextBoxComment.tooltip.style.maxHeight = "235px";              FormattedTextBoxComment.tooltip.style.width = "100%";              FormattedTextBoxComment.tooltip.style.height = "100%";              FormattedTextBoxComment.tooltip.style.overflow = "hidden";              FormattedTextBoxComment.tooltip.style.display = "none";              FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput); -            FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => { +            FormattedTextBoxComment.tooltip.onpointerdown = async (e: PointerEvent) => {                  const keep = e.target && (e.target as any).type === "checkbox" ? true : false;                  const textBox = FormattedTextBoxComment.textBox;                  if (FormattedTextBoxComment.linkDoc && !keep && textBox) { @@ -103,8 +105,22 @@ export class FormattedTextBoxComment {                              if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {                                  textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");                              } else { -                                DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, -                                    (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation)); +                                const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(FormattedTextBoxComment.linkDoc.anchor1, Doc)), textBox.dataDoc) ? +                                    Cast(FormattedTextBoxComment.linkDoc.anchor2, Doc) : (Cast(FormattedTextBoxComment.linkDoc.anchor1, Doc)) +                                    || FormattedTextBoxComment.linkDoc); +                                const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; + +                                if (FormattedTextBoxComment.linkDoc.follow) { +                                    if (FormattedTextBoxComment.linkDoc.follow === "Default") { +                                        DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "onRight"), false); +                                    } else if (FormattedTextBoxComment.linkDoc.follow === "Always open in right tab") { +                                        if (target) { textBox.props.addDocTab(target, "onRight"); } +                                    } else if (FormattedTextBoxComment.linkDoc.follow === "Always open in new tab") { +                                        if (target) { textBox.props.addDocTab(target, "inTab"); } +                                    } +                                } else { +                                    DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "onRight"), false); +                                }                              }                          } else {                              if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) { @@ -128,6 +144,7 @@ export class FormattedTextBoxComment {          }      } +    @undoBatch      @action      deleteLink = () => {          FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null; @@ -241,55 +258,36 @@ export class FormattedTextBoxComment {                              }                              if (target?.author) {                                  FormattedTextBoxComment.showCommentbox("", view, nbef); -                                const docPreview = <div style={{ backgroundColor: "white", border: "8px solid white" }}> -                                    {target.title} -                                    <div className="wrapper" style={{ float: "right" }}> -                                        <div title="Delete link" className="FormattedTextBoxComment-button" style={{ -                                            display: "inline", -                                            paddingLeft: "6px", -                                            paddingRight: "6px", -                                            paddingTop: "2.5px", -                                            paddingBottom: "2.5px", -                                            width: "20px", -                                            height: "20px", -                                            margin: 0, -                                            marginRight: "6px", -                                            borderRadius: "50%", -                                            pointerEvents: "auto", -                                            backgroundColor: "rgb(38, 40, 41)", -                                            color: "rgb(178, 181, 184)", -                                            transition: "transform 0.2s", -                                            textAlign: "center", -                                            position: "relative" -                                        }} ref={(r) => this._deleteRef = r}> -                                            <FontAwesomeIcon className="FormattedTextBox-fa-icon" icon="trash" -                                                size="sm" /></div> -                                        <div title="Follow link" className="FormattedTextBoxComment-button" style={{ -                                            display: "inline", -                                            paddingLeft: "6px", -                                            paddingRight: "6px", -                                            paddingTop: "2.5px", -                                            paddingBottom: "2.5px", -                                            width: "20px", -                                            height: "20px", -                                            margin: 0, -                                            marginRight: "6px", -                                            borderRadius: "50%", -                                            pointerEvents: "auto", -                                            backgroundColor: "rgb(38, 40, 41)", -                                            color: "rgb(178, 181, 184)", -                                            transition: "transform 0.2s", -                                            textAlign: "center", -                                            position: "relative" -                                        }} ref={(r) => this._followRef = r}> -                                            <FontAwesomeIcon className="FormattedTextBox-fa-icon" icon="arrow-right" -                                                size="sm" /> + +                                const title = StrCast(target.title).length > 16 ? +                                    StrCast(target.title).substr(0, 16) + "..." : target.title; + + +                                const docPreview = <div className="FormattedTextBoxComment"> +                                    <div className="FormattedTextBoxComment-info"> +                                        <div className="FormattedTextBoxComment-title"> +                                            {title} +                                            {FormattedTextBoxComment.linkDoc.description !== "" ? <p className="FormattedTextBoxComment-description"> +                                                {StrCast(FormattedTextBoxComment.linkDoc.description)}</p> : null}                                          </div> -                                    </div> -                                    <div className="wrapper" style={{ -                                        maxWidth: "180px", maxHeight: "168px", overflow: "hidden", -                                        overflowY: "hidden", paddingTop: "5px" -                                    }}> +                                        <div className="wrapper" style={{ float: "right" }}> + +                                            <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>} placement="top"> +                                                <div className="FormattedTextBoxComment-button" +                                                    ref={(r) => this._deleteRef = r}> +                                                    <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="trash" color="white" +                                                        size="sm" /></div> +                                            </Tooltip> + +                                            <Tooltip title={<><div className="dash-tooltip">Follow Link</div></>} placement="top"> +                                                <div className="FormattedTextBoxComment-button" +                                                    ref={(r) => this._followRef = r}> +                                                    <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="arrow-right" color="white" +                                                        size="sm" /> +                                                </div> +                                            </Tooltip> +                                        </div> </div> +                                    <div className="FormattedTextBoxComment-preview-wrapper">                                          <ContentFittingDocumentView                                              Document={target}                                              LibraryPath={emptyPath} @@ -307,17 +305,20 @@ export class FormattedTextBoxComment {                                              ContainingCollectionDoc={undefined}                                              ContainingCollectionView={undefined}                                              renderDepth={0} -                                            PanelWidth={() => Math.min(350, NumCast(target._width, 350))} -                                            PanelHeight={() => Math.min(250, NumCast(target._height, 250))} +                                            PanelWidth={() => 175} //Math.min(350, NumCast(target._width, 350))} +                                            PanelHeight={() => 175} //Math.min(250, NumCast(target._height, 250))}                                              focus={emptyFunction}                                              whenActiveChanged={returnFalse}                                              bringToFront={returnFalse}                                              ContentScaling={returnOne} -                                            NativeWidth={returnZero} -                                            NativeHeight={returnZero} +                                            NativeWidth={() => target._nativeWidth ? NumCast(target._nativeWidth) : 0} +                                            NativeHeight={() => target._nativeHeight ? NumCast(target._nativeHeight) : 0}                                          />                                      </div>                                  </div>; + + +                                  FormattedTextBoxComment.showCommentbox("", view, nbef);                                  ReactDOM.render(docPreview, FormattedTextBoxComment.tooltipText); diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 3f73ec436..8faf752b4 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -11,6 +11,7 @@ import { Doc, DataSym } from "../../../../fields/Doc";  import { FormattedTextBox } from "./FormattedTextBox";  import { Id } from "../../../../fields/FieldSymbols";  import { Docs } from "../../../documents/Documents"; +import { Utils } from "../../../../Utils";  const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false; @@ -102,7 +103,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any      //Command to create a new Tab with a PDF of all the command shortcuts      bind("Mod-/", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => { -        const newDoc = Docs.Create.PdfDocument("http://localhost:1050/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }); +        const newDoc = Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _fitWidth: true, _width: 300, _height: 300 });          props.addDocTab(newDoc, "onRight");      }); diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index f10c425d4..47a4911b8 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -11,7 +11,7 @@ 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, BoolCast } from "../../../../fields/Types"; +import { Cast, StrCast, BoolCast, NumCast } from "../../../../fields/Types";  import { unimplementedFunction, Utils } from "../../../../Utils";  import { DocServer } from "../../../DocServer";  import { LinkManager } from "../../../util/LinkManager"; @@ -23,7 +23,8 @@ import { updateBullets } from "./ProsemirrorExampleTransfer";  import "./RichTextMenu.scss";  import { schema } from "./schema_rts";  import { TraceMobx } from "../../../../fields/util"; -import { UndoManager } from "../../../util/UndoManager"; +import { UndoManager, undoBatch } from "../../../util/UndoManager"; +import { Tooltip } from "@material-ui/core";  const { toggleMark } = require("prosemirror-commands");  library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller); @@ -112,6 +113,7 @@ export default class RichTextMenu extends AntimodeMenu {              { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType },              { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType },              { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "A.1", command: this.changeListType }, +            { node: schema.nodes.ordered_list.create({ mapStyle: "" }), title: "Set list type", label: "<none>", command: this.changeListType },              //{ node: undefined, title: "Set list type", label: "Remove", command: this.changeListType },          ]; @@ -154,7 +156,9 @@ export default class RichTextMenu extends AntimodeMenu {      @action      changeView(view: EditorView) { -        this.view = view; +        if ((view as any)?.TextView?.props.isSelected(true)) { +            this.view = view; +        }      }      update(view: EditorView, lastState: EditorState | undefined) { @@ -163,8 +167,7 @@ export default class RichTextMenu extends AntimodeMenu {      @action      public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { -        if (!view) { -            console.log("no editor?  why?"); +        if (!view || !(view as any).TextView?.props.isSelected(true)) {              return;          }          this.view = view; @@ -217,7 +220,7 @@ export default class RichTextMenu extends AntimodeMenu {      // finds font sizes and families in selection      getActiveAlignment() { -        if (this.view) { +        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) { @@ -230,15 +233,18 @@ export default class RichTextMenu extends AntimodeMenu {      // finds font sizes and families in selection      getActiveListStyle() { -        if (this.view) { +        if (this.view && this.TextView.props.isSelected(true)) {              const path = (this.view.state.selection.$from as any).path;              for (let i = 0; i < path.length; i += 3) {                  if (path[i].type === this.view.state.schema.nodes.ordered_list) {                      return path[i].attrs.mapStyle;                  }              } +            if (this.view.state.selection.$from.nodeAfter?.type === this.view.state.schema.nodes.ordered_list) { +                return this.view.state.selection.$from.nodeAfter?.attrs.mapStyle; +            }          } -        return "decimal"; +        return "";      }      // finds font sizes and families in selection @@ -247,16 +253,21 @@ export default class RichTextMenu extends AntimodeMenu {          const activeFamilies: string[] = [];          const activeSizes: string[] = []; -        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) { -            ref_node.marks.forEach(m => { -                m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); -                m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt"); -            }); +        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) { +                ref_node.marks.forEach(m => { +                    m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); +                    m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt"); +                }); +            } +            !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))));          } - +        !activeFamilies.length && (activeFamilies.push(StrCast(Doc.UserDoc().fontFamily))); +        !activeSizes.length && (activeSizes.push(StrCast(Doc.UserDoc().fontSize)));          return { activeFamilies, activeSizes };      } @@ -269,14 +280,14 @@ export default class RichTextMenu extends AntimodeMenu {      //finds all active marks on selection in given group      getActiveMarksOnSelection() { -        if (!this.view) return; +        let activeMarks: MarkType[] = []; +        if (!this.view || !this.TextView.props.isSelected(true)) return activeMarks;          const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];          if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);          //current selection          const { empty, ranges, $to } = this.view.state.selection as TextSelection;          const state = this.view.state; -        let activeMarks: MarkType[] = [];          if (!empty) {              activeMarks = markGroup.filter(mark => {                  const has = false; @@ -308,7 +319,7 @@ export default class RichTextMenu extends AntimodeMenu {      }      destroy() { -        this.fadeOut(true); +        !this.TextView?.props.isSelected(true) && this.fadeOut(true);      }      @action @@ -348,22 +359,20 @@ export default class RichTextMenu extends AntimodeMenu {          }          return ( -            <button className={"antimodeMenu-button" + (isActive ? " active" : "")} key={title} title={title} onPointerDown={onClick}> -                <FontAwesomeIcon icon={faIcon as IconProp} size="lg" /> -            </button> +            <Tooltip title={<div className="dash-tooltip">{title}</div>} key={title} placement="bottom"> +                <button className={"antimodeMenu-button" + (isActive ? " active" : "")} onPointerDown={onClick}> +                    <FontAwesomeIcon icon={faIcon as IconProp} size="lg" /> +                </button> +            </Tooltip>          );      } -    createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element { +    createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => {}): JSX.Element {          const items = options.map(({ title, label, hidden, style }) => {              if (hidden) { -                return label === activeOption ? -                    <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 <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;              } -            return label === activeOption ? -                <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>; +            return <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;          });          const self = this; @@ -372,37 +381,47 @@ export default class RichTextMenu extends AntimodeMenu {              e.preventDefault();              self.TextView.endUndoTypingBatch();              options.forEach(({ label, mark, command }) => { -                if (e.target.value === label) { -                    UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown"); +                if (e.target.value === label && mark) { +                    if (!self.TextView.props.isSelected(true)) { +                        switch (mark.type) { +                            case schema.marks.pFontFamily: setter(Doc.UserDoc().fontFamily = mark.attrs.family); break; +                            case schema.marks.pFontSize: setter(Doc.UserDoc().fontSize = mark.attrs.fontSize.toString() + "pt"); break; +                        } +                    } +                    else UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown");                  }              });          } -        return <select onChange={onChange} key={key}>{items}</select>; +        return <Tooltip key={key} title={<div className="dash-tooltip">{key}</div>} placement="bottom"> +            <select onChange={onChange} value={activeOption}>{items}</select> +        </Tooltip>;      } -    createNodesDropdown(activeMap: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element { -        const activeOption = activeMap === "bullet" ? ":" : activeMap === "decimal" ? "1.1" : "A.1"; +    createNodesDropdown(activeMap: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => {}): JSX.Element { +        const activeOption = activeMap === "bullet" ? ":" : activeMap === "decimal" ? "1.1" : activeMap === "multi" ? "A.1" : "<none>";          const items = options.map(({ title, label, hidden, style }) => {              if (hidden) { -                return label === activeOption ? -                    <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 <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;              } -            return label === activeOption ? -                <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>; +            return <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;          });          const self = this;          function onChange(val: string) {              self.TextView.endUndoTypingBatch();              options.forEach(({ label, node, command }) => { -                if (val === label) { -                    UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown"); +                if (val === label && node) { +                    if (self.TextView.props.isSelected(true)) { +                        UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown"); +                        setter(val); +                    }                  }              });          } -        return <select onChange={e => onChange(e.target.value)} key={key}>{items}</select>; + +        return <Tooltip key={key} title={<div className="dash-tooltip">{key}</div>} placement="bottom"> +            <select value={activeOption} onChange={e => onChange(e.target.value)}>{items}</select> +        </Tooltip>;      }      changeFontSize = (mark: Mark, view: EditorView) => { @@ -416,10 +435,21 @@ export default class RichTextMenu extends AntimodeMenu {      // TODO: remove doesn't work      //remove all node type and apply the passed-in one to the selected text      changeListType = (nodeType: Node | undefined) => { -        if (!this.view) return; +        if (!this.view || (nodeType as any)?.attrs.mapStyle === "") return; + +        const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list; +        let inList: any = undefined; +        let fromList = -1; +        const path: any = Array.from((this.view.state.selection.$from as any).path); +        for (let i = 0; i < path.length; i++) { +            if (path[i]?.type === schema.nodes.ordered_list) { +                inList = path[i]; +                fromList = path[i - 1]; +            } +        }          const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks()); -        if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { +        if (inList || !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {              const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);              marks && tx3.ensureMarks([...marks]);              marks && tx3.setStoredMarks([...marks]); @@ -427,12 +457,12 @@ export default class RichTextMenu extends AntimodeMenu {              this.view!.dispatch(tx2);          })) {              const tx2 = this.view.state.tr; -            if (nodeType && this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list) { -                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view.state.selection.from, this.view.state.selection.to); +            if (nodeType && (inList || nextIsOL)) { +                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from, +                    inList ? fromList + inList.nodeSize : this.view.state.selection.to);                  marks && tx3.ensureMarks([...marks]);                  marks && tx3.setStoredMarks([...marks]); - -                this.view.dispatch(tx3.setSelection(new NodeSelection(tx3.doc.resolve(this.view.state.selection.$from.pos)))); +                this.view.dispatch(tx3);              }          }      } @@ -448,13 +478,13 @@ export default class RichTextMenu extends AntimodeMenu {          return true;      }      alignCenter = (state: EditorState<any>, dispatch: any) => { -        return this.alignParagraphs(state, "center", dispatch); +        return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "center", dispatch);      }      alignLeft = (state: EditorState<any>, dispatch: any) => { -        return this.alignParagraphs(state, "left", dispatch); +        return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "left", dispatch);      }      alignRight = (state: EditorState<any>, dispatch: any) => { -        return this.alignParagraphs(state, "right", dispatch); +        return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "right", dispatch);      }      alignParagraphs(state: EditorState<any>, align: "left" | "right" | "center", dispatch: any) { @@ -574,10 +604,11 @@ export default class RichTextMenu extends AntimodeMenu {              label = "No marks are currently stored";          } -        const button = -            <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}> +        const button = <Tooltip title={<div className="dash-tooltip">style brush</div>} placement="bottom"> +            <button className="antimodeMenu-button" onPointerDown={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}>                  <FontAwesomeIcon icon="paint-roller" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.brushMarks?.size > 0 ? 45 : 0}deg)` }} /> -            </button>; +            </button> +        </Tooltip>;          const dropdownContent =              <div className="dropdown"> @@ -626,7 +657,7 @@ export default class RichTextMenu extends AntimodeMenu {      @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; }      @action setActiveColor(color: string) { this.activeFontColor = color; } -    get TextView() { return (this.view as any).TextView as FormattedTextBox; } +    get TextView() { return (this.view as any)?.TextView as FormattedTextBox; }      createColorButton() {          const self = this; @@ -646,11 +677,12 @@ export default class RichTextMenu extends AntimodeMenu {              self.TextView.EditorView!.focus();          } -        const button = -            <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onColorClick}> +        const button = <Tooltip title={<div className="dash-tooltip">set font color</div>} placement="bottom"> +            <button className="antimodeMenu-button color-preview-button" onPointerDown={onColorClick}>                  <FontAwesomeIcon icon="palette" size="lg" />                  <div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div> -            </button>; +            </button> +        </Tooltip>;          const dropdownContent =              <div className="dropdown" > @@ -699,11 +731,12 @@ export default class RichTextMenu extends AntimodeMenu {              UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highlighter");          } -        const button = -            <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" onPointerDown={onHighlightClick}> +        const button = <Tooltip title={<div className="dash-tooltip">set highlight color</div>} placement="bottom"> +            <button className="antimodeMenu-button color-preview-button" key="highilghter-button" onPointerDown={onHighlightClick}>                  <FontAwesomeIcon icon="highlighter" size="lg" />                  <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div> -            </button>; +            </button> +        </Tooltip>;          const dropdownContent =              <div className="dropdown"> @@ -742,7 +775,9 @@ export default class RichTextMenu extends AntimodeMenu {          const link = this.currentLink ? this.currentLink : ""; -        const button = <FontAwesomeIcon icon="link" size="lg" />; +        const button = <Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom"> +            <div><FontAwesomeIcon icon="link" size="lg" /> </div> +        </Tooltip>;          const dropdownContent =              <div className="dropdown link-menu"> @@ -753,9 +788,7 @@ export default class RichTextMenu extends AntimodeMenu {                  <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>              </div>; -        return ( -            <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} /> -        ); +        return <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />;      }      async getTextLinkTargetTitle() { @@ -798,6 +831,8 @@ export default class RichTextMenu extends AntimodeMenu {          ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRight", "", target);      } +    @undoBatch +    @action      deleteLink = () => {          if (this.view) {              const link = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor); @@ -882,22 +917,22 @@ export default class RichTextMenu extends AntimodeMenu {      render() {          TraceMobx(); -        const row1 = <div className="antimodeMenu-row" key="row1" style={{ display: this.collapsed ? "none" : undefined }}>{[ +        const row1 = <div className="antimodeMenu-row" key="row 1" style={{ display: this.collapsed ? "none" : undefined }}>{[              !this.collapsed ? this.getDragger() : (null), -            !this.Pinned ? (null) : <> {[ +            !this.Pinned ? (null) : <div key="frag1"> {[                  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)),                  this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),                  this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),                  this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)), -                <div className="richTextMenu-divider" /> -            ]}</>, +                <div className="richTextMenu-divider" key="divider" /> +            ]}</div>,              this.createColorButton(),              this.createHighlighterButton(),              this.createLinkButton(),              this.createBrushButton(), -            <div className="richTextMenu-divider" />, +            <div className="richTextMenu-divider" key="divider 2" />,              this.createButton("align-left", "Align Left", this.activeAlignment === "left", this.alignLeft),              this.createButton("align-center", "Align Center", this.activeAlignment === "center", this.alignCenter),              this.createButton("align-right", "Align Right", this.activeAlignment === "right", this.alignRight), @@ -907,20 +942,20 @@ export default class RichTextMenu extends AntimodeMenu {              this.createButton("hand-point-right", "Indent", undefined, this.indentParagraph),          ]}</div>; -        const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2"> +        const row2 = <div className="antimodeMenu-row row-2" key="row2">              {this.collapsed ? this.getDragger() : (null)} -            <div key="row" style={{ display: this.collapsed ? "none" : undefined }}> -                <div className="richTextMenu-divider" />, -                {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"), -                this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"), -                <div className="richTextMenu-divider" />, -                this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes"), +            <div key="row 2" style={{ display: this.collapsed ? "none" : undefined }}> +                <div className="richTextMenu-divider" key="divider 3" />, +                {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => this.activeFontSize = val)), +                this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => this.activeFontFamily = val)), +                <div className="richTextMenu-divider" key="divider 4" />, +                this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", action((val: string) => this.activeListType = val)),                  this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),                  this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),                  this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule), -                <div className="richTextMenu-divider" />,]} +                <div className="richTextMenu-divider" key="divider 5" />,]}              </div> -            <div key="button"> +            <div key="collapser">                  {/* <div key="collapser">                      <button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>                          <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} /> diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index ca30dde9d..ef0fead4a 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -90,7 +90,7 @@ export class RichTextRules {                      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" }); +                    const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: "9pt", 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 diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx index a989abd6a..33a080fe4 100644 --- a/src/client/views/nodes/formattedText/RichTextSchema.tsx +++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx @@ -54,6 +54,8 @@ export class DashDocView {          this._dashSpan.style.height = node.attrs.height;          this._dashSpan.style.position = "absolute";          this._dashSpan.style.display = "inline-block"; +        this._dashSpan.style.left = "0"; +        this._dashSpan.style.top = "0";          this._dashSpan.style.whiteSpace = "normal";          this._dashSpan.onpointerleave = () => { @@ -160,9 +162,15 @@ export class DashDocView {                  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" })); +                        if (getPos() !== undefined) { +                            const node = view.state.tr.doc.nodeAt(getPos()); +                            if (node.attrs.width !== dashDoc._width + "px" || +                                node.attrs.height !== dashDoc._height + "px") { +                                view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" })); +                            } +                        }                      } catch (e) { -                        console.log(e); +                        console.log("RichTextSchema: " + e);                      }                  }              }; diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 3d7d71b14..f95f46104 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -258,9 +258,7 @@ export const marks: { [index: string]: MarkSpec } = {          },          parseDOM: [{ style: 'background: yellow' }],          toDOM(node: any) { -            return ['span', { -                style: `background: ${node.attrs.selected ? "orange" : "yellow"}` -            }]; +            return ['span', { style: `background: ${node.attrs.selected ? "orange" : "yellow"}` }];          }      }, @@ -277,8 +275,8 @@ export const marks: { [index: string]: MarkSpec } = {              const min = Math.round(node.attrs.modified / 12);              const hr = Math.round(min / 60);              const day = Math.round(hr / 60 / 24); -            const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : ""; -            return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0]; +            const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " UM-remote" : ""; +            return ['span', { class: "UM-" + uid + remote + " UM-min-" + min + " UM-hr-" + hr + " UM-day-" + day }, 0];          }      },      // the id of the user who entered the text @@ -292,7 +290,7 @@ export const marks: { [index: string]: MarkSpec } = {          inclusive: false,          toDOM(node: any) {              const uid = node.attrs.userid.replace(".", "").replace("@", ""); -            return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0]; +            return ['span', { class: "UT-" + uid + " UT-" + node.attrs.tag }, 0];          }      }, | 
