aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorusodhi <61431818+usodhi@users.noreply.github.com>2020-07-08 01:53:11 +0530
committerusodhi <61431818+usodhi@users.noreply.github.com>2020-07-08 01:53:11 +0530
commit5e9a710d046150aaea6d8d50e1ecc271f44ca50b (patch)
treefff94fc835035b8122ff6d74cce5dc9c49968394 /src
parentf4830de4f8c4794ec98e54be9ba8730e46155c35 (diff)
parent1442a7769becaec7aeaae437c6eae9107ec90750 (diff)
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into acls_uv
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/HypothesisAuthenticationManager.scss26
-rw-r--r--src/client/apis/HypothesisAuthenticationManager.tsx158
-rw-r--r--src/client/documents/Documents.ts48
-rw-r--r--src/client/util/CurrentUserUtils.ts1
-rw-r--r--src/client/util/LinkManager.ts6
-rw-r--r--src/client/views/EditableView.tsx6
-rw-r--r--src/client/views/GestureOverlay.tsx2
-rw-r--r--src/client/views/GlobalKeyHandler.ts2
-rw-r--r--src/client/views/MainView.tsx10
-rw-r--r--src/client/views/RecommendationsBox.tsx2
-rw-r--r--src/client/views/collections/CollectionLinearView.scss12
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx19
-rw-r--r--src/client/views/collections/CollectionSubView.tsx43
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx2
-rw-r--r--src/client/views/collections/CollectionView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx2
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx2
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx2
-rw-r--r--src/client/views/linking/LinkEditor.scss94
-rw-r--r--src/client/views/linking/LinkEditor.tsx104
-rw-r--r--src/client/views/linking/LinkMenu.scss24
-rw-r--r--src/client/views/linking/LinkMenu.tsx30
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx6
-rw-r--r--src/client/views/linking/LinkMenuItem.scss53
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx36
-rw-r--r--src/client/views/nodes/AudioBox.scss1
-rw-r--r--src/client/views/nodes/AudioBox.tsx1
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx62
-rw-r--r--src/client/views/nodes/DocumentView.tsx44
-rw-r--r--src/client/views/nodes/LinkCreatedBox.tsx8
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.scss69
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx73
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx2
-rw-r--r--src/server/ApiManagers/HypothesisManager.ts44
-rw-r--r--src/server/ApiManagers/UploadManager.ts3
-rw-r--r--src/server/apis/google/GoogleApiServerUtils.ts3
-rw-r--r--src/server/index.ts3
38 files changed, 860 insertions, 147 deletions
diff --git a/src/client/apis/HypothesisAuthenticationManager.scss b/src/client/apis/HypothesisAuthenticationManager.scss
new file mode 100644
index 000000000..bd30dd94f
--- /dev/null
+++ b/src/client/apis/HypothesisAuthenticationManager.scss
@@ -0,0 +1,26 @@
+.authorize-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ .paste-target {
+ padding: 5px;
+ width: 100%;
+ }
+
+ .avatar {
+ border-radius: 50%;
+ }
+
+ .welcome {
+ font-style: italic;
+ margin-top: 15px;
+ }
+
+ .disconnect {
+ font-size: 10px;
+ margin-top: 20px;
+ color: red;
+ cursor: grab;
+ }
+} \ No newline at end of file
diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx
new file mode 100644
index 000000000..cffb87227
--- /dev/null
+++ b/src/client/apis/HypothesisAuthenticationManager.tsx
@@ -0,0 +1,158 @@
+import { observable, action, reaction, runInAction, IReactionDisposer } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import MainViewModal from "../views/MainViewModal";
+import { Opt } from "../../fields/Doc";
+import { Networking } from "../Network";
+import "./HypothesisAuthenticationManager.scss";
+import { Scripting } from "../util/Scripting";
+
+const prompt = "Paste authorization code here...";
+
+@observer
+export default class HypothesisAuthenticationManager extends React.Component<{}> {
+ public static Instance: HypothesisAuthenticationManager;
+ private authenticationLink: Opt<string> = undefined;
+ @observable private openState = false;
+ @observable private authenticationCode: Opt<string> = undefined;
+ @observable private showPasteTargetState = false;
+ @observable private success: Opt<boolean> = undefined;
+ @observable private displayLauncher = true;
+ @observable private credentials: string;
+ private disposer: Opt<IReactionDisposer>;
+
+ private set isOpen(value: boolean) {
+ runInAction(() => this.openState = value);
+ }
+
+ private set shouldShowPasteTarget(value: boolean) {
+ runInAction(() => this.showPasteTargetState = value);
+ }
+
+ public cancel() {
+ this.openState && this.resetState(0, 0);
+ }
+
+ public fetchAccessToken = async (displayIfFound = false) => {
+ let response: any = await Networking.FetchFromServer("/readHypothesisAccessToken");
+ // if this is an authentication url, activate the UI to register the new access token
+ if (!response) { // new RegExp(AuthenticationUrl).test(response)) {
+ this.isOpen = true;
+ this.authenticationLink = response;
+ return new Promise<string>(async resolve => {
+ this.disposer?.();
+ this.disposer = reaction(
+ () => this.authenticationCode,
+ async authenticationCode => {
+ if (authenticationCode) {
+ this.disposer?.();
+ Networking.PostToServer("/writeHypothesisAccessToken", { authenticationCode });
+ runInAction(() => {
+ this.success = true;
+ this.credentials = response;
+ });
+ this.resetState();
+ resolve(authenticationCode);
+ }
+ }
+ );
+ });
+ }
+
+ if (displayIfFound) {
+ runInAction(() => {
+ this.success = true;
+ this.credentials = response;
+ });
+ this.resetState(-1, -1);
+ this.isOpen = true;
+ }
+ return response.access_token;
+ }
+
+ resetState = action((visibleForMS: number = 3000, fadesOutInMS: number = 500) => {
+ if (!visibleForMS && !fadesOutInMS) {
+ runInAction(() => {
+ this.isOpen = false;
+ this.success = undefined;
+ this.displayLauncher = true;
+ this.credentials = "";
+ this.shouldShowPasteTarget = false;
+ this.authenticationCode = undefined;
+ });
+ return;
+ }
+ this.authenticationCode = undefined;
+ this.displayLauncher = false;
+ this.shouldShowPasteTarget = false;
+ if (visibleForMS > 0 && fadesOutInMS > 0) {
+ setTimeout(action(() => {
+ this.isOpen = false;
+ setTimeout(action(() => {
+ this.success = undefined;
+ this.displayLauncher = true;
+ this.credentials = "";
+ }), fadesOutInMS);
+ }), visibleForMS);
+ }
+ });
+
+ constructor(props: {}) {
+ super(props);
+ HypothesisAuthenticationManager.Instance = this;
+ }
+
+ private get renderPrompt() {
+ return (
+ <div className={'authorize-container'}>
+
+ {this.displayLauncher ? <button
+ className={"dispatch"}
+ onClick={() => {
+ this.shouldShowPasteTarget = true;
+ }}
+ style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }}
+ >Authorize a Hypothesis account...</button> : (null)}
+ {this.showPasteTargetState ? <input
+ className={'paste-target'}
+ onChange={action(e => this.authenticationCode = e.currentTarget.value)}
+ placeholder={prompt}
+ /> : (null)}
+ {this.credentials ?
+ <>
+ <span
+ className={'welcome'}
+ >Welcome to Dash, {this.credentials}
+ </span>
+ <div
+ className={'disconnect'}
+ onClick={async () => {
+ await Networking.FetchFromServer("/revokeHypothesisAccessToken");
+ this.resetState(0, 0);
+ }}
+ >Disconnect Account</div>
+ </> : (null)}
+ </div>
+ );
+ }
+
+ private get dialogueBoxStyle() {
+ const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red";
+ return { borderColor, transition: "0.2s borderColor ease" };
+ }
+
+ render() {
+ return (
+ <MainViewModal
+ isDisplayed={this.openState}
+ interactive={true}
+ contents={this.renderPrompt}
+ overlayDisplayedOpacity={0.9}
+ dialogueBoxStyle={this.dialogueBoxStyle}
+ />
+ );
+ }
+
+}
+
+Scripting.addGlobal("HypothesisAuthenticationManager", HypothesisAuthenticationManager); \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f81c25bab..fa85d58f0 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -94,6 +94,7 @@ export interface DocumentOptions {
label?: string; // short form of title for use as an icon label
style?: string;
page?: number;
+ description?: string; // added for links
_viewScale?: number;
isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents
forceActive?: boolean;
@@ -259,7 +260,7 @@ export namespace Docs {
}],
[DocumentType.LINK, {
layout: { view: LinkBox, dataField: defaultDataKey },
- options: { _height: 150 }
+ options: { _height: 150, description: "" }
}],
[DocumentType.LINKDB, {
data: new List<Doc>(),
@@ -829,6 +830,47 @@ export namespace Docs {
}
export namespace DocUtils {
+ export function FilterDocs(docs: Doc[], docFilters: string[], docRangeFilters: string[], viewSpecScript?: ScriptField) {
+ const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+
+ const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
+ for (let i = 0; i < docFilters.length; i += 3) {
+ const [key, value, modifiers] = docFilters.slice(i, i + 3);
+ if (!filterFacets[key]) {
+ filterFacets[key] = {};
+ }
+ filterFacets[key][value] = modifiers;
+ }
+
+ const filteredDocs = docFilters.length ? childDocs.filter(d => {
+ for (const facetKey of Object.keys(filterFacets)) {
+ const facet = filterFacets[facetKey];
+ const satisfiesFacet = Object.keys(facet).some(value => {
+ if (facet[value] === "match") {
+ return d[facetKey] === undefined || Field.toString(d[facetKey] as Field).includes(value);
+ }
+ return (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value);
+ });
+ if (!satisfiesFacet) {
+ return false;
+ }
+ }
+ return true;
+ }) : childDocs;
+ const rangeFilteredDocs = filteredDocs.filter(d => {
+ for (let i = 0; i < docRangeFilters.length; i += 3) {
+ const key = docRangeFilters[i];
+ const min = Number(docRangeFilters[i + 1]);
+ const max = Number(docRangeFilters[i + 2]);
+ const val = Cast(d[key], "number", null);
+ if (val !== undefined && (val < min || val > max)) {
+ return false;
+ }
+ }
+ return true;
+ });
+ return rangeFilteredDocs;
+ }
export function Publish(promoteDoc: Doc, targetID: string, addDoc: any, remDoc: any) {
targetID = targetID.replace(/^-/, "").replace(/\([0-9]*\)$/, "");
@@ -867,12 +909,12 @@ export namespace DocUtils {
DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
}
- export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) {
+ export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string) {
const sv = DocumentManager.Instance.getDocumentView(source.doc);
if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
if (target.doc === Doc.UserDoc()) return undefined;
- const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship, layoutKey: "layout_linkView" }, id);
+ const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship, layoutKey: "layout_linkView", description }, id);
linkDoc.layout_linkView = Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null);
Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('self.anchor1?.title +" (" + (self.linkRelationship||"to") +") " + self.anchor2?.title');
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 4276e04e4..8099228c6 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -424,6 +424,7 @@ export class CurrentUserUtils {
{ title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc },
{ title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
{ title: "Connect a Google Account", label: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
+ { title: "Connect a Hypothesis Account", label: "Hypothesis Account", icon: "heading", click: 'HypothesisAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
];
}
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 749fabfcc..6da581f35 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,4 +1,4 @@
-import { Doc, DocListCast } from "../../fields/Doc";
+import { Doc, DocListCast, Opt } from "../../fields/Doc";
import { List } from "../../fields/List";
import { listSpec } from "../../fields/Schema";
import { Cast, StrCast } from "../../fields/Types";
@@ -23,6 +23,10 @@ import { Scripting } from "./Scripting";
export class LinkManager {
private static _instance: LinkManager;
+
+
+ public static currentLink: Opt<Doc>;
+
public static get Instance(): LinkManager {
return this._instance || (this._instance = new this());
}
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 628db366f..25a87ab56 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -194,7 +194,11 @@ export class EditableView extends React.Component<EditableProps> {
ref={this._ref}
style={{ display: this.props.display, minHeight: "20px", height: `${this.props.height ? this.props.height : "auto"}`, maxHeight: `${this.props.maxHeight}` }}
onClick={this.onClick} placeholder={this.props.placeholder}>
- <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
+ <span style={{
+ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize,
+ color: this.props.contents ? this.props.color ? this.props.color : "black" : "grey"
+ }}>
+ {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
</div>
);
}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 487467b2b..cdc468066 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -551,7 +551,7 @@ export default class GestureOverlay extends Touchable {
else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) {
// we don't want to create a link between ink strokes (doing so makes drawing a t very hard)
if (this._d1.type !== "ink" && doc.type !== "ink") {
- DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }, "gestural link");
+ DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }, "gestural link", "");
actionPerformed = true;
}
}
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 34f666f62..a3a023164 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -7,6 +7,7 @@ import { List } from "../../fields/List";
import { ScriptField } from "../../fields/ScriptField";
import { Cast, PromiseValue } from "../../fields/Types";
import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
+import HypothesisAuthenticationManager from "../apis/HypothesisAuthenticationManager";
import { DocServer } from "../DocServer";
import { DocumentType } from "../documents/DocumentTypes";
import { DictationManager } from "../util/DictationManager";
@@ -104,6 +105,7 @@ export default class KeyManager {
DictationManager.Controls.stop();
// RecommendationsBox.Instance.closeMenu();
GoogleAuthenticationManager.Instance.cancel();
+ HypothesisAuthenticationManager.Instance.cancel();
SharingManager.Instance.close();
break;
case "delete":
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 15f818d1f..81195b550 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -5,7 +5,8 @@ import {
faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
- faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight
+ faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
+ faHeading
} from '@fortawesome/free-solid-svg-icons';
import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -61,6 +62,8 @@ import { LinkMenu } from './linking/LinkMenu';
import { LinkDocPreview } from './nodes/LinkDocPreview';
import { Fade } from '@material-ui/core';
import { LinkCreatedBox } from './nodes/LinkCreatedBox';
+import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
+import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager';
@observer
export class MainView extends React.Component {
@@ -143,7 +146,8 @@ export class MainView extends React.Component {
faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
- faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight);
+ faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
+ faHeading);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -601,6 +605,7 @@ export class MainView extends React.Component {
<SettingsManager />
<GroupManager />
<GoogleAuthenticationManager />
+ <HypothesisAuthenticationManager />
<DocumentDecorations />
<GestureOverlay>
<RichTextMenu key="rich" />
@@ -608,6 +613,7 @@ export class MainView extends React.Component {
</GestureOverlay>
<PreviewCursor />
<LinkCreatedBox />
+ {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
{DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
{LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx
index cdde32c21..196151e32 100644
--- a/src/client/views/RecommendationsBox.tsx
+++ b/src/client/views/RecommendationsBox.tsx
@@ -169,7 +169,7 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
<div style={{ marginRight: 50 }} onClick={() => DocumentManager.Instance.jumpToDocument(doc, false)}>
<FontAwesomeIcon className="documentdecorations-icon" icon={"bullseye"} size="sm" />
</div>
- <div style={{ marginRight: 50 }} onClick={() => DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "Recommender", undefined)}>
+ <div style={{ marginRight: 50 }} onClick={() => DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "Recommender", "", undefined)}>
<FontAwesomeIcon className="documentdecorations-icon" icon={"link"} size="sm" />
</div>
</div>
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
index 5ada79a28..b8b72e756 100644
--- a/src/client/views/collections/CollectionLinearView.scss
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -35,6 +35,18 @@
font-size: 12.5px;
}
+ .bottomPopup-descriptions {
+ display: inline;
+ white-space: nowrap;
+ padding-left: 8px;
+ padding-right: 8px;
+ vertical-align: middle;
+ background-color: lightgrey;
+ border-radius: 5.5px;
+ color: black;
+ margin-right: 5px;
+ }
+
.bottomPopup-exit {
display: inline;
white-space: nowrap;
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 7cbe5c19d..c370415be 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -15,6 +15,7 @@ import { documentSchema } from '../../../fields/documentSchemas';
import { Id } from '../../../fields/FieldSymbols';
import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { LinkDescriptionPopup } from '../nodes/LinkDescriptionPopup';
type LinearDocument = makeInterface<[typeof documentSchema,]>;
@@ -89,6 +90,21 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
DocumentLinksButton.StartLink = undefined;
}
+ @action
+ changeDescriptionSetting = () => {
+ if (LinkDescriptionPopup.showDescriptions) {
+ if (LinkDescriptionPopup.showDescriptions === "ON") {
+ LinkDescriptionPopup.showDescriptions = "OFF";
+ LinkDescriptionPopup.descriptionPopup = false;
+ } else {
+ LinkDescriptionPopup.showDescriptions = "ON";
+ }
+ } else {
+ LinkDescriptionPopup.showDescriptions = "OFF";
+ LinkDescriptionPopup.descriptionPopup = false;
+ }
+ }
+
render() {
const guid = Utils.GenerateGuid();
const flexDir: any = StrCast(this.Document.flexDirection);
@@ -155,6 +171,9 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
onPointerDown={e => e.stopPropagation()} >
<span className="bottomPopup-text" >
Creating link from: {DocumentLinksButton.StartLink.title} </span>
+ <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}
+ > Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
+ </span>
<span className="bottomPopup-exit" onClick={this.exitLongLinks}
>Exit</span>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index ed8535ecb..ce6872695 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -106,16 +106,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
[...this.props.docFilters(), ...Cast(this.props.Document._docFilters, listSpec("string"), [])];
}
@computed get childDocs() {
- const docFilters = this.docFilters();
- const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
- const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
- for (let i = 0; i < docFilters.length; i += 3) {
- const [key, value, modifiers] = docFilters.slice(i, i + 3);
- if (!filterFacets[key]) {
- filterFacets[key] = {};
- }
- filterFacets[key][value] = modifiers;
- }
let rawdocs: (Doc | Promise<Doc>)[] = [];
if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list;
@@ -128,38 +118,13 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const rootDoc = Cast(this.props.Document.rootDocument, Doc, null);
rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : [];
}
+
const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
+ const docFilters = this.docFilters();
const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
- const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+ const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
- const filteredDocs = docFilters.length && !this.props.dontRegisterView ? childDocs.filter(d => {
- for (const facetKey of Object.keys(filterFacets)) {
- const facet = filterFacets[facetKey];
- const satisfiesFacet = Object.keys(facet).some(value => {
- if (facet[value] === "match") {
- return d[facetKey] === undefined || Field.toString(d[facetKey] as Field).includes(value);
- }
- return (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value);
- });
- if (!satisfiesFacet) {
- return false;
- }
- }
- return true;
- }) : childDocs;
- const rangeFilteredDocs = filteredDocs.filter(d => {
- for (let i = 0; i < docRangeFilters.length; i += 3) {
- const key = docRangeFilters[i];
- const min = Number(docRangeFilters[i + 1]);
- const max = Number(docRangeFilters[i + 2]);
- const val = Cast(d[key], "number", null);
- if (val !== undefined && (val < min || val > max)) {
- return false;
- }
- }
- return true;
- });
- return rangeFilteredDocs;
+ return this.props.Document.dontRegisterView ? docs : DocUtils.FilterDocs(docs, docFilters, docRangeFilters, viewSpecScript);
}
@action
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 620b977fa..26c41f524 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -207,7 +207,7 @@ class TreeView extends React.Component<TreeViewProps> {
if (complete.linkDragData) {
const sourceDoc = complete.linkDragData.linkSourceDocument;
const destDoc = this.doc;
- DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link");
+ DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link", "");
e.stopPropagation();
}
const docDragData = complete.docDragData;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 0dfe9c52a..31f0c1df3 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -149,7 +149,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
_width: 15, _height: 15, _xPadding: 0, isLinkButton: true, displayTimecode: Cast(doc.displayTimecode, "number", null)
});
Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
- const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin");
+ const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", "");
const first = DocListCast(pushpin.links).find(d => d instanceof Doc);
first && (first.hidden = true);
pushpinLink && (Doc.GetProto(pushpinLink).isPushpin = true);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b81e400b3..9bf425db2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -246,7 +246,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
} else {
const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
this.props.addDocument(source);
- linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
+ linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation", ""); // TODODO this is where in text links get passed
e.stopPropagation();
return true;
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 97ed74c10..b47236bea 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -493,7 +493,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
summary._backgroundColor = "#e2ad32";
portal.layoutKey = "layout_portal";
portal.title = "document collection";
- DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing");
+ DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing", "");
this.props.addLiveTextDocument(summary);
MarqueeOptionsMenu.Instance.fadeOut(true);
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 776266ce6..cd25c21b4 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -10,7 +10,7 @@ import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView';
import { CollectionSubView } from '../CollectionSubView';
-import "./collectionMulticolumnView.scss";
+import "./CollectionMulticolumnView.scss";
import ResizeBar from './MulticolumnResizer';
import WidthLabel from './MulticolumnWidthLabel';
import { List } from '../../../../fields/List';
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 1703ff4dc..51dcdcfe6 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -7,7 +7,7 @@ import { Doc } from '../../../../fields/Doc';
import { NumCast, StrCast, BoolCast, ScriptCast } from '../../../../fields/Types';
import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView';
import { Utils, returnZero, returnFalse, returnOne } from '../../../../Utils';
-import "./collectionMultirowView.scss";
+import "./CollectionMultirowView.scss";
import { computed, trace, observable, action } from 'mobx';
import { Transform } from '../../../util/Transform';
import HeightLabel from './MultirowHeightLabel';
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index b47c8976e..406a38c26 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -7,34 +7,112 @@
user-select: none;
}
-.linkEditor-back {
+.linkEditor-button-back {
margin-bottom: 6px;
+ border-radius: 10px;
+ width: 18px;
+ height: 18px;
+ padding: 0;
}
.linkEditor-info {
- border-bottom: 0.5px solid $light-color-secondary;
- padding-bottom: 6px;
- margin-bottom: 6px;
+ //border-bottom: 0.5px solid $light-color-secondary;
+ padding-bottom: 4px;
+ padding-top: 5px;
+ padding-left: 5px;
+ //margin-bottom: 6px;
display: flex;
justify-content: space-between;
.linkEditor-linkedTo {
width: calc(100% - 26px);
+ padding-left: 5px;
+ padding-right: 5px
}
}
-.linkEditor-button, .linkEditor-addbutton {
+.linkEditor-description {
+ padding-left: 6.5px;
+ padding-right: 6.5px;
+ padding-bottom: 3.5px;
+
+ .linkEditor-description-text {
+ text-decoration-color: grey;
+ }
+
+ .linkEditor-description-input {
+ border: 1px solid grey;
+ border-radius: 4px;
+ background-color: rgb(236, 236, 236);
+ padding-left: 2px;
+ padding-right: 2px;
+ color: grey;
+ text-decoration-color: grey;
+ }
+}
+
+.linkEditor-followingDropdown {
+ padding-left: 6.5px;
+ padding-right: 6.5px;
+ padding-bottom: 3.5px;
+
+ .linkEditor-followingDropdown-dropdown {
+
+ .linkEditor-followingDropdown-header {
+
+ border: 1px solid grey;
+ border-radius: 4px;
+ background-color: rgb(236, 236, 236);
+ padding-left: 2px;
+ padding-right: 2px;
+ color: grey;
+ text-decoration-color: grey;
+
+ .linkEditor-followingDropdown-icon {
+ float: right;
+ }
+ }
+
+ .linkEditor-followingDropdown-optionsList {
+ padding-left: 3px;
+ padding-right: 3px;
+
+ .linkEditor-followingDropdown-option {
+ border: 0.25px dotted grey;
+ background-color: rgb(236, 236, 236);
+ padding-left: 2px;
+ padding-right: 2px;
+ color: grey;
+ text-decoration-color: grey;
+ font-size: 9px;
+
+ &:hover {
+ background-color: rgb(211, 210, 210);
+ }
+ }
+
+ }
+ }
+
+
+}
+
+
+.linkEditor-button,
+.linkEditor-addbutton {
width: 18px;
height: 18px;
padding: 0;
// font-size: 12px;
border-radius: 10px;
+
&:disabled {
background-color: gray;
}
}
-.linkEditor-addbutton{
+
+.linkEditor-addbutton {
margin-left: 0px;
}
@@ -44,7 +122,7 @@
}
.linkEditor-group {
- background-color: $light-color-secondary;
+ background-color: $light-color-secondary;
padding: 6px;
margin: 3px 0;
border-radius: 3px;
@@ -56,7 +134,7 @@
.linkEditor-group-row-label {
margin-right: 6px;
- display:inline-block;
+ display: inline-block;
}
.linkEditor-metadata-row {
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 13b9a2459..014d57ed0 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -1,14 +1,18 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faArrowLeft, faCog, faEllipsisV, faExchangeAlt, faPlus, faTable, faTimes, faTrash } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable } from "mobx";
+import { action, observable, computed } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../fields/Doc";
+import { Doc, Opt } from "../../../fields/Doc";
import { StrCast } from "../../../fields/Types";
import { Utils } from "../../../Utils";
import { LinkManager } from "../../util/LinkManager";
import './LinkEditor.scss';
import React = require("react");
+import { DocumentView } from "../nodes/DocumentView";
+import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
+import { EditableView } from "../EditableView";
+import { RefObject } from "react";
library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus);
@@ -281,27 +285,113 @@ interface LinkEditorProps {
@observer
export class LinkEditor extends React.Component<LinkEditorProps> {
+
+ @observable description = StrCast(LinkManager.currentLink?.description);
+ @observable openDropdown: boolean = false;
+
+ @observable followBehavior = this.props.linkDoc.follow ? this.props.linkDoc.follow : "Default";
+
+
+ //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION";
+
@action
deleteLink = (): void => {
LinkManager.Instance.deleteLink(this.props.linkDoc);
this.props.showLinks();
}
+ @action
+ setDescripValue = (value: string) => {
+ if (LinkManager.currentLink) {
+ LinkManager.currentLink.description = value;
+ return true;
+ }
+ }
+
+ @computed
+ get editDescription() {
+ return <div className="linkEditor-description">
+ <div className="linkEditor-description-label">
+ Link Description:</div>
+ <div className="linkEditor-description-input">
+ <EditableView
+ GetValue={() => StrCast(LinkManager.currentLink?.description)}
+ SetValue={value => { this.setDescripValue(value); return false; }}
+ contents={LinkManager.currentLink?.description}
+ placeholder={"(optional) enter link description"}
+ color={"rgb(88, 88, 88)"}
+ ></EditableView></div></div>;
+ }
+
+ @action
+ changeDropdown = () => {
+ this.openDropdown = !this.openDropdown;
+ }
+
+ @action
+ changeFollowBehavior = (follow: string) => {
+ this.openDropdown = false;
+ this.followBehavior = follow;
+ this.props.linkDoc.follow = follow;
+ }
+
+ @computed
+ get followingDropdown() {
+ return <div className="linkEditor-followingDropdown">
+ <div className="linkEditor-followingDropdown-label">
+ Follow Behavior:</div>
+ <div className="linkEditor-followingDropdown-dropdown">
+ <div className="linkEditor-followingDropdown-header"
+ onPointerDown={this.changeDropdown}>
+ {this.followBehavior}
+ <FontAwesomeIcon className="linkEditor-followingDropdown-icon"
+ icon={this.openDropdown ? "chevron-up" : "chevron-down"}
+ size={"lg"} onPointerDown={this.changeDropdown} />
+ </div>
+ <div className="linkEditor-followingDropdown-optionsList"
+ style={{ display: this.openDropdown ? "" : "none" }}>
+ <div className="linkEditor-followingDropdown-option"
+ onPointerDown={() => this.changeFollowBehavior("Default")}>
+ Default
+ </div>
+ <div className="linkEditor-followingDropdown-option"
+ onPointerDown={() => this.changeFollowBehavior("Always open in right tab")}>
+ Always open in right tab
+ </div>
+ <div className="linkEditor-followingDropdown-option"
+ onPointerDown={() => this.changeFollowBehavior("Always open in new tab")}>
+ Always open in new tab
+ </div>
+ </div>
+ </div>
+ </div>;
+ }
+
render() {
const destination = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
const groups = [this.props.linkDoc].map(groupDoc => {
- return <LinkGroupEditor key={"gred-" + StrCast(groupDoc.linkRelationship)} linkDoc={this.props.linkDoc} sourceDoc={this.props.sourceDoc} groupDoc={groupDoc} />;
+ return <LinkGroupEditor key={"gred-" + StrCast(groupDoc.linkRelationship)} linkDoc={this.props.linkDoc}
+ sourceDoc={this.props.sourceDoc} groupDoc={groupDoc} />;
});
return !destination ? (null) : (
<div className="linkEditor">
- {this.props.hideback ? (null) : <button className="linkEditor-back" onPointerDown={() => this.props.showLinks()}><FontAwesomeIcon icon="arrow-left" size="sm" /></button>}
<div className="linkEditor-info">
- <p className="linkEditor-linkedTo">editing link to: <b>{destination.proto?.title ?? destination.title ?? "untitled"}</b></p>
- <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link"><FontAwesomeIcon icon="trash" size="sm" /></button>
+ <button className="linkEditor-button-back"
+ style={{ display: this.props.hideback ? "none" : "" }}
+ onClick={this.props.showLinks}>
+ <FontAwesomeIcon icon="arrow-left" size="sm" /> </button>
+ <p className="linkEditor-linkedTo">editing link to: <b>{
+ destination.proto?.title ?? destination.title ?? "untitled"}</b></p>
+ <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link">
+ <FontAwesomeIcon icon="trash" size="sm" /></button>
</div>
- {groups.length > 0 ? groups : <div className="linkEditor-group">There are currently no relationships associated with this link.</div>}
+
+ <div>{this.editDescription}</div>
+ <div>{this.followingDropdown}</div>
+
+ {/* {groups.length > 0 ? groups : <div className="linkEditor-group">There are currently no relationships associated with this link.</div>} */}
</div>
);
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index 6468ccd3d..4b1a3f425 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -3,20 +3,32 @@
.linkMenu {
width: 100%;
height: auto;
+ //border: 1px solid black;
+
+ &:hover {
+ width: calc(auto + 26px);
+ }
}
.linkMenu-list {
+ border: 1px solid black;
max-height: 200px;
overflow-y: scroll;
position: absolute;
z-index: 10;
- background: $link-color;
- min-width: 150px
+ background: white;
+ min-width: 150px;
+ border-radius: 5px;
+ padding-top: 6.5px;
+ padding-bottom: 6.5px;
+ padding-left: 6.5px;
+ padding-right: 2px;
+ //width: calc(auto + 50px);
}
.linkMenu-group {
- border-bottom: 0.5px solid lightgray;
- padding: 5px 0;
+ //border-bottom: 0.5px solid lightgray;
+ //@extend: 5px 0;
&:last-child {
@@ -26,13 +38,15 @@
.linkMenu-group-name {
display: flex;
+
&:hover {
p {
background-color: lightgray;
+
}
p.expand-one {
- width: calc(100% - 26px);
+ width: calc(100% + 26px);
}
.linkEditor-tableButton {
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index c672511ac..8a7b12f48 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -11,6 +11,7 @@ import { faTrash } from '@fortawesome/free-solid-svg-icons';
import { library } from "@fortawesome/fontawesome-svg-core";
import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
import { LinkDocPreview } from "../nodes/LinkDocPreview";
+import { isUndefined } from "util";
library.add(faTrash);
@@ -25,19 +26,20 @@ interface Props {
export class LinkMenu extends React.Component<Props> {
@observable private _editingLink?: Doc;
- @observable private _linkMenuRef: Opt<HTMLDivElement | null>;
+ @observable private _linkMenuRef = React.createRef<HTMLDivElement>();
+ private _editorRef = React.createRef<HTMLDivElement>();
@action
onClick = (e: PointerEvent) => {
LinkDocPreview.LinkInfo = undefined;
- if (this._linkMenuRef?.contains(e.target as any)) {
- DocumentLinksButton.EditLink = undefined;
- }
- if (this._linkMenuRef && !this._linkMenuRef.contains(e.target as any)) {
- DocumentLinksButton.EditLink = undefined;
+ if (this._linkMenuRef && !!!this._linkMenuRef.current?.contains(e.target as any)) {
+ if (this._editorRef && !!!this._editorRef.current?.contains(e.target as any)) {
+ console.log("outside click");
+ DocumentLinksButton.EditLink = undefined;
+ }
}
}
@action
@@ -78,12 +80,14 @@ export class LinkMenu extends React.Component<Props> {
render() {
const sourceDoc = this.props.docView.props.Document;
const groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc);
- return <div className="linkMenu-list"
- ref={(r) => this._linkMenuRef = r} style={{ left: this.props.location[0], top: this.props.location[1] }}>
- {!this._editingLink ?
- this.renderAllGroups(groups) :
- <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)} />
- }
- </div>;
+ return <div className="linkMenu" ref={this._linkMenuRef} >
+ <div className="linkMenu-list"
+ style={{ left: this.props.location[0], top: this.props.location[1] }}>
+ {!this._editingLink ?
+ this.renderAllGroups(groups) :
+ <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink}
+ showLinks={action(() => this._editingLink = undefined)} />
+ }
+ </div> </div>;
}
} \ No newline at end of file
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 7892d381b..ec17776e3 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -26,6 +26,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
private _drag = React.createRef<HTMLDivElement>();
private _table = React.createRef<HTMLDivElement>();
+ private _menuRef = React.createRef<HTMLDivElement>();
onLinkButtonDown = (e: React.PointerEvent): void => {
e.stopPropagation();
@@ -74,12 +75,13 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
linkDoc={linkDoc}
sourceDoc={this.props.sourceDoc}
destinationDoc={destination}
- showEditor={this.props.showEditor} />;
+ showEditor={this.props.showEditor}
+ menuRef={this._menuRef} />;
}
});
return (
- <div className="linkMenu-group">
+ <div className="linkMenu-group" ref={this._menuRef}>
{/* <div className="linkMenu-group-name">
<p ref={this._drag} onPointerDown={this.onLinkButtonDown}
className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index e3ce69cd7..67bf71fb9 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -4,19 +4,39 @@
// border-top: 0.5px solid $main-accent;
position: relative;
display: flex;
- font-size: 12px;
.linkMenu-name {
position: relative;
- p {
- padding: 4px 6px;
- line-height: 12px;
- border-radius: 5px;
- overflow-wrap: break-word;
- user-select: none;
+ .linkMenu-text {
+
+ padding: 4px 2px;
+ //display: inline;
+
+ .linkMenu-destination-title {
+ text-decoration: none;
+ color: rgb(85, 120, 196);
+ font-size: 14px;
+ padding-bottom: 2px;
+ }
+
+ .linkMenu-description {
+ text-decoration: none;
+ font-style: italic;
+ color: rgb(95, 97, 102);
+ font-size: 10px;
+ }
+
+ p {
+ //padding: 4px 2px;
+ line-height: 12px;
+ border-radius: 5px;
+ overflow-wrap: break-word;
+ user-select: none;
+ }
}
+
}
.linkMenu-item-content {
@@ -32,25 +52,40 @@
}
&:hover {
+
+
.linkMenu-item-buttons {
display: flex;
}
.linkMenu-item-content {
+
+ .linkMenu-destination-title {
+ text-decoration: underline;
+ color: rgb(60, 90, 156);
+ //display: inline;
+ text-overflow: break;
+ }
+
&.expand-two p {
width: calc(100% - 52px);
- background-color: lightgray;
+ //text-decoration: underline;
+ //color: rgb(15, 57, 148);
+ //background-color: lightgray;
}
&.expand-three p {
width: calc(100% - 84px);
- background-color: lightgray;
+ //text-decoration: underline;
+ //color: rgb(15, 57, 148);
+ //background-color: lightgray;
}
}
}
}
.linkMenu-item-buttons {
+ //@extend: right;
position: absolute;
top: 50%;
right: 0;
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 04cd83ee0..6af474513 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -26,6 +26,7 @@ interface LinkMenuItemProps {
destinationDoc: Doc;
showEditor: (linkDoc: Doc) => void;
addDocTab: (document: Doc, where: string) => boolean;
+ menuRef: React.Ref<HTMLDivElement>;
}
// drag links and drop link targets (aliasing them if needed)
@@ -77,6 +78,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
@action toggleShowMore(e: React.PointerEvent) { e.stopPropagation(); this._showMore = !this._showMore; }
onEdit = (e: React.PointerEvent): void => {
+ LinkManager.currentLink = this.props.linkDoc;
setupMoveUpEvents(this, e, this.editMoved, emptyFunction, () => this.props.showEditor(this.props.linkDoc));
}
@@ -110,7 +112,8 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
document.removeEventListener("pointerup", this.onLinkButtonUp);
document.addEventListener("pointerup", this.onLinkButtonUp);
- if (this._buttonRef && this._buttonRef.current?.contains(e.target as any)) {
+ if (this._buttonRef && !!!this._buttonRef.current?.contains(e.target as any)) {
+ console.log("outside click");
LinkDocPreview.LinkInfo = undefined;
}
}
@@ -147,7 +150,18 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
console.log("FOLLOWWW");
DocumentLinksButton.EditLink = undefined;
LinkDocPreview.LinkInfo = undefined;
- DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
+
+ if (this.props.linkDoc.follow) {
+ if (this.props.linkDoc.follow === "Default") {
+ DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
+ } else if (this.props.linkDoc.follow === "Always open in right tab") {
+ this.props.addDocTab(this.props.destinationDoc, "onRight");
+ } else if (this.props.linkDoc.follow === "Always open in new tab") {
+ this.props.addDocTab(this.props.destinationDoc, "inTab");
+ }
+ } else {
+ DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
+ }
}
@action
@@ -174,18 +188,24 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
Location: [e.clientX, e.clientY + 20]
}))}
onPointerDown={this.onLinkButtonDown}>
- <p >{StrCast(this.props.destinationDoc.title)}</p>
+
+ <div className="linkMenu-text">
+ <p className="linkMenu-destination-title"
+ onPointerDown={this.followDefault}>
+ {StrCast(this.props.destinationDoc.title)}</p>
+ {this.props.linkDoc.description !== "" ? <p className="linkMenu-description">
+ {StrCast(this.props.linkDoc.description)}</p> : null} </div>
+
<div className="linkMenu-item-buttons" ref={this._buttonRef} >
{canExpand ? <div title="Show more" className="button" onPointerDown={e => this.toggleShowMore(e)}>
<FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
- {/* <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}>
- <FontAwesomeIcon className="fa-icon" icon="pencil-alt" size="sm" /></div> */}
+ <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}>
+ <FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
<div title="Delete link" className="button" onPointerDown={this.deleteLink}>
<FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
- <div title="Follow link" className="button" onPointerDown={this.followDefault} onContextMenu={this.onContextMenu}>
- <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
- </div>
+ {/* <div title="Follow link" className="button" onPointerDown={this.followDefault} onContextMenu={this.onContextMenu}>
+ <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div> */}
</div>
</div>
{this._showMore ? this.renderMetadata() : <></>}
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index c959b79f5..e9420a072 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -5,6 +5,7 @@
position: inherit;
display: flex;
pointer-events: all;
+ position: relative;
cursor: default;
.audiobox-buttons {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index d5288fff6..5c921cea4 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -268,6 +268,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
LayoutTemplate={undefined}
LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(l, la2)}`)}
ContainingCollectionDoc={this.props.Document}
+ dontRegisterView={true}
parentActive={returnTrue}
bringToFront={emptyFunction}
backgroundColor={returnTransparent} />
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index bfd860f65..7fb447cab 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -11,6 +11,8 @@ import { DocUtils } from "../../documents/Documents";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { LinkDocPreview } from "./LinkDocPreview";
import { LinkCreatedBox } from "./LinkCreatedBox";
+import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
+import { LinkManager } from "../../util/LinkManager";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -54,6 +56,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
return false;
}
+
onLinkButtonDown = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
if (doubleTap && this.props.InMenu) {
@@ -87,15 +90,22 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
// Doc.UnBrushDoc(this.props.View.Document);
// });
} else {
- DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
- DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
-
- runInAction(() => {
- LinkCreatedBox.popupX = e.screenX;
- LinkCreatedBox.popupY = e.screenY - 120;
- LinkCreatedBox.linkCreated = true;
- setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
- });
+
+ if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
+ const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+ LinkManager.currentLink = linkDoc;
+ runInAction(() => {
+ LinkCreatedBox.popupX = e.screenX;
+ LinkCreatedBox.popupY = e.screenY - 133;
+ LinkCreatedBox.linkCreated = true;
+
+ LinkDescriptionPopup.popupX = e.screenX;
+ LinkDescriptionPopup.popupY = e.screenY - 100;
+ LinkDescriptionPopup.descriptionPopup = true;
+
+ setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+ });
+ }
}
}
}));
@@ -109,15 +119,23 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
// Doc.UnBrushDoc(this.props.View.Document);
// });
} else {
- DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
- DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
-
- runInAction(() => {
- LinkCreatedBox.popupX = e.screenX;
- LinkCreatedBox.popupY = e.screenY - 120;
- LinkCreatedBox.linkCreated = true;
- setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
- });
+ if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
+ const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+ LinkManager.currentLink = linkDoc;
+ runInAction(() => {
+ LinkCreatedBox.popupX = e.screenX;
+ LinkCreatedBox.popupY = e.screenY - 133;
+ LinkCreatedBox.linkCreated = true;
+
+ if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
+ LinkDescriptionPopup.popupX = e.screenX;
+ LinkDescriptionPopup.popupY = e.screenY - 100;
+ LinkDescriptionPopup.descriptionPopup = true;
+ }
+
+ setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+ });
+ }
}
}
@@ -128,10 +146,14 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
@computed
get linkButton() {
const links = DocListCast(this.props.View.props.Document.links);
+
+ const title = this.props.InMenu ? "Drag or tap to create links" : "Tap to view links";
+
return (!links.length || links[0].hidden) && !this.props.AlwaysOn ? (null) :
- <div title="Drag(create link) Tap(view links)" ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}>
+ <div title={title} ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}>
<div className={"documentLinksButton"} style={{
- backgroundColor: DocumentLinksButton.StartLink ? "transparent" : "",
+ backgroundColor: DocumentLinksButton.StartLink ? "transparent" : this.props.InMenu ? "black" : "",
+ color: this.props.InMenu ? "white" : "black",
width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px", fontWeight: "bold"
}}
onPointerDown={this.onLinkButtonDown} onClick={this.onLinkClick}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e277ddc36..1d76633be 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -43,6 +43,8 @@ import React = require("react");
import { DocumentLinksButton } from './DocumentLinksButton';
import { MobileInterface } from '../../../mobile/MobileInterface';
import { LinkCreatedBox } from './LinkCreatedBox';
+import { LinkDescriptionPopup } from './LinkDescriptionPopup';
+import { LinkManager } from '../../util/LinkManager';
library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -642,30 +644,46 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
e.stopPropagation();
de.complete.annoDragData.linkedToDoc = true;
+ const linkDoc = DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
+ LinkManager.currentLink = linkDoc;
+
runInAction(() => {
LinkCreatedBox.popupX = de.x;
- LinkCreatedBox.popupY = de.y;
+ LinkCreatedBox.popupY = de.y - 33;
LinkCreatedBox.linkCreated = true;
+
+ LinkDescriptionPopup.popupX = de.x;
+ LinkDescriptionPopup.popupY = de.y;
+ LinkDescriptionPopup.descriptionPopup = true;
+
setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
});
-
- DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
}
if (de.complete.linkDragData) {
e.stopPropagation();
// const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
// const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
- runInAction(() => {
- LinkCreatedBox.popupX = de.x;
- LinkCreatedBox.popupY = de.y;
- LinkCreatedBox.linkCreated = true;
- setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
- });
+ if (de.complete.linkDragData.linkSourceDocument !== this.props.Document) {
+ const linkDoc = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
+ { doc: this.props.Document }, `link`);
+ LinkManager.currentLink = linkDoc;
+
+ de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
+ (de.complete.linkDragData.linkDocument = linkDoc); // TODODO this is where in text links get passed
+ runInAction(() => {
+ LinkCreatedBox.popupX = de.x;
+ LinkCreatedBox.popupY = de.y - 33;
+ LinkCreatedBox.linkCreated = true;
+
+ LinkDescriptionPopup.popupX = de.x;
+ LinkDescriptionPopup.popupY = de.y;
+ LinkDescriptionPopup.descriptionPopup = true;
+
+ setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+ });
+ }
- de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
- (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
- { doc: this.props.Document }, `link`)); // TODODO this is where in text links get passed
}
}
@@ -1094,7 +1112,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
return (this.props.treeViewDoc && this.props.LayoutTemplateString) || // render nothing for: tree view anchor dots
this.layoutDoc.presBox || // presentationbox nodes
this.props.dontRegisterView ? (null) : // view that are not registered
- DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
+ DocUtils.FilterDocs(DocListCast(this.Document.links), this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
<DocumentView {...this.props} key={i + 1}
Document={d}
ContainingCollectionView={this.props.ContainingCollectionView}
diff --git a/src/client/views/nodes/LinkCreatedBox.tsx b/src/client/views/nodes/LinkCreatedBox.tsx
index d157d3fca..648ae23c8 100644
--- a/src/client/views/nodes/LinkCreatedBox.tsx
+++ b/src/client/views/nodes/LinkCreatedBox.tsx
@@ -11,8 +11,8 @@ import { Fade } from "@material-ui/core";
export class LinkCreatedBox extends React.Component<{}> {
@observable public static linkCreated: boolean = false;
- @observable public static popupX: number = 600;
- @observable public static popupY: number = 250;
+ @observable public static popupX: number = 500;
+ @observable public static popupY: number = 150;
@action
public static changeLinkCreated = () => {
@@ -23,8 +23,8 @@ export class LinkCreatedBox extends React.Component<{}> {
return <Fade in={LinkCreatedBox.linkCreated}>
<div className="linkCreatedBox-fade"
style={{
- left: LinkCreatedBox.popupX ? LinkCreatedBox.popupX : 600,
- top: LinkCreatedBox.popupY ? LinkCreatedBox.popupY : 250,
+ left: LinkCreatedBox.popupX ? LinkCreatedBox.popupX : 500,
+ top: LinkCreatedBox.popupY ? LinkCreatedBox.popupY : 150,
}}>Link Created</div>
</Fade>;
}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.scss b/src/client/views/nodes/LinkDescriptionPopup.scss
new file mode 100644
index 000000000..54002fd1b
--- /dev/null
+++ b/src/client/views/nodes/LinkDescriptionPopup.scss
@@ -0,0 +1,69 @@
+.linkDescriptionPopup {
+
+ display: flex;
+
+ border: 1px solid rgb(170, 26, 26);
+
+ width: auto;
+ position: absolute;
+
+ height: auto;
+ z-index: 10000;
+ border-radius: 10px;
+ font-size: 12px;
+ //white-space: nowrap;
+
+ background-color: rgba(250, 250, 250, 0.95);
+ padding-top: 9px;
+ padding-bottom: 9px;
+ padding-left: 9px;
+ padding-right: 9px;
+
+ .linkDescriptionPopup-input {
+ float: left;
+ background-color: rgba(250, 250, 250, 0.95);
+ color: rgb(100, 100, 100);
+ border: none;
+ min-width: 160px;
+ }
+
+ .linkDescriptionPopup-btn {
+
+ float: right;
+
+ justify-content: center;
+ vertical-align: middle;
+
+
+ .linkDescriptionPopup-btn-dismiss {
+ background-color: white;
+ color: black;
+ display: inline;
+ right: 0;
+ border-radius: 10px;
+ border: 1px solid black;
+ padding: 3px;
+ font-size: 9px;
+ text-align: center;
+ position: relative;
+ margin-right: 4px;
+ justify-content: center;
+ }
+
+ .linkDescriptionPopup-btn-add {
+ background-color: black;
+ color: white;
+ display: inline;
+ right: 0;
+ border-radius: 10px;
+ border: 1px solid black;
+ padding: 3px;
+ font-size: 9px;
+ text-align: center;
+ position: relative;
+ justify-content: center;
+ }
+ }
+
+
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
new file mode 100644
index 000000000..3bb52d9fb
--- /dev/null
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -0,0 +1,73 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import "./LinkDescriptionPopup.scss";
+import { observable, action } from "mobx";
+import { EditableView } from "../EditableView";
+import { LinkManager } from "../../util/LinkManager";
+import { LinkCreatedBox } from "./LinkCreatedBox";
+
+
+@observer
+export class LinkDescriptionPopup extends React.Component<{}> {
+
+ @observable public static descriptionPopup: boolean = false;
+ @observable public static showDescriptions: string = "ON";
+ @observable public static popupX: number = 700;
+ @observable public static popupY: number = 350;
+ @observable description: string = "";
+ @observable popupRef = React.createRef<HTMLDivElement>();
+
+ @action
+ descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.description = e.currentTarget.value;
+ }
+
+ @action
+ setDescription = () => {
+ if (LinkManager.currentLink) {
+ LinkManager.currentLink.description = this.description;
+ }
+ LinkDescriptionPopup.descriptionPopup = false;
+ }
+
+ @action
+ onDismiss = () => {
+ LinkDescriptionPopup.descriptionPopup = false;
+ }
+
+ @action
+ onClick = (e: PointerEvent) => {
+ if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
+ LinkDescriptionPopup.descriptionPopup = false;
+ LinkCreatedBox.linkCreated = false;
+ }
+ }
+
+ @action
+ componentDidMount() {
+ document.addEventListener("pointerdown", this.onClick);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener("pointerdown", this.onClick);
+ }
+
+ render() {
+ return <div className="linkDescriptionPopup" ref={this.popupRef}
+ style={{
+ left: LinkDescriptionPopup.popupX ? LinkDescriptionPopup.popupX : 700,
+ top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350,
+ }}>
+ <input className="linkDescriptionPopup-input"
+ placeholder={"(optional) enter link label..."}
+ onChange={(e) => this.descriptionChanged(e)}>
+ </input>
+ <div className="linkDescriptionPopup-btn">
+ <div className="linkDescriptionPopup-btn-dismiss"
+ onPointerDown={this.onDismiss}> Dismiss </div>
+ <div className="linkDescriptionPopup-btn-add"
+ onPointerDown={this.setDescription}> Add </div>
+ </div>
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 9075a6486..f10c425d4 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -219,7 +219,7 @@ export default class RichTextMenu extends AntimodeMenu {
getActiveAlignment() {
if (this.view) {
const path = (this.view.state.selection.$from as any).path;
- for (let i = path.length - 3; i < path.length; i -= 3) {
+ for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
if (path[i]?.type === this.view.state.schema.nodes.paragraph) {
return path[i].attrs.align || "left";
}
diff --git a/src/server/ApiManagers/HypothesisManager.ts b/src/server/ApiManagers/HypothesisManager.ts
new file mode 100644
index 000000000..33badbc42
--- /dev/null
+++ b/src/server/ApiManagers/HypothesisManager.ts
@@ -0,0 +1,44 @@
+import ApiManager, { Registration } from "./ApiManager";
+import { Method, _permission_denied } from "../RouteManager";
+import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils";
+import { Database } from "../database";
+import { writeFile, readFile, readFileSync, existsSync } from "fs";
+import { serverPathToFile, Directory } from "./UploadManager";
+
+export default class HypothesisManager extends ApiManager {
+
+ protected initialize(register: Registration): void {
+
+ register({
+ method: Method.GET,
+ subscription: "/readHypothesisAccessToken",
+ secureHandler: async ({ user, res }) => {
+ if (existsSync(serverPathToFile(Directory.hypothesis, user.id))) {
+ const read = readFileSync(serverPathToFile(Directory.hypothesis, user.id), "base64") || "";
+ console.log("READ = " + read);
+ res.send(read);
+ } else res.send("");
+ }
+ });
+
+ register({
+ method: Method.POST,
+ subscription: "/writeHypothesisAccessToken",
+ secureHandler: async ({ user, req, res }) => {
+ const write = req.body.authenticationCode;
+ console.log("WRITE = " + write);
+ res.send(await writeFile(serverPathToFile(Directory.hypothesis, user.id), write, "base64", () => { }));
+ }
+ });
+
+ register({
+ method: Method.GET,
+ subscription: "/revokeHypothesisAccessToken",
+ secureHandler: async ({ user, res }) => {
+ await Database.Auxiliary.GoogleAccessToken.Revoke("dash-hyp-" + user.id);
+ res.send();
+ }
+ });
+
+ }
+} \ No newline at end of file
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index fe39b84e6..55ceab9fb 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -24,7 +24,8 @@ export enum Directory {
pdfs = "pdfs",
text = "text",
pdf_thumbnails = "pdf_thumbnails",
- audio = "audio"
+ audio = "audio",
+ hypothesis = "hypothesis"
}
export function serverPathToFile(directory: Directory, filename: string) {
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index 20f96f432..b0157a85f 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -39,7 +39,8 @@ export namespace GoogleApiServerUtils {
*/
export enum Service {
Documents = "Documents",
- Slides = "Slides"
+ Slides = "Slides",
+ Hypothesis = "Hypothesis"
}
/**
diff --git a/src/server/index.ts b/src/server/index.ts
index 083173bb5..9af4b00bc 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -18,10 +18,10 @@ import PDFManager from "./ApiManagers/PDFManager";
import UploadManager from "./ApiManagers/UploadManager";
import { log_execution } from "./ActionUtilities";
import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager";
+import HypothesisManager from "./ApiManagers/HypothesisManager";
import GooglePhotosManager from "./ApiManagers/GooglePhotosManager";
import { Logger } from "./ProcessFactory";
import { yellow } from "colors";
-import { DashSessionAgent } from "./DashSession/DashSessionAgent";
import SessionManager from "./ApiManagers/SessionManager";
import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent";
@@ -72,6 +72,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
new DeleteManager(),
new UtilManager(),
new GeneralGoogleManager(),
+ new HypothesisManager(),
new GooglePhotosManager(),
];