aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorusodhi <61431818+usodhi@users.noreply.github.com>2020-06-25 18:53:08 +0530
committerusodhi <61431818+usodhi@users.noreply.github.com>2020-06-25 18:53:08 +0530
commitbb23d4e546a305d15cd5a7e7443a2bff8a17d1f6 (patch)
treefda76301de5f40da0ad557f644cfcb96f66bba78
parent1f1d636529c8250fb7439e82192a77740c596d11 (diff)
parentc692276b6c6909d6cf32b620eb69b114057662ca (diff)
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into acls_uv
-rw-r--r--src/client/util/DragManager.ts4
-rw-r--r--src/client/views/DocumentButtonBar.tsx63
-rw-r--r--src/client/views/DocumentDecorations.tsx2
-rw-r--r--src/client/views/GlobalKeyHandler.ts2
-rw-r--r--src/client/views/MainView.tsx11
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx3
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx4
-rw-r--r--src/client/views/globalCssVariables.scss1
-rw-r--r--src/client/views/linking/LinkMenu.scss4
-rw-r--r--src/client/views/linking/LinkMenu.tsx37
-rw-r--r--src/client/views/nodes/DocumentLinksButton.scss38
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx103
-rw-r--r--src/client/views/nodes/DocumentView.tsx28
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx8
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx107
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx5
-rw-r--r--src/client/views/nodes/formattedText/ParagraphNodeSpec.ts2
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx57
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts20
-rw-r--r--src/client/views/nodes/formattedText/prosemirrorPatches.js4
20 files changed, 386 insertions, 117 deletions
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 533bc8485..417ddf989 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -218,7 +218,7 @@ export namespace DragManager {
docDragData.dropAction !== "same" && docDragData.droppedDocuments.forEach((drop: Doc, i: number) => {
const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []);
const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps));
- remProps.map(prop => drop[prop] = undefined)
+ remProps.map(prop => drop[prop] = undefined);
});
batch.end();
}
@@ -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 62a95116f..49c15f764 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,28 +1,28 @@
import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowAltCircleDown, faPhotoVideo, faArrowAltCircleUp, faArrowAltCircleRight, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { faArrowAltCircleDown, faArrowAltCircleRight, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faPhotoVideo, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast } from "../../fields/Doc";
import { RichTextField } from '../../fields/RichTextField';
-import { NumCast, StrCast, Cast } from "../../fields/Types";
+import { Cast, NumCast } from "../../fields/Types";
import { emptyFunction, setupMoveUpEvents } from "../../Utils";
+import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
+import { Docs, DocUtils } from '../documents/Documents';
+import { DragManager } from '../util/DragManager';
import { UndoManager } from "../util/UndoManager";
import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView';
import { ParentDocSelector } from './collections/ParentDocumentSelector';
import './collections/ParentDocumentSelector.scss';
import './DocumentButtonBar.scss';
-import { LinkMenu } from "./linking/LinkMenu";
+import { MetadataEntryMenu } from './MetadataEntryMenu';
import { DocumentView } from './nodes/DocumentView';
import { GoogleRef } from "./nodes/formattedText/FormattedTextBox";
import { TemplateMenu } from "./TemplateMenu";
import { Template, Templates } from "./Templates";
import React = require("react");
-import { DragManager } from '../util/DragManager';
-import { MetadataEntryMenu } from './MetadataEntryMenu';
-import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
-import { Docs } from '../documents/Documents';
+import { DocumentLinksButton } from './nodes/DocumentLinksButton';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -113,39 +113,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
get view0() { return this.props.views()?.[0]; }
- @action
- onLinkButtonMoved = (e: PointerEvent) => {
- if (this._linkButton.current !== null) {
- const linkDrag = UndoManager.StartBatch("Drag Link");
- this.view0 && DragManager.StartLinkDrag(this._linkButton.current, this.view0.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.view0 && 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.view0!._link = linkDoc);
- setTimeout(action(() => this.view0!._link = undefined), 0);
- }
- linkDrag?.end();
- },
- hideSource: false
- });
- return true;
- }
- return false;
- }
-
-
- onLinkButtonDown = (e: React.PointerEvent): void => {
- setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, emptyFunction);
- }
-
-
@computed
get considerGoogleDocsPush() {
const targetDoc = this.view0?.props.Document;
@@ -236,20 +203,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
}
@computed
- 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" />}
- </div>
- </Flyout>
- </div>;
- }
-
- @computed
get metadataButton() {
const view0 = this.view0;
return !view0 ? (null) : <div title="Show metadata panel" className="documentButtonBar-linkFlyout">
@@ -318,7 +271,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const considerPush = isText && this.considerGoogleDocsPush;
return <div className="documentButtonBar">
<div className="documentButtonBar-button">
- {this.linkButton}
+ <DocumentLinksButton View={this.view0!} />
</div>
<div className="documentButtonBar-button">
{this.templateButton}
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/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 27755737e..7bc8cf6a7 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -19,6 +19,7 @@ import { MarqueeView } from "./collections/collectionFreeForm/MarqueeView";
import { DocumentDecorations } from "./DocumentDecorations";
import { MainView } from "./MainView";
import { DocumentView } from "./nodes/DocumentView";
+import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
const modifiers = ["control", "meta", "shift", "alt"];
type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -77,6 +78,7 @@ export default class KeyManager {
// MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI
break;
case "escape":
+ DocumentLinksButton.StartLink = undefined;
const main = MainView.Instance;
Doc.SetSelectedTool(InkTool.None);
if (main.isPointerDown) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 885e50aba..055e39669 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';
@@ -56,6 +56,9 @@ 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';
+import { LinkDocPreview } from './nodes/LinkDocPreview';
@observer
export class MainView extends React.Component {
@@ -136,7 +139,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();
}
@@ -585,6 +588,10 @@ 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)}
+ {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/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index ee987abdb..79c577b6d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -527,7 +527,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
dragData.dropAction = doc.dropAction as dropActionType;
DragManager.StartDocumentDrag([gearSpan], dragData, e.clientX, e.clientY);
return true;
- } return false
+ }
+ return false;
}, returnFalse, emptyFunction);
};
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3a8778c14..936174b52 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -47,6 +47,7 @@ import { Timeline } from "../../animationtimeline/Timeline";
import { SnappingManager } from "../../../util/SnappingManager";
import { ActiveInkColor, ActiveInkWidth, ActiveInkBezierApprox } from "../../InkingStroke";
import { DocumentType } from "../../../documents/DocumentTypes";
+import { DocumentLinksButton } from "../../nodes/DocumentLinksButton";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -245,7 +246,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
// const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
// this.props.addDocument(source);
// linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
- return false;
+ return false;
} else {
const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
this.props.addDocument(source);
@@ -589,6 +590,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
onClick = (e: React.MouseEvent) => {
if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
if (Date.now() - this._lastTap < 300) {
+ runInAction(() => DocumentLinksButton.StartLink = undefined);
const docpt = this.getTransform().transformPoint(e.clientX, e.clientY);
this.scaleAtPt(docpt, 1);
e.stopPropagation();
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..484f8c469
--- /dev/null
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -0,0 +1,38 @@
+@import "../globalCssVariables.scss";
+
+
+.documentLinksButton,
+.documentLinksButton-endLink,
+.documentLinksButton-startLink {
+ height: 20px;
+ width: 20px;
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0.9;
+ pointer-events: auto;
+ 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: deepskyblue;
+ transform: scale(1.05);
+ cursor: default;
+ }
+}
+.documentLinksButton {
+ background-color: $link-color;
+}
+.documentLinksButton-endLink {
+ border: red solid 2px;
+}
+.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
new file mode 100644
index 000000000..ef900fff6
--- /dev/null
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -0,0 +1,103 @@
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, DocListCast } from "../../../fields/Doc";
+import { emptyFunction, setupMoveUpEvents, returnFalse } from "../../../Utils";
+import { DragManager } from "../../util/DragManager";
+import { UndoManager } from "../../util/UndoManager";
+import './DocumentLinksButton.scss';
+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;
+
+interface DocumentLinksButtonProps {
+ View: DocumentView;
+ Offset?: number[];
+}
+@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;
+ }
+
+ @observable static StartLink: DocumentView | undefined;
+ onLinkButtonDown = (e: React.PointerEvent): void => {
+ setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
+ if (doubleTap) {
+ DocumentLinksButton.StartLink = this.props.View;
+ } else {
+ DocumentLinksButton.EditLink = this.props.View;
+ DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
+ }
+ }));
+ }
+ completeLink = (e: React.PointerEvent): void => {
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e, doubleTap) => {
+ if (doubleTap) {
+ if (DocumentLinksButton.StartLink === this.props.View) {
+ DocumentLinksButton.StartLink = undefined;
+ } else {
+ DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
+ DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+ }
+ }
+ }));
+ }
+
+ @observable
+ public static EditLink: DocumentView | undefined;
+ public static EditLinkLoc: number[] = [0, 0];
+
+ @computed
+ get linkButton() {
+ 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} style={{ position: "absolute", left: this.props.Offset?.[0] }}>
+ <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-endLink"} onPointerDown={this.completeLink} /> : (null)}
+ {DocumentLinksButton.StartLink === this.props.View ? <div className={"documentLinksButton-startLink"} /> : (null)}
+ </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 4e6038f86..8178c8aa7 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,
@@ -583,10 +584,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
toggleLinkButtonBehavior = (): void => {
if (this.Document.isLinkButton || this.layoutDoc.onClick || this.Document.ignoreClick) {
this.Document.isLinkButton = false;
+ const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
+ first && (first.hidden = false);
this.Document.ignoreClick = false;
this.layoutDoc.onClick = undefined;
} else {
this.Document.isLinkButton = true;
+ const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
+ first && (first.hidden = true);
this.Document.followLinkZoom = false;
this.Document.followLinkLocation = undefined;
}
@@ -596,8 +601,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
toggleFollowInPlace = (): void => {
if (this.Document.isLinkButton) {
this.Document.isLinkButton = false;
+ const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
+ first && (first.hidden = false);
} else {
this.Document.isLinkButton = true;
+ const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
+ first && (first.hidden = true);
this.Document.followLinkZoom = true;
this.Document.followLinkLocation = "inPlace";
}
@@ -607,6 +616,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
toggleFollowOnRight = (): void => {
if (this.Document.isLinkButton) {
this.Document.isLinkButton = false;
+ const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
+ first && (first.hidden = false);
} else {
this.Document.isLinkButton = true;
this.Document.followLinkZoom = false;
@@ -735,10 +746,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 : [];
@@ -1038,7 +1047,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" || this.props.dontRegisterView ? (null) : <DocumentLinksButton View={this} Offset={[-15, 0]} />}
</div>
);
}
@@ -1062,7 +1072,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}
@@ -1070,6 +1080,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}
@@ -1193,7 +1204,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.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
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e01144f82..cdf231985 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -933,7 +933,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
});
- // applyDevTools.applyDevTools(this._editorView);
+ //applyDevTools.applyDevTools(this._editorView);
const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
if (startupText) {
const { state: { tr }, dispatch } = this._editorView;
@@ -1043,11 +1043,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (bounds && this.layoutDoc._chromeStatus !== "disabled") {
const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width);
let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height);
- console.log("y = " + y + " hgt = " + RichTextMenu.Instance.height + " cords = " + coords.top);
if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) {
y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height);
}
- RichTextMenu.Instance.jumpTo(x, y);
+ setTimeout(() => window.document.activeElement === this.ProseRef?.children[0] && RichTextMenu.Instance.jumpTo(x, y), 250);
}
}
onPointerWheel = (e: React.WheelEvent): void => {
diff --git a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
index d80e64634..30da91710 100644
--- a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
+++ b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
@@ -28,7 +28,7 @@ const ALIGN_PATTERN = /(left|right|center|justify)/;
// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
// :: NodeSpec A plain paragraph textblock. Represented in the DOM
// as a `<p>` element.
-const ParagraphNodeSpec: NodeSpec = {
+export const ParagraphNodeSpec: NodeSpec = {
attrs: {
align: { default: null },
color: { default: null },
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 68101fbde..839943aac 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,30 +1,33 @@
import React = require("react");
-import AntimodeMenu from "../../AntimodeMenu";
-import { observable, action, } from "mobx";
+import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
+import { faBold, faCaretDown, faChevronLeft, faEyeDropper, faHighlighter, faIndent, faItalic, faLink, faPaintRoller, faPalette, faStrikethrough, faSubscript, faSuperscript, faUnderline } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, observable } from "mobx";
import { observer } from "mobx-react";
-import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model";
-import { schema } from "./schema_rts";
-import { EditorView } from "prosemirror-view";
+import { lift, wrapIn } from "prosemirror-commands";
+import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model";
+import { wrapInList } from "prosemirror-schema-list";
import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller, faSleigh } from "@fortawesome/free-solid-svg-icons";
-import { updateBullets } from "./ProsemirrorExampleTransfer";
-import { FieldViewProps } from "../FieldView";
-import { Cast, StrCast } from "../../../../fields/Types";
-import { FormattedTextBoxProps, FormattedTextBox } from "./FormattedTextBox";
+import { EditorView } from "prosemirror-view";
+import { Doc } from "../../../../fields/Doc";
+import { DarkPastelSchemaPalette, PastelSchemaPalette } from '../../../../fields/SchemaHeaderField';
+import { Cast, StrCast, BoolCast } from "../../../../fields/Types";
import { unimplementedFunction, Utils } from "../../../../Utils";
-import { wrapInList } from "prosemirror-schema-list";
-import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../../../fields/SchemaHeaderField';
-import "./RichTextMenu.scss";
import { DocServer } from "../../../DocServer";
-import { Doc } from "../../../../fields/Doc";
-import { SelectionManager } from "../../../util/SelectionManager";
import { LinkManager } from "../../../util/LinkManager";
-const { toggleMark, setBlockType } = require("prosemirror-commands");
+import { SelectionManager } from "../../../util/SelectionManager";
+import AntimodeMenu from "../../AntimodeMenu";
+import { FieldViewProps } from "../FieldView";
+import { FormattedTextBox, FormattedTextBoxProps } from "./FormattedTextBox";
+import { updateBullets } from "./ProsemirrorExampleTransfer";
+import "./RichTextMenu.scss";
+import { schema } from "./schema_rts";
+import { TraceMobx } from "../../../../fields/util";
+const { toggleMark } = require("prosemirror-commands");
library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
+
@observer
export default class RichTextMenu extends AntimodeMenu {
static Instance: RichTextMenu;
@@ -69,6 +72,7 @@ export default class RichTextMenu extends AntimodeMenu {
super(props);
RichTextMenu.Instance = this;
this._canFade = false;
+ 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 },
@@ -143,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 });
@@ -201,7 +204,7 @@ export default class RichTextMenu extends AntimodeMenu {
} else dispatch(tx);
});
} else {
- toggleMark(mark.type, mark.attrs)(state, (tx: any) => dispatch(tx));
+ toggleMark(mark.type, mark.attrs)(state, dispatch);
}
}
}
@@ -411,8 +414,12 @@ export default class RichTextMenu extends AntimodeMenu {
}
insertBlockquote(state: EditorState<any>, dispatch: any) {
- if (state.selection.empty) return false;
- setBlockType(state.schema.nodes.blockquote)(state, (tx: any) => dispatch(tx));
+ const path = (state.selection.$from as any).path;
+ if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) {
+ lift(state, dispatch);
+ } else {
+ wrapIn(schema.nodes.blockquote)(state, dispatch);
+ }
return true;
}
@@ -742,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);
}
@@ -758,7 +765,7 @@ export default class RichTextMenu extends AntimodeMenu {
}
render() {
-
+ TraceMobx();
const row1 = <div className="antimodeMenu-row" key="row1" style={{ display: this.collapsed ? "none" : undefined }}>{[
!this.collapsed ? this.getDragger() : (null),
this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
@@ -771,7 +778,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>;
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 1a292d9af..0a867912f 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -1,7 +1,7 @@
import React = require("react");
import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import ParagraphNodeSpec from "./ParagraphNodeSpec";
+import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from "./ParagraphNodeSpec";
const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -32,13 +32,29 @@ export const nodes: { [index: string]: NodeSpec } = {
// :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
blockquote: {
- content: "block+",
+ content: "block*",
group: "block",
defining: true,
parseDOM: [{ tag: "blockquote" }],
toDOM() { return blockquoteDOM; }
},
+
+ // blockquote: {
+ // ...ParagraphNodeSpec,
+ // defining: true,
+ // parseDOM: [{
+ // tag: "blockquote", getAttrs(dom: any) {
+ // return getParagraphNodeAttrs(dom);
+ // }
+ // }],
+ // toDOM(node: any) {
+ // const dom = toParagraphDOM(node);
+ // (dom as any)[0] = 'blockquote';
+ // return dom;
+ // },
+ // },
+
// :: NodeSpec A horizontal rule (`<hr>`).
horizontal_rule: {
group: "block",
diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js
index 269423482..763961958 100644
--- a/src/client/views/nodes/formattedText/prosemirrorPatches.js
+++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js
@@ -136,4 +136,6 @@ function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWith
(!joinPredicate || joinPredicate(match, before))) { tr.join(start - 1); }
return tr
})
-} \ No newline at end of file
+}
+
+