From 3a8f534a9bea20cedbb194d3cc9dceff6afcef37 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 22 Jun 2020 12:19:21 -0400
Subject: added a way to broadcast a message to all users (e.g., for restarting
 server)

---
 src/client/views/MainView.tsx | 5 +++++
 1 file changed, 5 insertions(+)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index fcaa349cd..d6c46e3b0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -80,6 +80,11 @@ export class MainView extends React.Component {
     componentDidMount() {
         const tag = document.createElement('script');
 
+        const proto = DocServer.GetRefField("rtfProto").then(proto => {
+            (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE),
+                msg => msg && alert(msg));
+        });
+
         tag.src = "https://www.youtube.com/iframe_api";
         const firstScriptTag = document.getElementsByTagName('script')[0];
         firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
-- 
cgit v1.2.3-70-g09d2


From 9e37056e3b1e5412e2eca9020841a56503f70d32 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 24 Jun 2020 01:46:42 -0400
Subject: changed richTextMenu to dock at top of window.  fixed ColorBox to set
 foreground color of selected text.

---
 deploy/index.html                                  |  2 +-
 src/client/views/AntimodeMenu.tsx                  |  6 ++++-
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/MetadataEntryMenu.tsx             | 26 +++++++++++++---------
 .../views/collections/CollectionDockingView.tsx    | 17 +++++++-------
 src/client/views/nodes/ColorBox.tsx                |  6 ++---
 .../views/nodes/formattedText/RichTextMenu.tsx     |  6 ++---
 7 files changed, 37 insertions(+), 28 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/deploy/index.html b/deploy/index.html
index fdfd77cc2..e441e3610 100644
--- a/deploy/index.html
+++ b/deploy/index.html
@@ -10,7 +10,7 @@
 
 <body>
     <!-- <script src="https://hypothes.is/embed.js" async></script> -->
-    <div id="root"></div>
+    <div id="root" style="position:relative"></div>
     <script src="/bundle.js"></script>
 </body>
 
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 2d26afcf6..3e4d20fea 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -147,7 +147,11 @@ export default abstract class AntimodeMenu extends React.Component {
     protected getElementWithRows(rows: JSX.Element[], numRows: number, hasDragger: boolean = true) {
         return (
             <div className="antimodeMenu-cont with-rows" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
-                style={{ left: this._left, top: this._top, opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, height: "auto" }}>
+                style={{
+                    left: this._left, top: this._top, opacity: this._opacity, transitionProperty: this._transitionProperty,
+                    transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, height: "auto",
+                    flexDirection: this.Pinned ? "row" : undefined, position: this.Pinned ? "unset" : undefined
+                }}>
                 {hasDragger ? <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: "20px" }} /> : (null)}
                 {rows}
             </div>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d6c46e3b0..0686e797e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -570,6 +570,7 @@ export class MainView extends React.Component {
             <GoogleAuthenticationManager />
             <DocumentDecorations />
             <GestureOverlay>
+                <RichTextMenu key="rich" />
                 {this.mainContent}
             </GestureOverlay>
             <PreviewCursor />
@@ -578,7 +579,6 @@ export class MainView extends React.Component {
             <PDFMenu />
             <MarqueeOptionsMenu />
             <InkOptionsMenu />
-            <RichTextMenu />
             <OverlayView />
             <TimelineMenu />
             {this.snapLines}
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index e100d3f52..b0752ffb2 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -197,19 +197,23 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
     render() {
         return (<div className="metadataEntry-outerDiv" id="metadataEntry-outer" onPointerDown={e => e.stopPropagation()}>
             <div className="metadataEntry-inputArea">
-                Key:
-                <div className="metadataEntry-autoSuggester" onClick={e => this.autosuggestRef.current!.input?.focus()}  >
-                    <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
-                        getSuggestionValue={this.getSuggestionValue}
-                        suggestions={emptyPath}
-                        alwaysRenderSuggestions={false}
-                        renderSuggestion={this.renderSuggestion}
-                        onSuggestionsFetchRequested={emptyFunction}
-                        onSuggestionsClearRequested={emptyFunction}
-                        ref={this.autosuggestRef} />
+                <div style={{ display: "flex", flexDirection: "row" }}>
+                    <span>Key:</span>
+                    <div className="metadataEntry-autoSuggester" onClick={e => this.autosuggestRef.current!.input?.focus()}  >
+                        <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
+                            getSuggestionValue={this.getSuggestionValue}
+                            suggestions={emptyPath}
+                            alwaysRenderSuggestions={false}
+                            renderSuggestion={this.renderSuggestion}
+                            onSuggestionsFetchRequested={emptyFunction}
+                            onSuggestionsClearRequested={emptyFunction}
+                            ref={this.autosuggestRef} />
+                    </div>
                 </div>
-                Value:
+                <div style={{ display: "flex", flexDirection: "row" }}>
+                    <span>Value:</span>
                     <input className="metadataEntry-input" ref={this._ref} value={this._currentValue} onClick={e => this._ref.current!.focus()} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} />
+                </div>
                 {this.considerChildOptions}
             </div>
             <div className="metadataEntry-keys" >
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index a969e302d..ee987abdb 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -11,7 +11,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { FieldId } from "../../../fields/RefField";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnOne, returnTrue, Utils, returnZero, returnEmptyFilter } from "../../../Utils";
+import { emptyFunction, returnOne, returnTrue, Utils, returnZero, returnEmptyFilter, setupMoveUpEvents, returnFalse } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { Docs } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -521,13 +521,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     tab.setActive(true);
                 };
                 const onDown = (e: React.PointerEvent) => {
-                    if (!(e.nativeEvent as any).defaultPrevented) {
-                        e.preventDefault();
-                        e.stopPropagation();
-                        const dragData = new DragManager.DocumentDragData([doc]);
-                        dragData.dropAction = doc.dropAction as dropActionType;
-                        DragManager.StartDocumentDrag([gearSpan], dragData, e.clientX, e.clientY);
-                    }
+                    setupMoveUpEvents(this, e, (e) => {
+                        if (!(e as any).defaultPrevented) {
+                            const dragData = new DragManager.DocumentDragData([doc]);
+                            dragData.dropAction = doc.dropAction as dropActionType;
+                            DragManager.StartDocumentDrag([gearSpan], dragData, e.clientX, e.clientY);
+                            return true;
+                        } return false
+                    }, returnFalse, emptyFunction);
                 };
 
                 tab.buttonDisposer = reaction(() => ((view: Opt<DocumentView>) => view ? [view] : [])(DocumentManager.Instance.getDocumentView(doc)),
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 7b7c4d266..d6d9a8cfd 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -36,11 +36,11 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument
                     view.props.Document.layout instanceof Doc ? view.props.Document.layout :
                         view.props.Document.isTemplateForField ? view.props.Document : Doc.GetProto(view.props.Document);
                 if (targetDoc) {
-                    if (view.props.LayoutTemplate || view.props.LayoutTemplateString) {  // this situation typically occurs when you have a link dot 
+                    if (view.props.LayoutTemplate?.() || view.props.LayoutTemplateString) {  // this situation typically occurs when you have a link dot 
                         targetDoc.backgroundColor = Doc.UserDoc().backgroundColor;  // bcz: don't know how to change the color of an inline template...
                     }
-                    else if (StrCast(Doc.Layout(view.props.Document).layout).indexOf("FormattedTextBox") !== -1 && FormattedTextBox.HadSelection) {
-                        Doc.Layout(view.props.Document).color = Doc.UserDoc().backgroundColor;
+                    else if (StrCast(Doc.Layout(view.props.Document).layout).includes("FormattedTextBox") && FormattedTextBox.HadSelection) {
+                        Doc.Layout(view.props.Document)[Doc.LayoutFieldKey(view.props.Document) + "-color"] = Doc.UserDoc().backgroundColor;
                     } else {
                         Doc.Layout(view.props.Document)._backgroundColor = Doc.UserDoc().backgroundColor; // '_backgroundColor' is template specific.  'backgroundColor' would apply to all templates, but has no UI at the moment
                     }
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 1a961ae21..e80951918 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -776,11 +776,11 @@ export default class RichTextMenu extends AntimodeMenu {
                 this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes")]}
             </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)` }} />
                     </button>
-                </div>
+                </div> */}
                 <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
                     <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
                 </button>
@@ -788,7 +788,7 @@ export default class RichTextMenu extends AntimodeMenu {
         </div>;
 
         return (
-            <div className="richTextMenu" onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+            <div className="richTextMenu" onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} >
                 {this.getElementWithRows([row1, row2], 2, false)}
             </div>
         );
-- 
cgit v1.2.3-70-g09d2


From 7e02ca22f9667d0bdb89b29da3c412ba52976fbf Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 24 Jun 2020 09:52:05 -0400
Subject: fixed placement of settings panel icon & panel.  fixed layout of
 docked text bar.  added blockquote icon

---
 deploy/index.html                                  |  2 +-
 src/client/views/AntimodeMenu.scss                 |  5 +-
 src/client/views/MainView.tsx                      | 54 ++++++++++++----------
 src/client/views/globalCssVariables.scss           |  2 +
 src/client/views/globalCssVariables.scss.d.ts      |  1 +
 .../views/nodes/formattedText/RichTextMenu.tsx     | 39 +++++++++-------
 6 files changed, 60 insertions(+), 43 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/deploy/index.html b/deploy/index.html
index e441e3610..29a3f15cd 100644
--- a/deploy/index.html
+++ b/deploy/index.html
@@ -10,7 +10,7 @@
 
 <body>
     <!-- <script src="https://hypothes.is/embed.js" async></script> -->
-    <div id="root" style="position:relative"></div>
+    <div id="root" style="position:relative;width:100%;height:100%"></div>
     <script src="/bundle.js"></script>
 </body>
 
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
index d4a76ee17..e56574bb7 100644
--- a/src/client/views/AntimodeMenu.scss
+++ b/src/client/views/AntimodeMenu.scss
@@ -1,7 +1,10 @@
+@import "./globalCssVariables";
+
+
 .antimodeMenu-cont {
     position: absolute;
     z-index: 10000;
-    height: 35px;
+    height: $antimodemenu-height;
     background: #323232;
     box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
     border-radius: 0px 6px 6px 6px;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 0686e797e..97953452d 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -5,8 +5,9 @@ import {
     faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
     faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
     faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
-    faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye
+    faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft
 } from '@fortawesome/free-solid-svg-icons';
+import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -134,7 +135,7 @@ export class MainView extends React.Component {
             faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
             faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
             faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
-            faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye);
+            faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
@@ -326,9 +327,7 @@ export class MainView extends React.Component {
         const width = this.flyoutWidth;
         return <Measure offset onResize={this.onResize}>
             {({ measureRef }) =>
-                <div ref={measureRef} className="mainContent-div" onDragEnter={e => {
-                    console.log("ENTERING");
-                }} onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)` }}>
+                <div ref={measureRef} className="mainContent-div" onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)` }}>
                     {!mainContainer ? (null) : this.mainDocView}
                 </div>
             }
@@ -459,28 +458,33 @@ export class MainView extends React.Component {
     @computed get mainContent() {
         const sidebar = this.userDoc?.["tabs-panelContainer"];
         return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
-            <div className="mainView-mainContent" style={{ color: this.darkScheme ? "rgb(205,205,205)" : "black" }} >
-                <div className="mainView-flyoutContainer" onPointerLeave={this.pointerLeaveDragger} style={{ width: this.flyoutWidth }}>
-                    <div className="mainView-libraryHandle" onPointerDown={this.onPointerDown} onPointerOver={this.pointerOverDragger}
-                        style={{ backgroundColor: this.defaultBackgroundColors(sidebar) }}>
-                        <span title="library View Dragger" style={{
-                            width: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "3vw",
-                            //height: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "100vh",
-                            position: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "absolute" : "fixed",
-                            top: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "" : "0"
-                        }} />
-                    </div>
-                    <div className="mainView-libraryFlyout" style={{
-                        //transformOrigin: this._flyoutTranslate ? "" : "left center",
-                        transition: this._flyoutTranslate ? "" : "width .5s",
-                        //transform: `scale(${this._flyoutTranslate ? 1 : 0.8})`,
-                        boxShadow: this._flyoutTranslate ? "" : "rgb(156, 147, 150) 0.2vw 0.2vw 0.8vw"
-                    }}>
-                        {this.flyout}
-                        {this.expandButton}
+            <div className="mainView-mainContent" style={{
+                color: this.darkScheme ? "rgb(205,205,205)" : "black",
+                height: RichTextMenu.Instance?.Pinned ? `calc(100% - ${ANTIMODEMENU_HEIGHT})` : "100%"
+            }} >
+                <div style={{ display: "contents", flexDirection: "row", position: "relative" }}>
+                    <div className="mainView-flyoutContainer" onPointerLeave={this.pointerLeaveDragger} style={{ width: this.flyoutWidth }}>
+                        <div className="mainView-libraryHandle" onPointerDown={this.onPointerDown} onPointerOver={this.pointerOverDragger}
+                            style={{ backgroundColor: this.defaultBackgroundColors(sidebar) }}>
+                            <span title="library View Dragger" style={{
+                                width: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "3vw",
+                                //height: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "100vh",
+                                position: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "absolute" : "fixed",
+                                top: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "" : "0"
+                            }} />
+                        </div>
+                        <div className="mainView-libraryFlyout" style={{
+                            //transformOrigin: this._flyoutTranslate ? "" : "left center",
+                            transition: this._flyoutTranslate ? "" : "width .5s",
+                            //transform: `scale(${this._flyoutTranslate ? 1 : 0.8})`,
+                            boxShadow: this._flyoutTranslate ? "" : "rgb(156, 147, 150) 0.2vw 0.2vw 0.8vw"
+                        }}>
+                            {this.flyout}
+                            {this.expandButton}
+                        </div>
                     </div>
+                    {this.dockingContent}
                 </div>
-                {this.dockingContent}
             </div>);
     }
 
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index 9d3d2e592..4e85fe0ca 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -12,6 +12,7 @@ $lighter-alt-accent: rgb(207, 220, 240);
 $darker-alt-accent: rgb(178, 206, 248);
 $intermediate-color: #9c9396;
 $dark-color: #121721;
+$antimodemenu-height: 35px;
 // fonts
 $sans-serif: "Noto Sans",
 sans-serif;
@@ -40,4 +41,5 @@ $MAX_ROW_HEIGHT: 44px;
     MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE;
     MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT;
     SEARCH_THUMBNAIL_SIZE: $search-thumnail-size;
+    ANTIMODEMENU_HEIGHT: $antimodemenu-height;
 }
\ No newline at end of file
diff --git a/src/client/views/globalCssVariables.scss.d.ts b/src/client/views/globalCssVariables.scss.d.ts
index d95cec9d8..a7ca4b300 100644
--- a/src/client/views/globalCssVariables.scss.d.ts
+++ b/src/client/views/globalCssVariables.scss.d.ts
@@ -5,6 +5,7 @@ interface IGlobalScss {
     MINIMIZED_ICON_SIZE: string;
     MAX_ROW_HEIGHT: string;
     SEARCH_THUMBNAIL_SIZE: string;
+    ANTIMODEMENU_HEIGHT: string;
 }
 declare const globalCssVariables: IGlobalScss;
 
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index e80951918..68101fbde 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -161,11 +161,10 @@ export default class RichTextMenu extends AntimodeMenu {
             return;
         }
         this.view = view;
-        const state = view.state;
         props && (this.editorProps = props);
 
         // Don't do anything if the document/selection didn't change
-        if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return;
+        if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return;
 
         // update active marks
         const activeMarks = this.getActiveMarksOnSelection();
@@ -173,18 +172,18 @@ export default class RichTextMenu extends AntimodeMenu {
 
         // update active font family and size
         const active = this.getActiveFontStylesOnSelection();
-        const activeFamilies = active && active.get("families");
-        const activeSizes = active && active.get("sizes");
+        const activeFamilies = active?.get("families");
+        const activeSizes = active?.get("sizes");
 
-        this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
-        this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
+        this.activeFontFamily = !activeFamilies?.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
+        this.activeFontSize = !activeSizes?.length ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) : "various";
 
         // update link in current selection
         const targetTitle = await this.getTextLinkTargetTitle();
         this.setCurrentLink(targetTitle);
     }
 
-    setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
+    setMark = (mark: Mark, state: EditorState<any>, dispatch: any, dontToggle: boolean = false) => {
         if (mark) {
             const node = (state.selection as NodeSelection).node;
             if (node?.type === schema.nodes.ordered_list) {
@@ -194,14 +193,15 @@ export default class RichTextMenu extends AntimodeMenu {
                 if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, fontColor: mark.attrs.color };
                 const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
                 dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
-            } else {
+            } else if (dontToggle) {
                 toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
                     const { from, $from, to, empty } = tx.selection;
-                    // if (!tx.doc.rangeHasMark(from, to, mark.type)) {
-                    //     toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
-                    // } else
-                    dispatch(tx);
+                    if (!tx.doc.rangeHasMark(from, to, mark.type)) { // hack -- should have just set the mark in the first place
+                        toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
+                    } else dispatch(tx);
                 });
+            } else {
+                toggleMark(mark.type, mark.attrs)(state, (tx: any) => dispatch(tx));
             }
         }
     }
@@ -368,11 +368,11 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     changeFontSize = (mark: Mark, view: EditorView) => {
-        this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch);
+        this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch, true);
     }
 
     changeFontFamily = (mark: Mark, view: EditorView) => {
-        this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch);
+        this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch, true);
     }
 
     // TODO: remove doesn't work
@@ -406,7 +406,13 @@ export default class RichTextMenu extends AntimodeMenu {
         tr.addMark(state.selection.from, state.selection.to, mark);
         const content = tr.selection.content();
         const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
-        dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
+        dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
+        return true;
+    }
+
+    insertBlockquote(state: EditorState<any>, dispatch: any) {
+        if (state.selection.empty) return false;
+        setBlockType(state.schema.nodes.blockquote)(state, (tx: any) => dispatch(tx));
         return true;
     }
 
@@ -539,7 +545,7 @@ export default class RichTextMenu extends AntimodeMenu {
             dispatch(state.tr.addStoredMark(colorMark));
             return false;
         }
-        this.setMark(colorMark, state, dispatch);
+        this.setMark(colorMark, state, dispatch, true);
     }
 
     @action toggleHighlightDropdown() { this.showHighlightDropdown = !this.showHighlightDropdown; }
@@ -766,6 +772,7 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createLinkButton(),
             this.createBrushButton(),
             this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
+            this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
         ]}</div>;
 
         const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2">
-- 
cgit v1.2.3-70-g09d2


From ab55a59f47efdd8f134a619ca0fa59203efe2fa9 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 24 Jun 2020 13:43:23 -0400
Subject: fixed summary icon

---
 src/client/views/MainView.tsx                         | 4 ++--
 src/client/views/nodes/formattedText/RichTextMenu.tsx | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 97953452d..415f8b364 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -5,7 +5,7 @@ import {
     faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
     faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
     faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
-    faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft
+    faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft, faSortAmountDown
 } from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -135,7 +135,7 @@ export class MainView extends React.Component {
             faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
             faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
             faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
-            faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft);
+            faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft, faSortAmountDown);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 5e32b3ef8..3a4363587 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -779,7 +779,7 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createHighlighterButton(),
             this.createLinkButton(),
             this.createBrushButton(),
-            this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
+            this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
             this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
         ]}</div>;
 
-- 
cgit v1.2.3-70-g09d2


From 8e9f77d93ec4393c52a215e75bcd9a362173e06a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 24 Jun 2020 16:24:37 -0400
Subject: made persistent link Icon in bottom left of document

---
 src/client/views/DocumentButtonBar.tsx             | 18 +++---
 src/client/views/DocumentDecorations.tsx           |  2 +-
 src/client/views/MainView.tsx                      |  3 +
 src/client/views/globalCssVariables.scss           |  1 +
 src/client/views/linking/LinkMenu.scss             |  4 ++
 src/client/views/linking/LinkMenu.tsx              | 37 ++++++-----
 src/client/views/nodes/DocumentLinksButton.scss    | 39 +++++++++++
 src/client/views/nodes/DocumentLinksButton.tsx     | 75 ++++++++++++++++++++++
 src/client/views/nodes/DocumentView.tsx            | 10 +--
 .../views/nodes/formattedText/RichTextMenu.tsx     |  7 +-
 10 files changed, 160 insertions(+), 36 deletions(-)
 create mode 100644 src/client/views/nodes/DocumentLinksButton.scss
 create mode 100644 src/client/views/nodes/DocumentLinksButton.tsx

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 62a95116f..208b4d57a 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -239,14 +239,14 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     get linkButton() {
         const view0 = this.view0;
         const linkCount = view0 && DocListCast(view0.props.Document.links).length;
-        return !view0 ? (null) : <div title="Drag(create link) Tap(view links)" className="documentButtonBar-linkFlyout" ref={this._linkButton}>
-            <Flyout anchorPoint={anchorPoints.LEFT_TOP}
-                content={<LinkMenu docView={view0} addDocTab={view0.props.addDocTab} changeFlyout={emptyFunction} />}>
-                <div className={"documentButtonBar-linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >
-                    {linkCount ? linkCount : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
+        return !view0 || linkCount ? (null) :
+            <div className="documentButtonBar-button">
+                <div title="Drag(create link) Tap(view links)" className="documentButtonBar-linkFlyout" ref={this._linkButton}>
+                    <div className={"documentButtonBar-linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >
+                        {linkCount ? linkCount : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
+                    </div>
                 </div>
-            </Flyout>
-        </div>;
+            </div>;
     }
 
     @computed
@@ -317,9 +317,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         const considerPull = isText && this.considerGoogleDocsPull;
         const considerPush = isText && this.considerGoogleDocsPush;
         return <div className="documentButtonBar">
-            <div className="documentButtonBar-button">
-                {this.linkButton}
-            </div>
+            {this.linkButton}
             <div className="documentButtonBar-button">
                 {this.templateButton}
             </div>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index beb6155ca..d7b0ab7a9 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -552,7 +552,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}></div>
 
             </div >
-            <div className="link-button-container" style={{ left: bounds.x - this._resizeBorderWidth / 2, top: bounds.b + this._resizeBorderWidth / 2 }}>
+            <div className="link-button-container" style={{ left: bounds.x - this._resizeBorderWidth / 2 + 10, top: bounds.b + this._resizeBorderWidth / 2 }}>
                 <DocumentButtonBar views={SelectionManager.SelectedDocuments} />
             </div>
         </div >
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 415f8b364..374fd7421 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -55,6 +55,8 @@ import { TimelineMenu } from './animationtimeline/TimelineMenu';
 import { SnappingManager } from '../util/SnappingManager';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { DocumentManager } from '../util/DocumentManager';
+import { DocumentLinksButton } from './nodes/DocumentLinksButton';
+import { LinkMenu } from './linking/LinkMenu';
 
 @observer
 export class MainView extends React.Component {
@@ -578,6 +580,7 @@ export class MainView extends React.Component {
                 {this.mainContent}
             </GestureOverlay>
             <PreviewCursor />
+            {DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
             <ContextMenu />
             <RadialMenu />
             <PDFMenu />
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index 4e85fe0ca..3e54d001b 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -12,6 +12,7 @@ $lighter-alt-accent: rgb(207, 220, 240);
 $darker-alt-accent: rgb(178, 206, 248);
 $intermediate-color: #9c9396;
 $dark-color: #121721;
+$link-color: lightBlue;
 $antimodemenu-height: 35px;
 // fonts
 $sans-serif: "Noto Sans",
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index 7dee22f66..c372e7098 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -8,6 +8,10 @@
 .linkMenu-list {
     max-height: 200px;
     overflow-y: scroll;
+    position: absolute;
+    z-index: 10;
+    background: $link-color;
+    min-width: 150px
 }
 
 .linkMenu-group {
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 56f40ad69..0fcc0f0b9 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -4,11 +4,12 @@ import { DocumentView } from "../nodes/DocumentView";
 import { LinkEditor } from "./LinkEditor";
 import './LinkMenu.scss';
 import React = require("react");
-import { Doc } from "../../../fields/Doc";
+import { Doc, Opt } from "../../../fields/Doc";
 import { LinkManager } from "../../util/LinkManager";
 import { LinkMenuGroup } from "./LinkMenuGroup";
 import { faTrash } from '@fortawesome/free-solid-svg-icons';
 import { library } from "@fortawesome/fontawesome-svg-core";
+import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
 
 library.add(faTrash);
 
@@ -16,16 +17,29 @@ interface Props {
     docView: DocumentView;
     changeFlyout: () => void;
     addDocTab: (document: Doc, where: string) => boolean;
+    location: number[];
 }
 
 @observer
 export class LinkMenu extends React.Component<Props> {
 
     @observable private _editingLink?: Doc;
+    @observable private _linkMenuRef: Opt<HTMLDivElement | null>;
 
+    @action
+    onClick = (e: PointerEvent) => {
+        if (!Array.from(this._linkMenuRef?.getElementsByTagName((e.target as HTMLElement).tagName) || []).includes(e.target as any)) {
+            DocumentLinksButton.EditLink = undefined;
+        }
+    }
     @action
     componentDidMount() {
         this._editingLink = undefined;
+        document.addEventListener("pointerdown", this.onClick);
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener("pointerdown", this.onClick);
     }
 
     clearAllLinks = () => {
@@ -56,20 +70,11 @@ export class LinkMenu extends React.Component<Props> {
     render() {
         const sourceDoc = this.props.docView.props.Document;
         const groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc);
-        if (this._editingLink === undefined) {
-            return (
-                <div className="linkMenu">
-                    {/* <button className="linkEditor-button linkEditor-clearButton" onClick={() => this.clearAllLinks()} title="Clear all links"><FontAwesomeIcon icon="trash" size="sm" /></button> */}
-                    {/* <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input> */}
-                    <div className="linkMenu-list">
-                        {this.renderAllGroups(groups)}
-                    </div>
-                </div>
-            );
-        } else {
-            return (
-                <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)}></LinkEditor>
-            );
-        }
+        return <div className="linkMenu-list" ref={(r) => this._linkMenuRef = r} style={{ left: this.props.location[0], top: this.props.location[1] }}>
+            {!this._editingLink ?
+                this.renderAllGroups(groups) :
+                <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)} />
+            }
+        </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
new file mode 100644
index 000000000..b9d5d437b
--- /dev/null
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -0,0 +1,39 @@
+@import "../globalCssVariables.scss";
+
+
+.documentLinksButton-button-empty:hover {
+    background: $main-accent;
+    transform: scale(1.05);
+    cursor: pointer;
+}
+
+.documentLinksButton-button-nonempty:hover {
+    background: $main-accent;
+    transform: scale(1.05);
+    cursor: pointer;
+}
+
+.documentLinksButton-button-empty,
+.documentLinksButton-button-nonempty {
+    height: 20px;
+    width: 20px;
+    border-radius: 50%;
+    opacity: 0.9;
+    pointer-events: auto;
+    background-color: $link-color;
+    color: black;
+    text-transform: uppercase;
+    letter-spacing: 2px;
+    font-size: 75%;
+    transition: transform 0.2s;
+    text-align: center;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    &:hover {
+        background: $main-accent;
+        transform: scale(1.05);
+        cursor: pointer;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
new file mode 100644
index 000000000..4f94b06cb
--- /dev/null
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -0,0 +1,75 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { computed, action, runInAction, observable } from "mobx";
+import { observer } from "mobx-react";
+import './DocumentLinksButton.scss';
+import React = require("react");
+import { emptyFunction, setupMoveUpEvents } from "../../../Utils";
+import { DocListCast, Doc } from "../../../fields/Doc";
+import { DocumentView } from "./DocumentView";
+import { LinkMenu } from "../linking/LinkMenu";
+import { UndoManager } from "../../util/UndoManager";
+import { DragManager } from "../../util/DragManager";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+
+interface DocumentLinksButtonProps {
+    View: DocumentView;
+}
+@observer
+export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
+    private _linkButton = React.createRef<HTMLDivElement>();
+
+    @action
+    onLinkButtonMoved = (e: PointerEvent) => {
+        if (this._linkButton.current !== null) {
+            const linkDrag = UndoManager.StartBatch("Drag Link");
+            this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View.props.Document, e.pageX, e.pageY, {
+                dragComplete: dropEv => {
+                    const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
+                    if (this.props.View && linkDoc) {
+                        !linkDoc.linkRelationship && (Doc.GetProto(linkDoc).linkRelationship = "hyperlink");
+
+                        // we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks)
+                        // the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures
+                        // however, the dropped document isn't so accessible.  What we do is set the newly created link document on the documentView
+                        // The documentView passes a function prop returning this link doc to its descendants who can react to changes to it.
+                        dropEv.linkDragData?.linkDropCallback?.(dropEv.linkDragData);
+                        runInAction(() => this.props.View._link = linkDoc);
+                        setTimeout(action(() => this.props.View._link = undefined), 0);
+                    }
+                    linkDrag?.end();
+                },
+                hideSource: false
+            });
+            return true;
+        }
+        return false;
+    }
+
+    onLinkButtonDown = (e: React.PointerEvent): void => {
+        setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e) => {
+            DocumentLinksButton.EditLink = this.props.View;
+            DocumentLinksButton.EditLinkLoc = [e.clientX, e.clientY];
+        }));
+    }
+
+    @observable
+    public static EditLink: DocumentView | undefined;
+    public static EditLinkLoc: number[] = [0, 0];
+
+    @computed
+    get linkButton() {
+        const view0 = this.props.View;
+        const linkCount = view0 && DocListCast(view0.props.Document.links).length;
+        return !view0 || !linkCount ? (null) :
+            <div title="Drag(create link) Tap(view links)" style={{ position: "absolute", left: -15, bottom: -15 }} ref={this._linkButton}>
+                <div className={"documentLinksButton-button-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >
+                    {linkCount ? linkCount : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
+                </div>
+            </div>;
+    }
+    render() {
+        return this.linkButton;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7e20b40a3..d9a211e7a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -41,6 +41,7 @@ import "./DocumentView.scss";
 import { LinkAnchorBox } from './LinkAnchorBox';
 import { RadialMenu } from './RadialMenu';
 import React = require("react");
+import { DocumentLinksButton } from './DocumentLinksButton';
 
 library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
     fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -735,10 +736,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
         const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
         templateDoc && optionItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
-        if (!options) {
-            options = { description: "Options...", subitems: optionItems, icon: "compass" };
-            cm.addItem(options);
-        }
+        optionItems.push({ description: "Toggle Show Each Link Dot", event: () => this.layoutDoc.showLinks = !this.layoutDoc.showLinks, icon: "eye" });
+        !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
 
         const existingOnClick = cm.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
@@ -1037,7 +1036,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 select={this.select}
                 onClick={this.onClickHandler}
                 layoutKey={this.finalLayoutKey} />
-            {this.anchors}
+            {this.layoutDoc.showLinks ? this.anchors : (null)}
+            {this.props.forcedBackgroundColor?.(this.Document) === "transparent" ? (null) : <DocumentLinksButton View={this} />}
         </div>
         );
     }
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 3a4363587..839943aac 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 } from "../../../../fields/Types";
+import { Cast, StrCast, BoolCast } from "../../../../fields/Types";
 import { unimplementedFunction, Utils } from "../../../../Utils";
 import { DocServer } from "../../../DocServer";
 import { LinkManager } from "../../../util/LinkManager";
@@ -72,7 +72,7 @@ export default class RichTextMenu extends AntimodeMenu {
         super(props);
         RichTextMenu.Instance = this;
         this._canFade = false;
-        this.Pinned = true;
+        this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]);
 
         this.fontSizeOptions = [
             { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
@@ -147,7 +147,6 @@ export default class RichTextMenu extends AntimodeMenu {
         this.updateFromDash(view, lastState, this.editorProps);
     }
 
-
     public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => {
         if (this.view) {
             const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId });
@@ -750,7 +749,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
     @action
     toggleMenuPin = (e: React.MouseEvent) => {
-        this.Pinned = !this.Pinned;
+        Doc.UserDoc()["menuRichText-pinned"] = this.Pinned = !this.Pinned;
         if (!this.Pinned) {
             this.fadeOut(true);
         }
-- 
cgit v1.2.3-70-g09d2


From ba42ca25ae828b6279f618dea87f7cd37a3b3814 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 25 Jun 2020 00:19:40 -0400
Subject: added link preview for blue buttons.

---
 src/client/util/DragManager.ts                  |   2 +-
 src/client/views/DocumentButtonBar.tsx          |   2 +-
 src/client/views/MainView.tsx                   |   4 +
 src/client/views/nodes/DocumentLinksButton.scss |  28 ++-----
 src/client/views/nodes/DocumentLinksButton.tsx  |  15 +++-
 src/client/views/nodes/DocumentView.tsx         |  10 ++-
 src/client/views/nodes/LinkAnchorBox.tsx        |   8 ++
 src/client/views/nodes/LinkDocPreview.tsx       | 107 ++++++++++++++++++++++++
 8 files changed, 148 insertions(+), 28 deletions(-)
 create mode 100644 src/client/views/nodes/LinkDocPreview.tsx

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 91bc51101..417ddf989 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -353,7 +353,7 @@ export namespace DragManager {
             const dragElement = ele.parentNode === dragDiv ? ele : ele.cloneNode(true) as HTMLElement;
             const rect = ele.getBoundingClientRect();
             const scaleX = rect.width / ele.offsetWidth,
-                scaleY = rect.height / ele.offsetHeight;
+                scaleY = ele.offsetHeight ? rect.height / ele.offsetHeight : scaleX;
             elesCont.left = Math.min(rect.left, elesCont.left);
             elesCont.top = Math.min(rect.top, elesCont.top);
             elesCont.right = Math.max(rect.right, elesCont.right);
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 8cd1b650e..106b7163e 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -248,7 +248,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     get linkButton() {
         const view0 = this.view0;
         const linkCount = view0 && DocListCast(view0.props.Document.links).length;
-        return !view0 || linkCount ? (null) :
+        return !view0 ? (null) :
             <div className="documentButtonBar-button">
                 <div title="Drag(create link) Tap(view links)" className="documentButtonBar-linkFlyout" ref={this._linkButton}>
                     <div className={"documentButtonBar-linkButton-" + (linkCount ? "nonempty" : "empty")}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 374fd7421..4e2c067ac 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -57,6 +57,7 @@ import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { DocumentManager } from '../util/DocumentManager';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 import { LinkMenu } from './linking/LinkMenu';
+import { LinkDocPreview } from './nodes/LinkDocPreview';
 
 @observer
 export class MainView extends React.Component {
@@ -581,6 +582,9 @@ export class MainView extends React.Component {
             </GestureOverlay>
             <PreviewCursor />
             {DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
+            {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
+                linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
+                addDocTab={LinkDocPreview.LinkInfo.addDocTab} /> : (null)}
             <ContextMenu />
             <RadialMenu />
             <PDFMenu />
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index b4c59b91c..79364ab8e 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -1,21 +1,9 @@
 @import "../globalCssVariables.scss";
 
 
-.documentLinksButton-button-empty:hover {
-    background: $main-accent;
-    transform: scale(1.05);
-    cursor: pointer;
-}
-
-.documentLinksButton-button-nonempty:hover {
-    background: $main-accent;
-    transform: scale(1.05);
-    cursor: pointer;
-}
-
-.documentLinksButton-button,
-.documentLinksButton-button-empty,
-.documentLinksButton-button-nonempty {
+.documentLinksButton,
+.documentLinksButton-endLink,
+.documentLinksButton-startLink {
     height: 20px;
     width: 20px;
     left: -15px;
@@ -34,18 +22,18 @@
     align-items: center;
 
     &:hover {
-        background: $main-accent;
+        background: deepskyblue;
         transform: scale(1.05);
-        cursor: pointer;
+        cursor: default;
     }
 }
-.documentLinksButton-button-nonempty {
+.documentLinksButton {
     background-color: $link-color;
 }
-.documentLinksButton-button-empty {
+.documentLinksButton-endLink {
     border: red solid 2px;
 }
-.documentLinksButton-button {
+.documentLinksButton-startLink {
     border: red solid 2px;
     background-color: rgba(255, 192, 203, 0.5);
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index c9083a65f..fc314ae8b 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -9,6 +9,7 @@ import { DocumentView } from "./DocumentView";
 import React = require("react");
 import { DocUtils } from "../../documents/Documents";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { LinkDocPreview } from "./LinkDocPreview";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -80,11 +81,19 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         const links = DocListCast(this.props.View.props.Document.links);
         return !links.length || links[0].hidden ? (null) :
             <div title="Drag(create link) Tap(view links)" ref={this._linkButton}>
-                <div className={"documentLinksButton-button-nonempty"} onPointerDown={this.onLinkButtonDown} >
+                <div className={"documentLinksButton"} style={{ backgroundColor: DocumentLinksButton.StartLink ? "transparent" : "" }}
+                    onPointerDown={this.onLinkButtonDown}
+                    onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
+                    onPointerEnter={action(e => LinkDocPreview.LinkInfo = {
+                        addDocTab: this.props.View.props.addDocTab,
+                        linkSrc: this.props.View.props.Document,
+                        linkDoc: links[0],
+                        Location: [e.clientX, e.clientY + 20]
+                    })} >
                     {links.length ? links.length : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
                 </div>
-                {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-button-empty"} onPointerDown={this.completeLink} /> : (null)}
-                {DocumentLinksButton.StartLink === this.props.View ? <div className={"documentLinksButton-button"} /> : (null)}
+                {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"} onPointerDown={this.completeLink} /> : (null)}
+                {DocumentLinksButton.StartLink === this.props.View ? <div className={"documentLinksButton-startLink"} /> : (null)}
             </div>;
     }
     render() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 20a606937..4cb08bab3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1047,7 +1047,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 onClick={this.onClickHandler}
                 layoutKey={this.finalLayoutKey} />
             {this.layoutDoc.showLinks ? this.anchors : (null)}
-            {this.props.forcedBackgroundColor?.(this.Document) === "transparent" ? (null) : <DocumentLinksButton View={this} />}
+            {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.props.dontRegisterView ? (null) : <DocumentLinksButton View={this} />}
         </div>
         );
     }
@@ -1071,7 +1071,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     anchorPanelHeight = () => this.props.PanelHeight() || 1;
     @computed get anchors() {
         TraceMobx();
-        return this.layoutDoc.presBox ? (null) : DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
+        return this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.layoutDoc.presBox || this.props.dontRegisterView ? (null) : DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
             <DocumentView {...this.props} key={i + 1}
                 Document={d}
                 ContainingCollectionView={this.props.ContainingCollectionView}
@@ -1079,6 +1079,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 PanelWidth={this.anchorPanelWidth}
                 PanelHeight={this.anchorPanelHeight}
                 ContentScaling={returnOne}
+                dontRegisterView={false}
                 forcedBackgroundColor={returnTransparent}
                 removeDocument={this.hideLinkAnchor}
                 pointerEvents={false}
@@ -1202,7 +1203,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 color: StrCast(this.layoutDoc.color, "inherit"),
                 outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
                 border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
-                boxShadow: this.Document.isLinkButton ? StrCast(this.props.Document._linkButtonShadow, "lightblue 0em 0em 1em") : this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined,
+                boxShadow: this.Document.isLinkButton && !this.props.dontRegisterView && this.props.forcedBackgroundColor?.(this.Document) !== "transparent" ?
+                    StrCast(this.props.Document._linkButtonShadow, "lightblue 0em 0em 1em") :
+                    this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" :
+                        undefined,
                 background: finalColor,
                 opacity: finalOpacity,
                 fontFamily: StrCast(this.Document._fontFamily, "inherit"),
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 2b64cdab6..2bcc6168b 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -18,6 +18,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { SelectionManager } from "../../util/SelectionManager";
 import { TraceMobx } from "../../../fields/util";
 import { Id } from "../../../fields/FieldSymbols";
+import { LinkDocPreview } from "./LinkDocPreview";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -132,6 +133,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
         );
         const small = this.props.PanelWidth() <= 1;
         return <div className={`linkAnchorBox-cont${small ? "-small" : ""} ${this.rootDoc[Id]}`}
+            onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
+            onPointerEnter={action(e => LinkDocPreview.LinkInfo = {
+                addDocTab: this.props.addDocTab,
+                linkSrc: this.props.ContainingCollectionDoc!,
+                linkDoc: this.rootDoc,
+                Location: [e.clientX, e.clientY + 20]
+            })}
             onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu}
             ref={this._ref} style={{
                 background: c,
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
new file mode 100644
index 000000000..126dc240a
--- /dev/null
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -0,0 +1,107 @@
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from "mobx-react";
+import wiki from "wikijs";
+import { Doc, DocCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { Cast, FieldValue, NumCast } from "../../../fields/Types";
+import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../../Utils";
+import { Docs } from "../../documents/Documents";
+import { DocumentManager } from "../../util/DocumentManager";
+import { Transform } from "../../util/Transform";
+import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
+import React = require("react");
+import { DocumentView } from './DocumentView';
+
+interface Props {
+    linkDoc?: Doc;
+    linkSrc?: Doc;
+    href?: string;
+    backgroundColor: (doc: Doc) => string;
+    addDocTab: (document: Doc, where: string) => boolean;
+    location: number[];
+}
+@observer
+export class LinkDocPreview extends React.Component<Props> {
+    @observable public static LinkInfo: Opt<{ linkDoc?: Doc; addDocTab: (document: Doc, where: string) => boolean, linkSrc: Doc; href?: string; Location: number[] }>;
+    @observable _targetDoc: Opt<Doc>;
+    @observable _toolTipText = "";
+
+    componentDidUpdate() { this.updatePreview() }
+    componentDidMount() { this.updatePreview() }
+    async updatePreview() {
+        const linkDoc = this.props.linkDoc;
+        const linkSrc = this.props.linkSrc;
+        if (this.props.href) {
+            if (this.props.href.startsWith("https://en.wikipedia.org/wiki/")) {
+                wiki().page(this.props.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500))));
+            } else {
+                runInAction(() => this._toolTipText = "external => " + this.props.href);
+            }
+        } else if (linkDoc && linkSrc) {
+            const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), linkSrc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
+            const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
+            runInAction(() => {
+                this._toolTipText = "";
+                this._targetDoc = target;
+                if (anchor !== this._targetDoc && anchor && this._targetDoc) {
+                    this._targetDoc._scrollY = NumCast(anchor?.y);
+                }
+            });
+        }
+    }
+    pointerDown = (e: React.PointerEvent) => {
+        if (this.props.linkDoc && this.props.linkSrc) {
+            DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.linkSrc,
+                (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+        } else if (this.props.href) {
+            this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, UseCors: true }), "onRight");
+        }
+    }
+    width = () => Math.min(350, NumCast(this._targetDoc?.[WidthSym](), 350));
+    height = () => Math.min(350, NumCast(this._targetDoc?.[HeightSym](), 350));
+    @computed get targetDocView() {
+        return !this._targetDoc ?
+            <div style={{ pointerEvents: "all", maxWidth: 350, maxHeight: 250, width: "100%", height: "100%", overflow: "hidden" }}>
+                <div style={{ width: "100%", height: "100%", textOverflow: "ellipsis", }} onPointerDown={this.pointerDown}>
+                    {this._toolTipText}
+                </div>
+            </div> :
+            <ContentFittingDocumentView
+                Document={this._targetDoc}
+                LibraryPath={emptyPath}
+                fitToBox={true}
+                backgroundColor={this.props.backgroundColor}
+                moveDocument={returnFalse}
+                rootSelected={returnFalse}
+                ScreenToLocalTransform={Transform.Identity}
+                parentActive={returnFalse}
+                addDocument={returnFalse}
+                removeDocument={returnFalse}
+                addDocTab={returnFalse}
+                pinToPres={returnFalse}
+                dontRegisterView={true}
+                docFilters={returnEmptyFilter}
+                ContainingCollectionDoc={undefined}
+                ContainingCollectionView={undefined}
+                renderDepth={0}
+                PanelWidth={this.width}
+                PanelHeight={this.height}
+                focus={emptyFunction}
+                whenActiveChanged={returnFalse}
+                bringToFront={returnFalse}
+                ContentScaling={returnOne}
+                NativeWidth={returnZero}
+                NativeHeight={returnZero}
+            />;
+    }
+
+    render() {
+        return <div className="linkDocPreview"
+            style={{
+                position: "absolute", left: this.props.location[0],
+                top: this.props.location[1], width: this.width(), height: this.height(),
+                boxShadow: "black 2px 2px 1em"
+            }}>
+            {this.targetDocView}
+        </div>;
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2

