aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts1
-rw-r--r--src/client/documents/Documents.ts21
-rw-r--r--src/client/util/LinkManager.ts13
-rw-r--r--src/client/views/MarqueeAnnotator.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx11
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx1
-rw-r--r--src/client/views/linking/LinkEditor.scss18
-rw-r--r--src/client/views/linking/LinkEditor.tsx103
-rw-r--r--src/client/views/linking/LinkMenu.tsx2
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx21
-rw-r--r--src/client/views/linking/LinkPopup.scss2
-rw-r--r--src/client/views/linking/LinkPopup.tsx44
-rw-r--r--src/client/views/linking/LinkRelationshipSearch.tsx63
-rw-r--r--src/client/views/nodes/FieldView.tsx5
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx4
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx1
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx23
-rw-r--r--src/client/views/search/SearchBox.tsx39
-rw-r--r--src/client/views/topbar/TopBar.scss1
19 files changed, 290 insertions, 85 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 5d86c755a..102ac520b 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -456,6 +456,7 @@ export function emptyFunction() { }
export function unimplementedFunction() { throw new Error("This function is not implemented, but should be."); }
+
export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type Predicate<K, V> = (entry: [K, V]) => boolean;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a579fad4d..db1e67203 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,7 +1,7 @@
import { action, runInAction } from "mobx";
-import { basename, extname } from "path";
+import { basename } from "path";
import { DateField } from "../../fields/DateField";
-import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, Initializing, updateCachedAcls } from "../../fields/Doc";
+import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Initializing, Opt, updateCachedAcls, WidthSym } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { HtmlField } from "../../fields/HtmlField";
import { InkField } from "../../fields/InkField";
@@ -13,20 +13,20 @@ import { ComputedField, ScriptField } from "../../fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../fields/Types";
import { AudioField, ImageField, PdfField, VideoField, WebField, YoutubeField } from "../../fields/URLField";
import { SharingPermissions } from "../../fields/util";
-import { MessageStore } from "../../server/Message";
import { Upload } from "../../server/SharedMediaTypes";
import { OmitKeys, Utils } from "../../Utils";
import { YoutubeBox } from "../apis/youtube/YoutubeBox";
import { DocServer } from "../DocServer";
import { Networking } from "../Network";
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { DocumentManager } from "../util/DocumentManager";
import { dropActionType } from "../util/DragManager";
import { DirectoryImportBox } from "../util/Import & Export/DirectoryImportBox";
import { LinkManager } from "../util/LinkManager";
import { Scripting } from "../util/Scripting";
import { undoBatch, UndoManager } from "../util/UndoManager";
-import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
+import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView";
import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
import { ContextMenu } from "../views/ContextMenu";
import { ContextMenuProps } from "../views/ContextMenuItem";
@@ -36,30 +36,29 @@ import { AudioBox } from "../views/nodes/AudioBox";
import { ColorBox } from "../views/nodes/ColorBox";
import { ComparisonBox } from "../views/nodes/ComparisonBox";
import { DocFocusOptions } from "../views/nodes/DocumentView";
+import { EquationBox } from "../views/nodes/EquationBox";
+import { FieldViewProps } from "../views/nodes/FieldView";
import { FilterBox } from "../views/nodes/FilterBox";
import { FontIconBox } from "../views/nodes/FontIconBox";
import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
+import { FunctionPlotBox } from "../views/nodes/FunctionPlotBox";
import { ImageBox } from "../views/nodes/ImageBox";
import { KeyValueBox } from "../views/nodes/KeyValueBox";
import { LabelBox } from "../views/nodes/LabelBox";
import { LinkBox } from "../views/nodes/LinkBox";
import { LinkDescriptionPopup } from "../views/nodes/LinkDescriptionPopup";
import { PDFBox } from "../views/nodes/PDFBox";
-import { PresBox } from "../views/nodes/trails/PresBox";
import { ScreenshotBox } from "../views/nodes/ScreenshotBox";
import { ScriptingBox } from "../views/nodes/ScriptingBox";
import { SliderBox } from "../views/nodes/SliderBox";
import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
+import { PresBox } from "../views/nodes/trails/PresBox";
+import { PresElementBox } from "../views/nodes/trails/PresElementBox";
import { VideoBox } from "../views/nodes/VideoBox";
import { WebBox } from "../views/nodes/WebBox";
-import { PresElementBox } from "../views/nodes/trails/PresElementBox";
import { SearchBox } from "../views/search/SearchBox";
import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
import { DocumentType } from "./DocumentTypes";
-import { EquationBox } from "../views/nodes/EquationBox";
-import { FunctionPlotBox } from "../views/nodes/FunctionPlotBox";
-import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { FieldViewProps } from "../views/nodes/FieldView";
const path = require('path');
const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", ""));
@@ -270,6 +269,8 @@ export class DocumentOptions {
useLinkSmallAnchor?: boolean; // whether links to this document should use a miniature linkAnchorBox
border?: string; //for searchbox
hoverBackgroundColor?: string; // background color of a label when hovered
+ linkRelationshipList?: List<string>; // for storing different link relationships (when set by user in the link editor)
+ linkColorList?: List<string>; // colors of links corresponding to specific link relationships
}
export namespace Docs {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 3579083e4..84ff3a3ff 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -3,6 +3,7 @@ import { computedFn } from "mobx-utils";
import { DirectLinksSym, Doc, DocListCast, Field, Opt } from "../../fields/Doc";
import { List } from "../../fields/List";
import { ProxyField } from "../../fields/Proxy";
+import { listSpec } from "../../fields/Schema";
import { BoolCast, Cast, PromiseValue, StrCast } from "../../fields/Types";
import { LightboxView } from "../views/LightboxView";
import { DocumentViewSharedProps, ViewAdjustment } from "../views/nodes/DocumentView";
@@ -33,6 +34,7 @@ export class LinkManager {
static links: Doc[] = [];
constructor() {
LinkManager._instance = this;
+ this.createLinkrelationshipLists();
setTimeout(() => {
LinkManager.userLinkDBs = [];
const addLinkToDoc = (link: Doc) => {
@@ -97,6 +99,17 @@ export class LinkManager {
});
}
+
+ public createLinkrelationshipLists = () => {
+ //create new lists for link relations and their associated colors if the lists don't already exist
+ if (!Doc.UserDoc().linkRelationshipList && !Doc.UserDoc().linkColorList) {
+ const linkRelationshipList = new List<string>();
+ const linkColorList = new List<string>();
+ Doc.UserDoc().linkRelationshipList = linkRelationshipList;
+ Doc.UserDoc().linkColorList = linkColorList;
+ }
+ }
+
public addLink(linkDoc: Doc, checkExists = false) {
if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) {
Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc);
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index c08734c3e..1897572f8 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -66,6 +66,8 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight("rgba(173, 216, 230, 0.75)", true));
AnchorMenu.Instance.Highlight = this.highlight;
AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => this.highlight("rgba(173, 216, 230, 0.75)", true, savedAnnotations);
+ AnchorMenu.Instance.onMakeAnchor = AnchorMenu.Instance.GetAnchor;
+
/**
* This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.
* It also initiates a Drag/Drop interaction to place the text annotation.
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index a8f5e6dd2..59891b7a1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -9,6 +9,9 @@ import { SnappingManager } from "../../../util/SnappingManager";
import { DocumentView } from "../../nodes/DocumentView";
import "./CollectionFreeFormLinkView.scss";
import React = require("react");
+import { LinkManager } from "../../../util/LinkManager";
+import { List } from "../../../fields/List";
+
export interface CollectionFreeFormLinkViewProps {
A: DocumentView;
@@ -173,8 +176,14 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
render() {
if (!this.renderData) return (null);
const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
+ LinkManager.currentLink = this.props.LinkDocs[0];
+ const linkRelationship = LinkManager.currentLink?.linkRelationship; //get string representing relationship
+ const linkRelationshipList = Doc.UserDoc().linkRelationshipList as List<string>;
+ const linkColorList = Doc.UserDoc().linkColorList as List<string>;
+ //access stroke color using index of the relationship in the color list (default black)
+ const strokeColor = linkRelationshipList.indexOf(linkRelationship) == -1 ? "black" : linkColorList[linkRelationshipList.indexOf(linkRelationship)];
return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
- <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2" }}
+ <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2", stroke: strokeColor }}
d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} />
{textX === undefined ? (null) : <text className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown} >
{StrCast(this.props.LinkDocs[0].description)}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 5e0b31754..e812064b7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -9,6 +9,7 @@ import "./CollectionFreeFormLinksView.scss";
import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
import React = require("react");
import { DocumentType } from "../../../documents/DocumentTypes";
+import { LinkManager } from "../../../util/LinkManager";
@observer
export class CollectionFreeFormLinksView extends React.Component {
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index e45a91d57..abd413f57 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -106,6 +106,24 @@
}
}
+.linkEditor-relationship-dropdown {
+ position: absolute;
+ width: 154px;
+ max-height: 90px;
+ overflow: auto;
+ background: white;
+
+ p {
+ padding: 3px;
+ cursor: pointer;
+ border: 1px solid $medium-gray;
+ }
+
+ p:hover {
+ background: $light-blue;
+ }
+}
+
.linkEditor-followingDropdown {
padding-left: 26px;
padding-right: 6.5px;
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index f74b422d3..fba95cad2 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -2,11 +2,12 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from "@material-ui/core";
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../fields/Doc";
+import { Doc, StrListCast } from "../../../fields/Doc";
import { DateCast, StrCast } from "../../../fields/Types";
import { LinkManager } from "../../util/LinkManager";
import { undoBatch } from "../../util/UndoManager";
import './LinkEditor.scss';
+import { LinkRelationshipSearch } from "./LinkRelationshipSearch";
import React = require("react");
@@ -26,6 +27,8 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
@computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; }
@observable private buttonColor: string = "";
@observable private relationshipButtonColor: string = "";
+ @observable private relationshipSearchVisibility: string = "none";
+ @observable private searchIsActive: boolean = false;
//@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION";
@@ -39,12 +42,40 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
setRelationshipValue = action((value: string) => {
if (LinkManager.currentLink) {
LinkManager.currentLink.linkRelationship = value;
+ const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList);
+ const linkColorList = StrListCast(Doc.UserDoc().linkColorList);
+ // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color
+ if (linkRelationshipList && !linkRelationshipList.includes(value)) {
+ linkRelationshipList.push(value);
+ const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")";
+ linkColorList.push(randColor)
+ }
this.relationshipButtonColor = "rgb(62, 133, 55)";
setTimeout(action(() => this.relationshipButtonColor = ""), 750);
return true;
}
});
+ /**
+ * returns list of strings with possible existing relationships that contain what is currently in the input field
+ */
+ @action
+ getRelationshipResults = () => {
+ const query = this.relationship; //current content in input box
+ const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList)
+ if (linkRelationshipList) {
+ return linkRelationshipList.filter(rel => rel.includes(query));
+ }
+ }
+
+ /**
+ * toggles visibility of the relationship search results when the input field is focused on
+ */
+ @action
+ toggleRelationshipResults = () => {
+ this.relationshipSearchVisibility = this.relationshipSearchVisibility == "none" ? "block" : "none";
+ }
+
@undoBatch
setDescripValue = action((value: string) => {
if (LinkManager.currentLink) {
@@ -55,7 +86,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
}
});
- onKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ onDescriptionKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
this.setDescripValue(this.description);
document.getElementById('input')?.blur();
@@ -69,16 +100,38 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
}
}
- onDown = () => this.setDescripValue(this.description);
- onRelationshipDown = () => this.setRelationshipValue(this.description);
+ onDescriptionDown = () => this.setDescripValue(this.description);
+ onRelationshipDown = () => this.setRelationshipValue(this.relationship);
+
+ onBlur = () => {
+ //only hide the search results if the user clicks out of the input AND not on any of the search results
+ // i.e. if search is not active
+ if (!this.searchIsActive) {
+ this.toggleRelationshipResults();
+ }
+ }
+ onFocus = () => {
+ this.toggleRelationshipResults();
+ }
+ toggleSearchIsActive = () => {
+ this.searchIsActive = !this.searchIsActive;
+ }
@action
- handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.description = e.target.value; }
+ handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.description = e.target.value; }
@action
- handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.relationship = e.target.value; }
-
+ handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.relationship = e.target.value;
+ }
+ @action
+ handleRelationshipSearchChange = (result: string) => {
+ this.setRelationshipValue(result);
+ this.toggleRelationshipResults();
+ this.relationship = result;
+ }
@computed
get editRelationship() {
+ //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS
return <div className="linkEditor-description">
<div className="linkEditor-description-label">Link Relationship:</div>
<div className="linkEditor-description-input">
@@ -87,11 +140,18 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
style={{ width: "100%" }}
id="input"
value={this.relationship}
- placeholder={"enter link label"}
- // color={"rgb(88, 88, 88)"}
+ placeholder={"Enter link relationship"}
onKeyDown={this.onRelationshipKey}
onChange={this.handleRelationshipChange}
+ onFocus={this.onFocus}
+ onBlur={this.onBlur}
></input>
+ <LinkRelationshipSearch
+ results={this.getRelationshipResults()}
+ display={this.relationshipSearchVisibility}
+ handleRelationshipSearchChange={this.handleRelationshipSearchChange}
+ toggleSearch={this.toggleSearchIsActive}
+ />
</div>
<div className="linkEditor-description-add-button"
style={{ background: this.relationshipButtonColor }}
@@ -110,15 +170,14 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
style={{ width: "100%" }}
id="input"
value={this.description}
- placeholder={"enter link label"}
- // color={"rgb(88, 88, 88)"}
- onKeyDown={this.onKey}
- onChange={this.handleChange}
+ placeholder={"Enter link description"}
+ onKeyDown={this.onDescriptionKey}
+ onChange={this.handleDescriptionChange}
></input>
</div>
<div className="linkEditor-description-add-button"
style={{ background: this.buttonColor }}
- onPointerDown={this.onDown}>Set</div>
+ onPointerDown={this.onDescriptionDown}>Set</div>
</div>
</div>;
}
@@ -149,35 +208,35 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
<div className="linkEditor-followingDropdown-option"
onPointerDown={() => this.changeFollowBehavior("default")}>
Default
- </div>
+ </div>
<div className="linkEditor-followingDropdown-option"
onPointerDown={() => this.changeFollowBehavior("add:left")}>
Always open in new left pane
- </div>
+ </div>
<div className="linkEditor-followingDropdown-option"
onPointerDown={() => this.changeFollowBehavior("add:right")}>
Always open in new right pane
- </div>
+ </div>
<div className="linkEditor-followingDropdown-option"
onPointerDown={() => this.changeFollowBehavior("replace:right")}>
Always replace right tab
- </div>
+ </div>
<div className="linkEditor-followingDropdown-option"
onPointerDown={() => this.changeFollowBehavior("replace:left")}>
Always replace left tab
- </div>
+ </div>
<div className="linkEditor-followingDropdown-option"
onPointerDown={() => this.changeFollowBehavior("fullScreen")}>
Always open full screen
- </div>
+ </div>
<div className="linkEditor-followingDropdown-option"
onPointerDown={() => this.changeFollowBehavior("add")}>
Always open in a new tab
- </div>
+ </div>
<div className="linkEditor-followingDropdown-option"
onPointerDown={() => this.changeFollowBehavior("replace")}>
Replace Tab
- </div>
+ </div>
{this.props.linkDoc.linksToAnnotation ?
<div className="linkEditor-followingDropdown-option"
onPointerDown={() => this.changeFollowBehavior("openExternal")}>
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 6fc860447..53fe3f682 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -41,7 +41,7 @@ export class LinkMenu extends React.Component<Props> {
/**
* maps each link to a JSX element to be rendered
- * @param groups LinkManager containing info of all of the links
+ * @param groups containing info of all of the links
* @returns list of link JSX elements if there at least one linked element
*/
renderAllGroups = (groups: Map<string, Array<Doc>>): Array<JSX.Element> => {
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index c7586a467..cb6571f92 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -1,5 +1,5 @@
import { observer } from "mobx-react";
-import { Doc } from "../../../fields/Doc";
+import { Doc, StrListCast } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
import { Cast } from "../../../fields/Types";
import { LinkManager } from "../../util/LinkManager";
@@ -20,6 +20,23 @@ interface LinkMenuGroupProps {
export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
private _menuRef = React.createRef<HTMLDivElement>();
+ getBackgroundColor = (): string => {
+ const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList);
+ const linkColorList = StrListCast(Doc.UserDoc().linkColorList);
+ let color = "white";
+ // if this link's relationship property is not default "link", set its color
+ if (linkRelationshipList) {
+ const relationshipIndex = linkRelationshipList.indexOf(this.props.groupType);
+ const RGBcolor: string = linkColorList[relationshipIndex];
+ if (RGBcolor) {
+ //set opacity to 0.25 by modifiying the rgb string
+ color = RGBcolor.slice(0, RGBcolor.length - 1) + ", 0.25)";
+ console.log(color);
+ }
+ }
+ return color;
+ }
+
render() {
const set = new Set<Doc>(this.props.group);
const groupItems = Array.from(set.keys()).map(linkDoc => {
@@ -39,7 +56,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
return (
<div className="linkMenu-group" ref={this._menuRef}>
- <div className="linkMenu-group-name">
+ <div className="linkMenu-group-name" style={{ background: this.getBackgroundColor() }}>
<p className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"}> {this.props.groupType}:</p>
</div>
<div className="linkMenu-group-wrapper">
diff --git a/src/client/views/linking/LinkPopup.scss b/src/client/views/linking/LinkPopup.scss
index 8ae65158d..60c9ebfcd 100644
--- a/src/client/views/linking/LinkPopup.scss
+++ b/src/client/views/linking/LinkPopup.scss
@@ -5,7 +5,7 @@
height: 200px;
width: 200px;
position: absolute;
- padding: 15px;
+ // padding: 15px;
border-radius: 3px;
input {
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index df469c53b..c8be9069c 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -1,33 +1,21 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
-import { action, observable, runInAction } from 'mobx';
+import { action, observable } from 'mobx';
import { observer } from "mobx-react";
-import { Doc, DocListCast } from '../../../fields/Doc';
-import { Cast, StrCast } from '../../../fields/Types';
-import { WebField } from '../../../fields/URLField';
-import { emptyFunction, setupMoveUpEvents, returnFalse, returnTrue, returnEmptyDoclist, returnEmptyFilter } from '../../../Utils';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager } from '../../util/DragManager';
-import { Hypothesis } from '../../util/HypothesisUtils';
-import { LinkManager } from '../../util/LinkManager';
-import { undoBatch } from '../../util/UndoManager';
-import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
-import { DocumentView, DocumentViewSharedProps } from '../nodes/DocumentView';
-import { LinkDocPreview } from '../nodes/LinkDocPreview';
-import './LinkPopup.scss';
-import React = require("react");
+import { EditorView } from 'prosemirror-view';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
+import { DocUtils } from '../../documents/Documents';
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
-import { DefaultStyleProvider } from '../StyleProvider';
import { Transform } from '../../util/Transform';
-import { DocUtils } from '../../documents/Documents';
-import { SearchBox } from '../search/SearchBox';
-import { EditorView } from 'prosemirror-view';
+import { undoBatch } from '../../util/UndoManager';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { SearchBox } from '../search/SearchBox';
+import { DefaultStyleProvider } from '../StyleProvider';
+import './LinkPopup.scss';
+import React = require("react");
+import { Doc, Opt } from '../../../fields/Doc';
interface LinkPopupProps {
showPopup: boolean;
+ linkFrom?: () => Doc | undefined;
// groupType: string;
// linkDoc: Doc;
// docView: DocumentView;
@@ -62,9 +50,10 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
render() {
const popupVisibility = this.props.showPopup ? "block" : "none";
+ const linkDoc = this.props.linkFrom ? this.props.linkFrom : undefined;
return (
<div className="linkPopup-container" style={{ display: popupVisibility }}>
- <div className="linkPopup-url-container">
+ {/* <div className="linkPopup-url-container">
<input autoComplete="off" type="text" value={this.linkURL} placeholder="Enter URL..." onChange={this.onLinkChange} />
<button onPointerDown={e => this.makeLinkToURL(this.linkURL, "add:right")}
style={{ display: "block", margin: "10px auto", }}>Apply hyperlink</button>
@@ -72,14 +61,17 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
<div className="divider">
<div className="line"></div>
<p className="divider-text">or</p>
- </div>
+ </div> */}
<div className="linkPopup-document-search-container">
{/* <i></i>
<input defaultValue={""} autoComplete="off" type="text" placeholder="Search for Document..." id="search-input"
className="linkPopup-searchBox searchBox-input" /> */}
- <SearchBox Document={CurrentUserUtils.MySearchPanelDoc}
+ <SearchBox
+ Document={CurrentUserUtils.MySearchPanelDoc}
DataDoc={CurrentUserUtils.MySearchPanelDoc}
+ linkFrom={linkDoc}
+ linkSearch={true}
fieldKey="data"
dropAction="move"
isSelected={returnTrue}
diff --git a/src/client/views/linking/LinkRelationshipSearch.tsx b/src/client/views/linking/LinkRelationshipSearch.tsx
new file mode 100644
index 000000000..8f83f0e6e
--- /dev/null
+++ b/src/client/views/linking/LinkRelationshipSearch.tsx
@@ -0,0 +1,63 @@
+import { observer } from "mobx-react";
+import './LinkEditor.scss';
+import React = require("react");
+
+interface LinkRelationshipSearchProps {
+ results: string[] | undefined;
+ display: string;
+ //callback fn to set rel + hide dropdown upon setting
+ handleRelationshipSearchChange: (result: string) => void;
+ toggleSearch: () => void;
+}
+@observer
+export class LinkRelationshipSearch extends React.Component<LinkRelationshipSearchProps> {
+
+ handleResultClick = (e: React.MouseEvent) => {
+ const relationship = (e.target as HTMLParagraphElement).textContent;
+ if (relationship) {
+ this.props.handleRelationshipSearchChange(relationship)
+ }
+ }
+
+ handleMouseEnter = () => {
+ this.props.toggleSearch();
+ }
+
+ handleMouseLeave = () => {
+ this.props.toggleSearch();
+ }
+
+ /**
+ * Render an empty div to increase the height of LinkEditor to accommodate 2+ results
+ */
+ emptyDiv = () => {
+ if (this.props.results && this.props.results.length > 2 && this.props.display == "block") {
+ return <div style={{ height: "50px" }}></div>
+ }
+ }
+
+ render() {
+ return (
+ <div className="linkEditor-relationship-dropdown-container">
+ <div className="linkEditor-relationship-dropdown"
+ style={{ display: this.props.display }}
+ onMouseEnter={this.handleMouseEnter}
+ onMouseLeave={this.handleMouseLeave}
+ >
+ { // return a dropdown of relationship results if there exist results
+ this.props.results
+ ? this.props.results.map(result => {
+ return (<p key={result} onClick={this.handleResultClick}>
+ {result}
+ </p>)
+ })
+ : <p>No matching relationships</p>
+ }
+ </div>
+
+ {/*Render an empty div to increase the height of LinkEditor to accommodate 2+ results */}
+ {this.emptyDiv()}
+ </div>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index ebbc1138a..ee81e106a 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -2,11 +2,10 @@ import React = require("react");
import { computed } from "mobx";
import { observer } from "mobx-react";
import { DateField } from "../../../fields/DateField";
-import { Doc, Field, FieldResult, Opt } from "../../../fields/Doc";
+import { Doc, Field, FieldResult } from "../../../fields/Doc";
import { List } from "../../../fields/List";
-import { VideoField, WebField } from "../../../fields/URLField";
+import { WebField } from "../../../fields/URLField";
import { DocumentViewSharedProps } from "./DocumentView";
-import { VideoBox } from "./VideoBox";
//
// these properties get assigned through the render() method of the DocumentView when it creates this node.
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 30b272a9a..b62a4dd56 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -54,7 +54,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
}}>
<input className="linkDescriptionPopup-input"
onKeyPress={e => e.key === "Enter" && this.onDismiss(true)}
- placeholder={"(optional) enter link label..."}
+ placeholder={"(Optional) Enter link description..."}
onChange={(e) => this.descriptionChanged(e)}>
</input>
<div className="linkDescriptionPopup-btn">
@@ -65,4 +65,4 @@ export class LinkDescriptionPopup extends React.Component<{}> {
</div>
</div>;
}
-} \ No newline at end of file
+} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b0b8ce75d..d1027dfd7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -217,6 +217,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._editorView?.state && RichTextMenu.Instance.insertHighlight(color, this._editorView.state, this._editorView?.dispatch);
return undefined;
});
+ AnchorMenu.Instance.onMakeAnchor = this.getAnchor;
/**
* This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.
* It also initiates a Drag/Drop interaction to place the text annotation.
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 55816ed52..75e3f81fb 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -45,6 +45,8 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable public Highlighting: boolean = false;
@observable public Status: "marquee" | "annotation" | "" = "";
+ public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
+
public OnClick: (e: PointerEvent) => void = unimplementedFunction;
public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
public Highlight: (color: string, isPushpin: boolean) => Opt<Doc> = (color: string, isPushpin: boolean) => undefined;
@@ -65,7 +67,11 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
componentDidMount() {
this._disposer = reaction(() => SelectionManager.Views(),
- selected => AnchorMenu.Instance.fadeOut(true));
+ selected => {
+ this._showLinkPopup = false;
+ console.log("unmount");
+ AnchorMenu.Instance.fadeOut(true)
+ });
}
pointerDown = (e: React.PointerEvent) => {
@@ -145,14 +151,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
<FontAwesomeIcon icon="comment-alt" size="lg" />
</button>
</Tooltip>,
-
- //NOTE: link popup is currently incomplete
- // <Tooltip key="link" title={<div className="dash-tooltip">{"Link selected text to document or URL"}</div>}>
- // <button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup} style={{}}>
- // <FontAwesomeIcon icon="link" size="lg" />
- // </button>
- // </Tooltip>,
- // <LinkPopup showPopup={this._showLinkPopup} />
+ //NOTE: link popup is currently in progress
+ <Tooltip key="link" title={<div className="dash-tooltip">{"Link selected text to document"}</div>}>
+ <button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup} style={{}}>
+ <FontAwesomeIcon icon="link" size="lg" />
+ </button>
+ </Tooltip>,
+ <LinkPopup showPopup={this._showLinkPopup} linkFrom={this.onMakeAnchor} />
] : [
<Tooltip key="trash" title={<div className="dash-tooltip">{"Remove Link Anchor"}</div>}>
<button className="antimodeMenu-button" onPointerDown={this.Delete}>
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index b07879674..07186fe8d 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -7,23 +7,31 @@ import { Id } from '../../../fields/FieldSymbols';
import { createSchema, makeInterface } from '../../../fields/Schema';
import { StrCast } from '../../../fields/Types';
import { DocumentType } from "../../documents/DocumentTypes";
-import { DocumentManager } from "../../util/DocumentManager";
import { CollectionDockingView } from "../collections/CollectionDockingView";
import { ViewBoxBaseComponent } from "../DocComponent";
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import "./SearchBox.scss";
+import { DocumentManager } from '../../util/DocumentManager';
+import { DocUtils } from '../../documents/Documents';
-export const searchSchema = createSchema({ Document: Doc });
+export const searchSchema = createSchema({
+ Document: Doc
+});
type SearchBoxDocument = makeInterface<[typeof documentSchema, typeof searchSchema]>;
const SearchBoxDocument = makeInterface(documentSchema, searchSchema);
+export interface SearchBoxProps extends FieldViewProps {
+ linkSearch: boolean;
+ linkFrom?: (() => Doc | undefined) | undefined;
+}
+
/**
* This is the SearchBox component. It represents the search box input and results in
* the search panel on the left side of the screen.
*/
@observer
-export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDocument>(SearchBoxDocument) {
+export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDocument>(SearchBoxDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); }
public static Instance: SearchBox;
@@ -100,6 +108,18 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
this._selectedResult = doc;
});
+ makeLink = action((linkTo: Doc) => {
+ console.log("[makeLink-1] got here!")
+ console.log(linkTo.title);
+ if (this.props.linkFrom){
+ const linkFrom = this.props.linkFrom();
+ if (linkFrom){
+ console.log(linkFrom.title);
+ DocUtils.MakeLink({doc: linkFrom}, {doc:linkTo});
+ }
+ }
+ });
+
/**
* @param {Doc[]} docs - docs to be searched through recursively
* @param {number, Doc => void} func - function to be called on each doc
@@ -275,6 +295,9 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
render() {
var validResults = 0;
+ const isLinkSearch:boolean = this.props.linkSearch;
+
+
const results = this._results.map(result => {
var className = "searchBox-results-scroll-view-result";
@@ -285,7 +308,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
if (this._docTypeString == "all" || this._docTypeString == result[0].type) {
validResults++;
return (
- <div key={result[0][Id]} onClick={() => this.onResultClick(result[0])} className={className}>
+ <div key={result[0][Id]} onClick={isLinkSearch ? () => this.makeLink(result[0]) : () => this.onResultClick(result[0])} className={className}>
<div className="searchBox-result-title">
{result[0].title}
</div>
@@ -306,11 +329,11 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
return (
<div style={{ pointerEvents: "all" }} className="searchBox-container">
- <div className="searchBox-bar">
- <select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}>
+ <div className="searchBox-bar" >
+ {isLinkSearch ? (null) : <select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}>
{this.selectOptions}
- </select>
- <input defaultValue={""} autoComplete="off" onChange={this.onInputChange} type="text" placeholder="Search..." id="search-input" className="searchBox-input" ref={this._inputRef} />
+ </select>}
+ <input defaultValue={""} autoComplete="off" onChange={this.onInputChange} type="text" placeholder="Search..." id="search-input" className="searchBox-input" style={{width: isLinkSearch ? "100%" : undefined, borderRadius: isLinkSearch ? "5px" : undefined}} ref={this._inputRef} />
</div >
<div className="searchBox-results-container">
<div className="searchBox-results-count">
diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss
index 2ecbb536b..d04ae8a80 100644
--- a/src/client/views/topbar/TopBar.scss
+++ b/src/client/views/topbar/TopBar.scss
@@ -53,6 +53,7 @@
.topbar-dashboards {
display: flex;
flex-direction: row;
+ gap: 5px;
}
.topbar-lozenge-dashboard {