aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx18
-rw-r--r--src/client/views/nodes/ColorBox.tsx4
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx6
-rw-r--r--src/client/views/nodes/ContentFittingDocumentView.tsx48
-rw-r--r--src/client/views/nodes/DocHolderBox.tsx9
-rw-r--r--src/client/views/nodes/DocumentView.tsx31
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx2
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx3
-rw-r--r--src/client/views/nodes/VideoBox.tsx2
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx4
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.scss2
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx26
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss43
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx56
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx65
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts1
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx8
-rw-r--r--src/client/views/nodes/formattedText/RichTextSchema.tsx4
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts28
19 files changed, 251 insertions, 109 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 910aa744d..f934945a6 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -14,6 +14,7 @@ import { List } from "../../../fields/List";
import { numberRange } from "../../../Utils";
import { ComputedField } from "../../../fields/ScriptField";
import { listSpec } from "../../../fields/Schema";
+import { DocumentType } from "../../documents/DocumentTypes";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
@@ -36,7 +37,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
return min + rnd * (max - min);
}
get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive
- get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${this.random(-1, 1) * this.props.jitterRotation}deg)`; }
+ get maskCentering() { return this.props.Document.isInkMask ? 2500 : 0; }
+ get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${this.random(-1, 1) * this.props.jitterRotation}deg)`; }
get X() { return this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
get Y() { return this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
get Opacity() { return this.dataProvider ? this.dataProvider.opacity : Cast(this.layoutDoc.opacity, "number", null); }
@@ -79,8 +81,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
public static setValues(time: number, d: Doc, x?: number, y?: number, opacity?: number) {
const timecode = Math.round(time);
- Cast(d["x-indexed"], listSpec("number"), [])[Math.max(0, timecode - 1)] = x as any as number;
- Cast(d["y-indexed"], listSpec("number"), [])[Math.max(0, timecode - 1)] = y as any as number;
Cast(d["x-indexed"], listSpec("number"), [])[timecode] = x as any as number;
Cast(d["y-indexed"], listSpec("number"), [])[timecode] = y as any as number;
Cast(d["opacity-indexed"], listSpec("number"), null)[timecode] = opacity as any as number;
@@ -110,12 +110,12 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
const xlist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
const ylist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
const olist = new List<number>(numberRange(timecode + 1).map(t => progressivize && t < i ? 0 : 1));
- xlist[Math.max(curTimecode - 1, 0)] = xlist[curTimecode] = NumCast(doc.x);
- ylist[Math.max(curTimecode - 1, 0)] = ylist[curTimecode] = NumCast(doc.y);
+ xlist[curTimecode] = NumCast(doc.x);
+ ylist[curTimecode] = NumCast(doc.y);
doc["x-indexed"] = xlist;
doc["y-indexed"] = ylist;
doc["opacity-indexed"] = olist;
- doc.activeFrame = ComputedField.MakeFunction("self.context ? (self.context.currentFrame||0) : 0");
+ doc.activeFrame = ComputedField.MakeFunction("self.context?.currentFrame||0");
doc.x = ComputedField.MakeInterpolated("x", "activeFrame");
doc.y = ComputedField.MakeInterpolated("y", "activeFrame");
doc.opacity = ComputedField.MakeInterpolated("opacity", "activeFrame");
@@ -151,12 +151,12 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
outline: this.Highlight ? "orange solid 2px" : "",
transform: this.transform,
transition: this.props.transition ? this.props.transition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.transition),
- width: this.width,
- height: this.height,
+ width: this.props.Document.isInkMask ? 5000 : this.width,
+ height: this.props.Document.isInkMask ? 5000 : this.height,
zIndex: this.ZInd,
mixBlendMode: StrCast(this.layoutDoc.mixBlendMode) as any,
display: this.ZInd === -99 ? "none" : undefined,
- pointerEvents: this.props.Document.isBackground || this.Opacity === 0 ? "none" : this.props.pointerEvents ? "all" : undefined
+ pointerEvents: this.props.Document.isBackground || this.Opacity === 0 || this.props.Document.type === DocumentType.INK || this.props.Document.isInkMask ? "none" : this.props.pointerEvents ? "all" : undefined
}} >
{Doc.UserDoc().renderStyle !== "comic" ? (null) :
<div style={{ width: "100%", height: "100%", position: "absolute" }}>
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 0d6258cf3..d04da8f5b 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -60,9 +60,9 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument
StrCast(selDoc?._backgroundColor, StrCast(selDoc?.backgroundColor, "black")))} />
<div style={{ display: "grid", gridTemplateColumns: "20% 80%", paddingTop: "10px" }}>
<div> {ActiveInkWidth() ?? 2}</div>
- <input type="range" value={ActiveInkWidth() ?? 2} defaultValue={2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => SetActiveInkWidth(e.target.value)} />
+ <input type="range" defaultValue={ActiveInkWidth() ?? 2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => SetActiveInkWidth(e.target.value)} />
<div> {ActiveInkBezierApprox() ?? 2}</div>
- <input type="range" value={ActiveInkBezierApprox() ?? 2} defaultValue={2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => SetActiveBezierApprox(e.target.value)} />
+ <input type="range" defaultValue={ActiveInkBezierApprox() ?? 2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => SetActiveBezierApprox(e.target.value)} />
<br />
<br />
</div>
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index cce60628d..14a2c25bf 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -76,12 +76,12 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
const clearButton = (which: string) => {
return <div className={`clear-button ${which}`}
onPointerDown={e => e.stopPropagation()} // prevent triggering slider movement in registerSliding
- onClick={e => this.clearDoc(e, `${which}Doc`)}>
+ onClick={e => this.clearDoc(e, `compareBox-${which}`)}>
<FontAwesomeIcon className={`clear-button ${which}`} icon={"times"} size="sm" />
</div>;
};
const displayDoc = (which: string) => {
- const whichDoc = Cast(this.dataDoc[`${which}Doc`], Doc, null);
+ const whichDoc = Cast(this.dataDoc[`compareBox-${which}`], Doc, null);
return whichDoc ? <>
<ContentFittingDocumentView {...childProps} Document={whichDoc} />
{clearButton(which)}
@@ -93,7 +93,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
const displayBox = (which: string, index: number, cover: number) => {
return <div className={`${which}Box-cont`} key={which} style={{ width: this.props.PanelWidth() }}
onPointerDown={e => this.registerSliding(e, cover)}
- ref={ele => this.createDropTarget(ele, `${which}Doc`, index)} >
+ ref={ele => this.createDropTarget(ele, `compareBox-${which}`, index)} >
{displayDoc(which)}
</div>;
};
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index a90b4668e..ba075886b 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -1,15 +1,50 @@
import React = require("react");
import { computed } from "mobx";
import { observer } from "mobx-react";
+import { Transform } from "nodemailer/lib/xoauth2";
import "react-table/react-table.css";
-import { Doc, Opt, WidthSym, HeightSym } from "../../../fields/Doc";
-import { NumCast, StrCast, Cast } from "../../../fields/Types";
+import { Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { ScriptField } from "../../../fields/ScriptField";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnOne } from "../../../Utils";
+import { emptyFunction } from "../../../Utils";
+import { dropActionType } from "../../util/DragManager";
+import { CollectionView } from "../collections/CollectionView";
import '../DocumentDecorations.scss';
import { DocumentView, DocumentViewProps } from "../nodes/DocumentView";
import "./ContentFittingDocumentView.scss";
+interface ContentFittingDocumentViewProps {
+ Document: Doc;
+ DataDocument?: Doc;
+ LayoutDoc?: () => Opt<Doc>;
+ NativeWidth?: () => number;
+ NativeHeight?: () => number;
+ FreezeDimensions?: boolean;
+ LibraryPath: Doc[];
+ renderDepth: number;
+ fitToBox?: boolean;
+ layoutKey?: string;
+ dropAction?: dropActionType;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
+ focus?: (doc: Doc) => void;
+ CollectionView?: CollectionView;
+ CollectionDoc?: Doc;
+ onClick?: ScriptField;
+ backgroundColor?: (doc: Doc) => string | undefined;
+ getTransform: () => Transform;
+ addDocument?: (document: Doc) => boolean;
+ moveDocument?: (document: Doc, target: Doc | undefined, addDoc: ((doc: Doc) => boolean)) => boolean;
+ removeDocument?: (document: Doc) => boolean;
+ active: (outsideReaction: boolean) => boolean;
+ whenActiveChanged: (isActive: boolean) => void;
+ addDocTab: (document: Doc, where: string) => boolean;
+ pinToPres: (document: Doc) => void;
+ dontRegisterView?: boolean;
+ rootSelected: (outsideReaction?: boolean) => boolean;
+ Display?: string;
+}
@observer
export class ContentFittingDocumentView extends React.Component<DocumentViewProps>{
@@ -38,8 +73,8 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
@computed get panelHeight() { return this.nativeHeight && !this.props.Document._fitWidth ? this.nativeHeight() * this.contentScaling() : this.props.PanelHeight(); }
private getTransform = () => this.props.ScreenToLocalTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling());
- private get centeringOffset() { return this.nativeWidth() && !this.props.Document._fitWidth ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
- private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight() * this.contentScaling()) / 2 : 0; }
+ private get centeringOffset() { return this.nativeWidth() && !this.props.Document._fitWidth && this.props.display !== "contents" ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
+ private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 && this.props.display !== "contents" ? (this.props.PanelHeight() - this.nativeHeight() * this.contentScaling()) / 2 : 0; }
@computed get borderRounding() { return StrCast(this.props.Document?.borderRounding); }
@@ -47,7 +82,8 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
TraceMobx();
return (<div className="contentFittingDocumentView" style={{
width: Math.abs(this.centeringYOffset) > 0.001 ? "auto" : this.props.PanelWidth(),
- height: Math.abs(this.centeringOffset) > 0.0001 ? "auto" : this.props.PanelHeight()
+ height: Math.abs(this.centeringOffset) > 0.0001 ? "auto" : this.props.PanelHeight(),
+ display: this.props.display /* just added for grid */
}}>
{!this.props.Document || !this.props.PanelWidth ? (null) : (
<div className="contentFittingDocumentView-previewDoc"
diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx
index 0c5239d66..a4c4663dd 100644
--- a/src/client/views/nodes/DocHolderBox.tsx
+++ b/src/client/views/nodes/DocHolderBox.tsx
@@ -198,11 +198,10 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
- if (de.complete.docDragData) {
- if (de.complete.docDragData.draggedDocuments[0].type === DocumentType.FONTICON) {
- const doc = Cast(de.complete.docDragData.draggedDocuments[0].dragFactory, Doc, null);
- this.layoutDoc.childLayoutTemplate = doc;
- }
+ const docDragData = de.complete.docDragData;
+ if (docDragData?.draggedDocuments[0].type === DocumentType.FONTICON) {
+ const doc = Cast(docDragData.draggedDocuments[0].dragFactory, Doc, null);
+ this.layoutDoc.childLayoutTemplate = doc;
}
}
protected createDropTarget = (ele: HTMLDivElement) => {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f9f453227..b59eba0ea 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -98,6 +98,7 @@ export interface DocumentViewProps {
dontRegisterView?: boolean;
layoutKey?: string;
radialMenu?: String[];
+ display?: string;
}
@observer
@@ -336,7 +337,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.props.addDocTab(alias, "onRight");
// UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick");
//ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click");
- } else if (this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
+ } else if (this.props.Document.links && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
} else {
if ((this.props.Document.onDragStart || (this.props.Document.rootDocument)) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
@@ -518,6 +519,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
e.stopPropagation();
+ if (SelectionManager.IsSelected(this, true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
// TODO: check here for panning/inking
}
return;
@@ -531,8 +533,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
!e.ctrlKey &&
(e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
!this.Document.inOverlay) {
- e.stopPropagation(); // events stop at the lowest document that is active. if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
-
+ e.stopPropagation();
+ if (SelectionManager.IsSelected(this, true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
}
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -687,13 +689,30 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
setAcl = (acl: "readOnly" | "addOnly" | "ownerOnly") => {
- this.layoutDoc.ACL = this.dataDoc.ACL = acl;
+ this.dataDoc.ACL = this.props.Document.ACL = acl;
DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
if (d.author === Doc.CurrentUserEmail) d.ACL = acl;
const data = d[DataSym];
if (data && data.author === Doc.CurrentUserEmail) data.ACL = acl;
});
}
+ @undoBatch
+ @action
+ testAcl = (acl: "readOnly" | "addOnly" | "ownerOnly") => {
+ this.dataDoc.author = this.props.Document.author = "ADMIN";
+ this.dataDoc.ACL = this.props.Document.ACL = acl;
+ DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
+ if (d.author === Doc.CurrentUserEmail) {
+ d.author = "ADMIN";
+ d.ACL = acl;
+ }
+ const data = d[DataSym];
+ if (data && data.author === Doc.CurrentUserEmail) {
+ data.author = "ADMIN";
+ data.ACL = acl;
+ }
+ });
+ }
@action
onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => {
@@ -758,6 +777,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
moreItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
moreItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
moreItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
+ moreItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
+ moreItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
@@ -1169,7 +1190,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] :
["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"];
- let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear;
+ let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear && this.props.Document.type !== DocumentType.INK;
highlighting = highlighting && this.props.focus !== emptyFunction; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way
return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
id={this.props.Document[Id]}
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 83245a89c..3c232eff8 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -117,7 +117,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
const y = this.props.PanelWidth() > 1 ? NumCast(this.layoutDoc[this.fieldKey + "_y"], 100) : 0;
const c = StrCast(this.layoutDoc.backgroundColor, "lightblue");
const anchor = this.fieldKey === "anchor1" ? "anchor2" : "anchor1";
- const anchorScale = (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .15;
+ const anchorScale = (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .25;
const timecode = this.dataDoc[anchor + "_timecode"];
const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title) + (timecode !== undefined ? ":" + timecode : "");
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index d75b864cf..f7dee0896 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -18,6 +18,7 @@ import { ContextMenuProps } from "../ContextMenuItem";
import { ViewBoxBaseComponent } from "../DocComponent";
import { FieldView, FieldViewProps } from './FieldView';
import "./ScreenshotBox.scss";
+import { InkTool } from "../../../fields/InkField";
const path = require('path');
type ScreenshotDocument = makeInterface<[typeof documentSchema]>;
@@ -132,7 +133,7 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh
}
@computed get content() {
- const interactive = Doc.GetSelectedTool() || !this.props.isSelected() ? "" : "-interactive";
+ const interactive = Doc.GetSelectedTool() !== InkTool.None || !this.props.isSelected() ? "" : "-interactive";
const style = "videoBox-content" + interactive;
return <video className={`${style}`} key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
style={{ width: this._screenCapture ? "100%" : undefined, height: this._screenCapture ? "100%" : undefined }}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 1e0a859d3..e4dbceca6 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -228,7 +228,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
@computed get content() {
const field = Cast(this.dataDoc[this.fieldKey], VideoField);
- const interactive = Doc.GetSelectedTool() || !this.props.isSelected() ? "" : "-interactive";
+ const interactive = Doc.GetSelectedTool() !== InkTool.None || !this.props.isSelected() ? "" : "-interactive";
const style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
return !field ? <div>Loading</div> :
<video className={`${style}`} key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 05e6a5959..55b3f6f1e 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -7,7 +7,7 @@ import { ComputedField } from "../../../../fields/ScriptField";
import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
-import { Docs } from "../../../documents/Documents";
+import { Docs, DocUtils } from "../../../documents/Documents";
import { DocumentView } from "../DocumentView";
import { FormattedTextBox } from "./FormattedTextBox";
import { Transform } from "../../../util/Transform";
@@ -48,7 +48,7 @@ export class DashDocView extends React.Component<IDashDocView> {
if (dashDocBase instanceof Doc) {
const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias);
aliasedDoc.layoutKey = "layout";
- node.attrs.fieldKey && Doc.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
+ node.attrs.fieldKey && DocUtils.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
this._dashDoc = aliasedDoc;
// self.doRender(aliasedDoc, removeDoc, node, view, getPos);
}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index 35ff9c1e6..23cf1e79b 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -25,7 +25,7 @@
margin-left: 2px;
margin-right: 5px;
position: relative;
- display: inline-block;
+ display: inline;
background-color: rgba(155, 155, 155, 0.24);
span {
min-width: 100%;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index d05e8f1ea..60a9c2a27 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -12,6 +12,7 @@ import React = require("react");
import * as ReactDOM from 'react-dom';
import "./DashFieldView.scss";
import { observer } from "mobx-react";
+import { DocUtils } from "../../../documents/Documents";
export class DashFieldView {
@@ -102,11 +103,14 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
// bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't
// use React events. Essentially, React events occur after native events have been processed, so corresponding React events
// will never fire because Prosemirror has handled the native events. So we add listeners for native events here.
- return <span contentEditable={true} suppressContentEditableWarning={true} defaultValue={strVal} ref={r => {
- r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r));
- r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false));
- r?.addEventListener("pointerdown", action((e) => this._showEnumerables = true));
- }} >
+ return <span className="dashFieldView-fieldSpan" contentEditable={true}
+ style={{ display: strVal.length < 2 ? "inline-block" : undefined }}
+ suppressContentEditableWarning={true} defaultValue={strVal}
+ ref={r => {
+ r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r));
+ r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false));
+ r?.addEventListener("pointerdown", action((e) => this._showEnumerables = true));
+ }} >
{strVal}
</span>;
}
@@ -117,7 +121,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
@action
fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => {
if (e.key === "Enter") { // handle the enter key by "submitting" the current text to Dash's database.
- e.ctrlKey && Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]);
+ e.ctrlKey && DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]);
this.updateText(span.textContent!, true);
e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view
}
@@ -147,7 +151,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
(options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
if (modText) {
// elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText;
- Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []);
+ DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []);
this._dashDoc![this._fieldKey] = modText;
} // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
else if (nodeText.startsWith(":=")) {
@@ -167,7 +171,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
// display a collection of all the enumerable values for this field
onPointerDownEnumerables = async (e: any) => {
e.stopPropagation();
- const collview = await Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]);
+ const collview = await DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]);
collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "onRight");
}
@@ -204,9 +208,9 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
{this._fieldKey}
</span>}
- <div className="dashFieldView-fieldSpan">
- {this.fieldValueContent}
- </div>
+ {/* <div className="dashFieldView-fieldSpan"> */}
+ {this.fieldValueContent}
+ {/* </div> */}
{!this._showEnumerables ? (null) : <div className="dashFieldView-enumerables" onPointerDown={this.onPointerDownEnumerables} />}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 49114d967..df4a05dd7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -67,6 +67,7 @@
display: inline-block;
position: absolute;
right: 0;
+ overflow: hidden;
.collectionfreeformview-container {
position: relative;
@@ -232,6 +233,48 @@ footnote::after {
}
}
+.prosemirror-anchor {
+ overflow:hidden;
+ display:inline-grid;
+}
+.prosemirror-linkBtn {
+ background:unset;
+ color:unset;
+ padding:0;
+ text-transform: unset;
+ letter-spacing: unset;
+ font-size:unset;
+}
+.prosemirror-links {
+ display: none;
+ position: absolute;
+ background-color: gray;
+ padding-bottom: 10px;
+ margin-top: 1em;
+ z-index: 1;
+ }
+ .prosemirror-hrefoptions{
+ width:0px;
+ border:unset;
+ padding:0px;
+
+ }
+
+ .prosemirror-links a {
+ float: left;
+ color: white;
+ text-decoration: none;
+ }
+
+ .prosemirror-links a:hover {
+ background-color: #eee;
+ color: black;
+ }
+
+ .prosemirror-anchor:hover .prosemirror-links {
+ display: grid;
+ }
+
.ProseMirror {
touch-action: none;
span {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 130931b24..99cd503ec 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -180,7 +180,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.linkOnDeselect.set(key, value);
const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
- const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + id), location: "onRight", title: value });
+ const allHrefs = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }];
+ const link = this._editorView.state.schema.marks.link.create({ allHrefs, location: "onRight", title: value });
const mval = this._editorView.state.schema.marks.metadataVal.create();
const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
@@ -239,10 +240,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const lastSel = Math.min(flattened.length - 1, this._searchIndex);
this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
const alink = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: target }, "automatic")!;
- const link = this._editorView.state.schema.marks.link.create({
- href: Utils.prepend("/doc/" + alink[Id]),
- title: "a link", location: location, linkId: alink[Id], targetId: target[Id]
- });
+ const allHrefs = [{ href: Utils.prepend("/doc/" + alink[Id]), title: "a link", targetId: target[Id], linkId: alink[Id] }];
+ const link = this._editorView.state.schema.marks.link.create({ allHrefs, title: "a link", location });
this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link));
}
}
@@ -283,8 +282,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
@undoBatch
@action
drop = async (e: Event, de: DragManager.DropEvent) => {
- if (de.complete.docDragData) {
- const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0];
+ const dragData = de.complete.docDragData;
+ if (dragData) {
+ const draggedDoc = dragData.draggedDocuments.length && dragData.draggedDocuments[0];
// replace text contents whend dragging with Alt
if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) {
if (draggedDoc.data instanceof RichTextField) {
@@ -292,8 +292,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
e.stopPropagation();
}
// embed document when dragging with a userDropAction or an embedDoc flag set
- } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) {
- const target = de.complete.docDragData.droppedDocuments[0];
+ } else if (dragData.userDropAction || dragData.embedDoc) {
+ const target = dragData.droppedDocuments[0];
// const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title);
// if (link) {
target._fitToBox = true;
@@ -583,11 +583,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
};
}
- makeLinkToSelection(linkDocId: string, title: string, location: string, targetDocId: string) {
- if (this._editorView) {
- const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId });
- this._editorView.dispatch(this._editorView.state.tr.removeMark(this._editorView.state.selection.from, this._editorView.state.selection.to, this._editorView.state.schema.marks.link).
- addMark(this._editorView.state.selection.from, this._editorView.state.selection.to, link));
+ makeLinkToSelection(linkId: string, title: string, location: string, targetId: string) {
+ const state = this._editorView?.state;
+ if (state) {
+ const href = Utils.prepend("/doc/" + linkId);
+ const sel = state.selection;
+ const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
+ let tr = state.tr.addMark(sel.from, sel.to, splitter);
+ sel.from !== sel.to && tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
+ if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
+ const allHrefs = [{ href, title, targetId, linkId }];
+ allHrefs.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.link.name)?.attrs.allHrefs ?? []));
+ const link = state.schema.marks.link.create({ href, allHrefs, title, location, linkId, targetId });
+ tr = tr.addMark(pos, pos + node.nodeSize, link);
+ }
+ });
+ this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
}
}
componentDidMount() {
@@ -695,7 +706,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
- return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined;
+ return linkIndex !== -1 && marks[linkIndex].attrs.allRefs.find((item: { href: string }) => scrollToLinkID === item.href.replace(/.*\/doc\//, "")) ? node : undefined;
};
let start = 0;
@@ -978,7 +989,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
FormattedTextBox._downEvent = false;
if (!(e.nativeEvent as any).formattedHandled) {
FormattedTextBoxComment.textBox = this;
- FormattedTextBoxComment.update(this._editorView!);
+ FormattedTextBoxComment.update(this._editorView!, undefined, (e.target as any)?.className === "prosemirror-dropdownlink" ? (e.target as any).href : "");
}
(e.nativeEvent as any).formattedHandled = true;
@@ -1155,7 +1166,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
this._lastTimedMark = mark;
- this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
+ // this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
if (!this._undoTyping) {
this._undoTyping = UndoManager.StartBatch("undoTyping");
@@ -1196,17 +1207,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
@computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
- sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0);
+ sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / this.props.ContentScaling(), 0);
@computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
render() {
TraceMobx();
const scale = this.props.ContentScaling() * NumCast(this.layoutDoc.scale, 1);
const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
- const interactive = Doc.GetSelectedTool() || this.layoutDoc.isBackground;
+ const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground;
if (this.props.isSelected()) {
- this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props);
+ setTimeout(() => this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props), 0);
} else if (FormattedTextBoxComment.textBox === this) {
- FormattedTextBoxComment.Hide();
+ setTimeout(() => FormattedTextBoxComment.Hide(), 0);
}
return (
<div className={"formattedTextBox-cont"} style={{
@@ -1223,7 +1234,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
background: Doc.UserDoc().renderStyle === "comic" ? "transparent" : this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""),
opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
- pointerEvents: interactive ? "none" : undefined,
+ pointerEvents: interactive ? undefined : "none",
fontSize: Cast(this.layoutDoc._fontSize, "number", null),
fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit")
}}
@@ -1264,6 +1275,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
PanelWidth={this.sidebarWidth}
NativeHeight={returnZero}
NativeWidth={returnZero}
+ scaleField={this.annotationKey + "-scale"}
annotationsKey={this.annotationKey}
isAnnotationOverlay={false}
focus={this.props.focus}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 59a6045ab..5ac173602 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -84,11 +84,13 @@ export class FormattedTextBoxComment {
const keep = e.target && (e.target as any).type === "checkbox" ? true : false;
const textBox = FormattedTextBoxComment.textBox;
if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
- if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
- textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
- } else {
- DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
- (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+ if (FormattedTextBoxComment.linkDoc.author) {
+ if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
+ textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+ } else {
+ DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
+ (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+ }
}
} else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, UseCors: true }), "onRight");
@@ -115,7 +117,24 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
}
- static update(view: EditorView, lastState?: EditorState) {
+ static showCommentbox(set: string, view: EditorView, nbef: number) {
+ const state = view.state;
+ if (set !== "none") {
+ // These are in screen coordinates
+ // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
+ const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
+ // The box in which the tooltip is positioned, to use as base
+ const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
+ // Find a center-ish x position from the selection endpoints (when
+ // crossing lines, end may be more to the left)
+ const left = Math.max((start.left + end.left) / 2, start.left + 3);
+ FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
+ FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
+ }
+ FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
+ }
+
+ static update(view: EditorView, lastState?: EditorState, forceUrl: string = "") {
const state = view.state;
// Don't do anything if the document/selection didn't change
if (lastState && lastState.doc.eq(state.doc) &&
@@ -160,32 +179,34 @@ export class FormattedTextBoxComment {
let child: any = null;
state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
const mark = child && findLinkMark(child.marks);
- if (mark && child && nbef && naft && mark.attrs.showPreview) {
- FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href;
- (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
- if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) {
- wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
+ const href = mark?.attrs.allHrefs.find((item: { href: string }) => item.href)?.href || forceUrl;
+ if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) {
+ FormattedTextBoxComment.tooltipText.textContent = "external => " + href;
+ (FormattedTextBoxComment.tooltipText as any).href = href;
+ if (href.startsWith("https://en.wikipedia.org/wiki/")) {
+ wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
} else {
FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre";
FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
}
- if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) {
+ if (href.indexOf(Utils.prepend("/doc/")) === 0) {
FormattedTextBoxComment.tooltipText.textContent = "target not found...";
(FormattedTextBoxComment.tooltipText as any).href = "";
- const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ const docTarget = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
try {
ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
} catch (e) { }
docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => {
if (linkDoc instanceof Doc) {
- (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
+ (FormattedTextBoxComment.tooltipText as any).href = href;
FormattedTextBoxComment.linkDoc = linkDoc;
const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
if (anchor !== target && anchor && target) {
target._scrollY = NumCast(anchor?.y);
}
- if (target) {
+ if (target?.author) {
+ FormattedTextBoxComment.showCommentbox("", view, nbef);
ReactDOM.render(<ContentFittingDocumentView
Document={target}
LibraryPath={emptyPath}
@@ -223,19 +244,7 @@ export class FormattedTextBoxComment {
set = "";
}
}
- if (set !== "none") {
- // These are in screen coordinates
- // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
- const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
- // The box in which the tooltip is positioned, to use as base
- const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
- // Find a center-ish x position from the selection endpoints (when
- // crossing lines, end may be more to the left)
- const left = Math.max((start.left + end.left) / 2, start.left + 3);
- FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
- FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
- }
- FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
+ FormattedTextBoxComment.showCommentbox(set, view, nbef);
}
destroy() { }
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 444a8a4c0..e4393e5d2 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -6,7 +6,6 @@ import { liftListItem, sinkListItem } from "./prosemirrorPatches.js";
import { splitListItem, wrapInList, } from "prosemirror-schema-list";
import { EditorState, Transaction, TextSelection } from "prosemirror-state";
import { SelectionManager } from "../../../util/SelectionManager";
-import { Docs } from "../../../documents/Documents";
import { NumCast, BoolCast, Cast, StrCast } from "../../../../fields/Types";
import { Doc } from "../../../../fields/Doc";
import { FormattedTextBox } from "./FormattedTextBox";
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index fd1b26208..03d393cde 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -631,7 +631,7 @@ export default class RichTextMenu extends AntimodeMenu {
const node = this.view.state.selection.$from.nodeAfter;
const link = node && node.marks.find(m => m.type.name === "link");
if (link) {
- const href = link.attrs.href;
+ const href = link.attrs.allHrefs.length > 0 ? link.attrs.allHrefs[0].href : undefined;
if (href) {
if (href.indexOf(Utils.prepend("/doc/")) === 0) {
const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
@@ -677,7 +677,7 @@ export default class RichTextMenu extends AntimodeMenu {
const node = this.view.state.selection.$from.nodeAfter;
const link = node && node.marks.find(m => m.type === this.view!.state.schema.marks.link);
- const href = link!.attrs.href;
+ const href = link!.attrs.allHrefs.length > 0 ? link!.attrs.allHrefs[0].href : undefined;
if (href) {
if (href.indexOf(Utils.prepend("/doc/")) === 0) {
const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
@@ -705,8 +705,8 @@ export default class RichTextMenu extends AntimodeMenu {
let startIndex = $start.index();
let endIndex = $start.indexAfter();
- while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.href === href).length) startIndex--;
- while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.href === href).length) endIndex++;
+ while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allHrefs.find((item: { href: string }) => item.href === href)).length) startIndex--;
+ while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allHrefs.find((item: { href: string }) => item.href === href)).length) endIndex++;
let startPos = $start.start();
let endPos = startPos;
diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx
index f6bc858da..79abfa995 100644
--- a/src/client/views/nodes/formattedText/RichTextSchema.tsx
+++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx
@@ -18,7 +18,7 @@ import { ComputedField } from "../../../../fields/ScriptField";
import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../../fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
-import { Docs } from "../../../documents/Documents";
+import { Docs, DocUtils } from "../../../documents/Documents";
import { CollectionViewType } from "../../collections/CollectionView";
import { DocumentView } from "../DocumentView";
import { FormattedTextBox } from "./FormattedTextBox";
@@ -171,7 +171,7 @@ export class DashDocView {
if (dashDocBase instanceof Doc) {
const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias);
aliasedDoc.layoutKey = "layout";
- node.attrs.fieldKey && Doc.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
+ node.attrs.fieldKey && DocUtils.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
self.doRender(aliasedDoc, removeDoc, node, view, getPos);
}
});
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index ebaa23e99..49d5c96a4 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -9,14 +9,20 @@ const codeDOM: DOMOutputSpecArray = ["code", 0];
// :: Object [Specs](#model.MarkSpec) for the marks in the schema.
export const marks: { [index: string]: MarkSpec } = {
+ splitter: {
+ attrs: {
+ id: { default: "" }
+ },
+ toDOM(node: any) {
+ return ["div", { className: "dummy" }, 0];
+ }
+ },
// :: MarkSpec A link. Has `href` and `title` attributes. `title`
// defaults to the empty string. Rendered and parsed as an `<a>`
// element.
link: {
attrs: {
- href: {},
- targetId: { default: "" },
- linkId: { default: "" },
+ allHrefs: { default: [] as { href: string, title: string, linkId: string, targetId: string }[] },
showPreview: { default: true },
location: { default: null },
title: { default: null },
@@ -25,13 +31,25 @@ export const marks: { [index: string]: MarkSpec } = {
inclusive: false,
parseDOM: [{
tag: "a[href]", getAttrs(dom: any) {
- return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title"), targetId: dom.getAttribute("id") };
+ return { allHrefs: [{ href: dom.getAttribute("href"), title: dom.getAttribute("title"), linkId: dom.getAttribute("linkids"), targetId: dom.getAttribute("targetids") }], location: dom.getAttribute("location"), };
}
}],
toDOM(node: any) {
+ const targetids = node.attrs.allHrefs.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.targetId, "");
+ const linkids = node.attrs.allHrefs.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.linkId, "");
return node.attrs.docref && node.attrs.title ?
["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] :
- ["a", { ...node.attrs, id: node.attrs.linkId + node.attrs.targetId, title: `${node.attrs.title}` }, 0];
+ node.attrs.allHrefs.length === 1 ?
+ ["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}`, href: node.attrs.allHrefs[0].href }, 0] :
+ ["div", { class: "prosemirror-anchor" },
+ ["span", { class: "prosemirror-linkBtn" },
+ ["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}` }, 0],
+ ["input", { class: "prosemirror-hrefoptions" }],
+ ],
+ ["div", { class: "prosemirror-links" }, ...node.attrs.allHrefs.map((item: { href: string, title: string }) =>
+ ["a", { class: "prosemirror-dropdownlink", href: item.href }, item.title]
+ )]
+ ];
}
},