aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
authoranika-ahluwalia <anika.ahluwalia@gmail.com>2020-06-14 00:38:23 -0500
committeranika-ahluwalia <anika.ahluwalia@gmail.com>2020-06-14 00:38:23 -0500
commit1edc0a77874bc701a1821b630f167a29688faa07 (patch)
tree2ef8381b0a50e0c037048190d97fa50a1e96b911 /src/client/views/nodes
parente5a23f3d3966db9b57a1919f8d9bfff9fdfedf1e (diff)
parent9300029e95dcb8406cd05ed57c9d86107df49547 (diff)
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into anika_schema_view
Diffstat (limited to 'src/client/views/nodes')
-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
8 files changed, 108 insertions, 35 deletions
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]
+ )]
+ ];
}
},