aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/util/DragManager.ts29
-rw-r--r--src/client/views/EditableView.tsx23
-rw-r--r--src/client/views/GestureOverlay.tsx4
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx42
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx11
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx25
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx2
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx2
-rw-r--r--src/client/views/nodes/ContentFittingDocumentView.tsx14
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss43
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx28
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx19
-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/marks_rts.ts28
-rw-r--r--src/fields/RichTextUtils.ts2
17 files changed, 184 insertions, 99 deletions
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index f1afaf734..cb899ef92 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -319,7 +319,7 @@ export namespace DragManager {
dragDiv = document.createElement("div");
dragDiv.className = "dragManager-dragDiv";
dragDiv.style.pointerEvents = "none";
- dragLabel = document.createElement("div") as HTMLDivElement;
+ dragLabel = document.createElement("div");
dragLabel.className = "dragManager-dragLabel";
dragLabel.style.zIndex = "100001";
dragLabel.style.fontSize = "10";
@@ -412,21 +412,20 @@ export namespace DragManager {
if (dragData instanceof DocumentDragData) {
dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : undefined;
}
- if (e)
- if (e.shiftKey && dragData.droppedDocuments.length === 1) {
- !dragData.dropAction && (dragData.dropAction = alias);
- if (dragData.dropAction === "move") {
- dragData.removeDocument?.(dragData.draggedDocuments[0]);
- }
- AbortDrag();
- finishDrag?.(new DragCompleteEvent(true, dragData));
- DragManager.StartWindowDrag?.({
- pageX: e.pageX,
- pageY: e.pageY,
- preventDefault: emptyFunction,
- button: 0
- }, dragData.droppedDocuments);
+ if (e?.shiftKey && dragData.droppedDocuments.length === 1) {
+ !dragData.dropAction && (dragData.dropAction = alias);
+ if (dragData.dropAction === "move") {
+ dragData.removeDocument?.(dragData.draggedDocuments[0]);
}
+ AbortDrag();
+ finishDrag?.(new DragCompleteEvent(true, dragData));
+ DragManager.StartWindowDrag?.({
+ pageX: e.pageX,
+ pageY: e.pageY,
+ preventDefault: emptyFunction,
+ button: 0
+ }, dragData.droppedDocuments);
+ }
const { thisX, thisY } = snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom);
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index e0e205df9..e21d431b1 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -66,16 +66,17 @@ export class EditableView extends React.Component<EditableProps> {
EditableView.loadId = "";
}
- @action
- componentDidUpdate(nextProps: EditableProps) {
- // this is done because when autosuggest is turned on, the suggestions are passed in as a prop,
- // so when the suggestions are passed in, and no editing prop is passed in, it used to set it
- // to false. this will no longer do so -syip
- if (nextProps.editing && nextProps.editing !== this._editing) {
- this._editing = nextProps.editing;
- EditableView.loadId = "";
- }
- }
+ // @action
+ // componentDidUpdate(nextProps: EditableProps) {
+ // // this is done because when autosuggest is turned on, the suggestions are passed in as a prop,
+ // // so when the suggestions are passed in, and no editing prop is passed in, it used to set it
+ // // to false. this will no longer do so -syip
+ // console.log("props editing = " + nextProps.editing);
+ // if (nextProps.editing && nextProps.editing !== this._editing) {
+ // this._editing = nextProps.editing;
+ // EditableView.loadId = "";
+ // }
+ // }
_didShow = false;
@@ -109,7 +110,7 @@ export class EditableView extends React.Component<EditableProps> {
if (this._ref.current && this.props.showMenuOnLoad) {
this.props.menuCallback?.(this._ref.current.getBoundingClientRect().x, this._ref.current.getBoundingClientRect().y);
} else {
- if (!this.props.onClick || !this.props.onClick(e)) {
+ if (!this.props.onClick?.(e)) {
this._editing = true;
this.props.isEditingCallback?.(true);
}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index d239a1d6f..f00fd7cb4 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -809,11 +809,11 @@ export default class GestureOverlay extends Touchable {
[this._strokes.map(l => {
const b = this.getBounds(l);
return <svg key={b.left} width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
- {InteractionUtils.CreatePolyline(l, b.left, b.top, ActiveInkColor(), ActiveInkWidth(), ActiveInkBezierApprox(), 1, 1, this.InkShape)}
+ {InteractionUtils.CreatePolyline(l, b.left, b.top, ActiveInkColor(), ActiveInkWidth(), ActiveInkBezierApprox(), 1, 1, this.InkShape, "none", false)}
</svg>;
}),
this._points.length <= 1 ? (null) : <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
- {InteractionUtils.CreatePolyline(this._points, B.left, B.top, ActiveInkColor(), ActiveInkWidth(), ActiveInkBezierApprox(), 1, 1, this.InkShape)}
+ {InteractionUtils.CreatePolyline(this._points, B.left, B.top, ActiveInkColor(), ActiveInkWidth(), ActiveInkBezierApprox(), 1, 1, this.InkShape, "none", false)}
</svg>]
];
}
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 62aed67ed..baf9d4156 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -1,5 +1,5 @@
import React = require("react");
-import { action, observable } from "mobx";
+import { action, observable, trace } from "mobx";
import { observer } from "mobx-react";
import { CellInfo } from "react-table";
import "react-table/react-table.css";
@@ -23,6 +23,7 @@ import { faExpand } from '@fortawesome/free-solid-svg-icons';
import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import { undoBatch } from "../../util/UndoManager";
import { SnappingManager } from "../../util/SnappingManager";
+import { ComputedField } from "../../../fields/ScriptField";
library.add(faExpand);
@@ -57,7 +58,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
componentDidMount() {
document.addEventListener("keydown", this.onKeyDown);
-
}
componentWillUnmount() {
@@ -70,7 +70,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
document.removeEventListener("keydown", this.onKeyDown);
this._isEditing = true;
this.props.setIsEditing(true);
-
}
}
@@ -217,7 +216,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
// <div className="collectionSchemaView-cellContents-docExpander" onPointerDown={this.expandDoc} >
// <FontAwesomeIcon icon="expand" size="sm" />
// </div>
- // );
+ // );
+ trace();
return (
<div className="collectionSchemaView-cellContainer" style={{ cursor: fieldIsDoc ? "grab" : "auto" }} ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}>
@@ -231,23 +231,29 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
height={"auto"}
maxHeight={Number(MAX_ROW_HEIGHT)}
GetValue={() => {
- const field = props.Document[props.fieldKey];
- if (Field.IsField(field)) {
- return Field.toScriptString(field);
- }
- return "";
- }
- }
- SetValue={(value: string) => {
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey]));
+ const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined;
+ const cfinalScript = cscript?.split("return")[cscript.split("return").length - 1];
+ const val = cscript !== undefined ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` :
+ Field.IsField(cfield) ? Field.toScriptString(cfield) : "";
+ return val;
+ }}
+ SetValue={action((value: string) => {
+ let retVal = false;
if (value.startsWith(":=")) {
- return this.props.setComputed(value.substring(2), props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col);
+ retVal = this.props.setComputed(value.substring(2), props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col);
+ } else {
+ const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
+ if (script.compiled) {
+ retVal = this.applyToDoc(props.Document, this.props.row, this.props.col, script.run);
+ }
}
- const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
- if (!script.compiled) {
- return false;
+ if (retVal) {
+ this._isEditing = false; // need to set this here. otherwise, the assignment of the field will invalidate & cause render() to be called with the wrong value for 'editing'
+ this.props.setIsEditing(false);
}
- return this.applyToDoc(props.Document, this.props.row, this.props.col, script.run);
- }}
+ return retVal;
+ })}
OnFillDown={async (value: string) => {
const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
if (script.compiled) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f3fc04752..6cac39f77 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -46,10 +46,15 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const bfield = afield === "anchor1" ? "anchor2" : "anchor1";
// really hacky stuff to make the LinkAnchorBox display where we want it to:
- // if there's an element in the DOM with the id of the opposite anchor, then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
+ // if there's an element in the DOM with a classname containing the link's id and a targetids attribute containing the other end of the link,
+ // then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
// otherwise, we just use the computed nearest point on the document boundary to the target Document
- const targetAhyperlink = window.document.getElementById(this.props.LinkDocs[0][Id] + (this.props.LinkDocs[0][afield] as Doc)[Id]);
- const targetBhyperlink = window.document.getElementById(this.props.LinkDocs[0][Id] + (this.props.LinkDocs[0][bfield] as Doc)[Id]);
+ const linkId = this.props.LinkDocs[0][Id]; // this link's Id
+ const AanchorId = (this.props.LinkDocs[0][afield] as Doc)[Id]; // anchor a's id
+ const BanchorId = (this.props.LinkDocs[0][bfield] as Doc)[Id]; // anchor b's id
+ const linkEles = Array.from(window.document.getElementsByClassName(linkId));
+ const targetAhyperlink = linkEles.find((ele: any) => ele.getAttribute("targetids")?.includes(AanchorId));
+ const targetBhyperlink = linkEles.find((ele: any) => ele.getAttribute("targetids")?.includes(BanchorId));
if (!targetBhyperlink) {
this.props.A.props.Document[afield + "_x"] = (apt.point.x - abounds.left) / abounds.width * 100;
this.props.A.props.Document[afield + "_y"] = (apt.point.y - abounds.top) / abounds.height * 100;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 678ad2a53..11a14a862 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -52,7 +52,6 @@ library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressAr
export const panZoomSchema = createSchema({
_panX: "number",
_panY: "number",
- scale: "number",
currentTimecode: "number",
displayTimecode: "number",
currentFrame: "number",
@@ -76,6 +75,7 @@ export type collectionFreeformViewProps = {
forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
viewDefDivClick?: ScriptField;
childPointerEvents?: boolean;
+ scaleField?: string;
};
@observer
@@ -108,6 +108,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@computed get nativeWidth() { return this.fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); }
@computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight, this.props.NativeHeight()); }
private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
+ private get scaleFieldKey() { return this.props.scaleField || "scale"; }
private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
private easing = () => this.props.Document.panTransformType === "Ease";
private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document._panX || 0;
@@ -115,14 +116,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
private zoomScaling = () => (this.fitToContentScaling / this.parentScaling) * (this.fitToContent ?
Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y),
this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
- this.Document.scale || 1)
+ NumCast(this.Document[this.scaleFieldKey], 1))
@computed get cachedCenteringShiftX(): number {
- const scaling = this.fitToContent ? 1 : this.contentScaling;
+ const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
return !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / scaling : 0; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedCenteringShiftY(): number {
- const scaling = this.fitToContent ? 1 : this.contentScaling;
+ const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
return !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / scaling : 0;// shift so pan position is at center of window for non-overlay collections
}
@computed get cachedGetLocalTransform(): Transform {
@@ -779,7 +780,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (localTransform.Scale >= 0.15 || localTransform.Scale > this.zoomScaling()) {
const safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40);
- this.props.Document.scale = Math.abs(safeScale);
+ this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
}
}
@@ -795,7 +796,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (!e.ctrlKey && MarqueeView.DragMarquee) this.setPan(this.panX() + e.deltaX, this.panY() + e.deltaY, "None", true);
else this.zoom(e.clientX, e.clientY, e.deltaY);
}
- this.props.Document.targetScale = NumCast(this.props.Document.scale);
+ this.props.Document.targetScale = NumCast(this.props.Document[this.scaleFieldKey]);
}
@action
@@ -852,7 +853,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
scaleAtPt(docpt: number[], scale: number) {
const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
this.Document.panTransformType = "Ease";
- this.layoutDoc.scale = scale;
+ this.layoutDoc[this.scaleFieldKey] = scale;
const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y);
@@ -896,7 +897,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
HistoryUtil.pushState(newState);
- const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType };
+ const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document[this.scaleFieldKey], pt: this.Document.panTransformType };
// if (!willZoom && DocumentView._focusHack.length) {
// Doc.BrushDoc(this.props.Document);
@@ -915,7 +916,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
if (afterFocus?.()) {
this.Document._panX = savedState.px;
this.Document._panY = savedState.py;
- this.Document.scale = savedState.s;
+ this.Document[this.scaleFieldKey] = savedState.s;
this.Document.panTransformType = savedState.pt;
}
}, 500);
@@ -924,7 +925,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
setScaleToZoom = (doc: Doc, scale: number = 0.75) => {
- this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc._width), this.props.PanelHeight() / NumCast(doc._height));
+ this.Document[this.scaleFieldKey] = scale * Math.min(this.props.PanelWidth() / NumCast(doc._width), this.props.PanelHeight() / NumCast(doc._height));
}
@computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
@@ -1125,7 +1126,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}));
if (this.props.isAnnotationOverlay) {
- this.props.Document.scale = Math.max(1, NumCast(this.props.Document.scale));
+ this.props.Document[this.scaleFieldKey] = Math.max(1, NumCast(this.props.Document[this.scaleFieldKey]));
}
return elements;
@@ -1206,7 +1207,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const options = ContextMenu.Instance.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
- optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
+ optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
optionItems.push({ description: "toggle snap line display", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
optionItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 085625e69..5f09fa0ee 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -122,7 +122,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
const tbox = Docs.Create.TextDocument("", {
_width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: NumCast(Doc.UserDoc().fontSize),
- _fontFamily: StrCast(Doc.UserDoc().fontFamily), _backgroundColor: StrCast(Doc.UserDoc().backgroundColor),
+ _fontFamily: StrCast(Doc.UserDoc().fontFamily),
title: "-typed text-"
});
const template = FormattedTextBox.DefaultLayout;
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index fe89b63ee..a5d355abc 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -162,7 +162,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
if (this.flexGrid) {
const savedLayouts = this.savedLayoutList;
this.childLayoutPairs.forEach(({ layout: doc }) => {
- let gridLayout = savedLayouts.find(gridLayout => gridLayout.i === doc[Id]);
+ const gridLayout = savedLayouts.find(gridLayout => gridLayout.i === doc[Id]);
gridLayout && Object.assign(gridLayout, layoutArray.find(layout => layout.i === doc[Id]) || gridLayout);
});
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index d9e7d072f..ba075886b 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -1,18 +1,18 @@
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";
-import { dropActionType } from "../../util/DragManager";
-import { CollectionView } from "../collections/CollectionView";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { Transform } from "nodemailer/lib/xoauth2";
interface ContentFittingDocumentViewProps {
Document: Doc;
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/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 1fab54d7e..ffab1d1e1 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -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;
@@ -987,7 +998,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;
@@ -1205,7 +1216,7 @@ 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();
@@ -1273,6 +1284,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..7513c881d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -115,7 +115,7 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
}
- static update(view: EditorView, lastState?: EditorState) {
+ 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,25 +160,26 @@ 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;
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 0e3e7f91e..77b93b9d2 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/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index ebaa23e99..c735155d8 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" },
+ ["button", { 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]
+ )]
+ ];
}
},
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index f81ec8c6d..66959882d 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -392,7 +392,7 @@ export namespace RichTextUtils {
const { attrs } = mark;
switch (converted) {
case "link":
- let url = attrs.href;
+ let url = attrs.allHrefs.length ? attrs.allHrefs[0].href : "";
const delimiter = "/doc/";
const alreadyShared = "?sharing=true";
if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) {