aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSam Wilkins <samwilkins333@gmail.com>2019-05-09 22:14:30 -0400
committerSam Wilkins <samwilkins333@gmail.com>2019-05-09 22:14:30 -0400
commitea1bffb43f0533cde4cf8a3f34da8b0e25ef6268 (patch)
tree5016ac7d3bbb37e62a809d738ddc45c3797bab18 /src
parent11c6ecdebdfb84246169f1e573c0e47e5336eff6 (diff)
parent310d4b0f4bb56adaa71e4b42d09e483e4face0f7 (diff)
merged with master and fixed linter issues
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts1
-rw-r--r--src/client/util/DocumentManager.ts31
-rw-r--r--src/client/util/TooltipTextMenu.tsx32
-rw-r--r--src/client/views/Main.scss3
-rw-r--r--src/client/views/Main.tsx6
-rw-r--r--src/client/views/PresentationView.tsx9
-rw-r--r--src/client/views/SearchBox.scss99
-rw-r--r--src/client/views/SearchBox.tsx133
-rw-r--r--src/client/views/SearchItem.tsx52
-rw-r--r--src/client/views/Templates.tsx15
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx4
-rw-r--r--src/client/views/nodes/DocumentView.tsx2
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx5
-rw-r--r--src/client/views/nodes/LinkBox.tsx23
-rw-r--r--src/debug/Test.tsx6
-rw-r--r--src/new_fields/util.ts1
-rw-r--r--src/server/Search.ts45
-rw-r--r--src/server/index.ts85
18 files changed, 484 insertions, 68 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 63ba01b6a..11929455c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -174,6 +174,7 @@ export namespace Docs {
if (!("creationDate" in protoProps)) {
protoProps.creationDate = new DateField;
}
+ protoProps.isPrototype = true;
return SetDelegateOptions(SetInstanceOptions(proto, protoProps, data), delegateProps);
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 779b07ce5..a8b643d4d 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,9 +1,10 @@
import { computed, observable } from 'mobx';
import { DocumentView } from '../views/nodes/DocumentView';
import { Doc } from '../../new_fields/Doc';
-import { FieldValue, Cast, BoolCast } from '../../new_fields/Types';
+import { FieldValue, Cast, NumCast, BoolCast } from '../../new_fields/Types';
import { listSpec } from '../../new_fields/Schema';
-import { SelectionManager } from './SelectionManager';
+import { undoBatch } from './UndoManager';
+import { CollectionDockingView } from '../views/collections/CollectionDockingView';
export class DocumentManager {
@@ -101,4 +102,30 @@ export class DocumentManager {
return pairs;
}, [] as { a: DocumentView, b: DocumentView, l: Doc }[]);
}
+
+ @undoBatch
+ public jumpToDocument = async (doc: Doc): Promise<void> => {
+ let docView = DocumentManager.Instance.getDocumentView(doc);
+ if (docView) {
+ docView.props.focus(docView.props.Document);
+ } else {
+ const contextDoc = await Cast(doc.annotationOn, Doc);
+ if (!contextDoc) {
+ CollectionDockingView.Instance.AddRightSplit(Doc.MakeDelegate(doc));
+ } else {
+ const page = NumCast(doc.page, undefined);
+ const curPage = NumCast(contextDoc.curPage, undefined);
+ if (page !== curPage) {
+ contextDoc.curPage = page;
+ }
+ let contextView = DocumentManager.Instance.getDocumentView(contextDoc);
+ if (contextView) {
+ contextDoc.panTransformType = "Ease";
+ contextView.props.focus(contextDoc);
+ } else {
+ CollectionDockingView.Instance.AddRightSplit(contextDoc);
+ }
+ }
+ }
+ }
} \ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 358fdc81b..b860f6758 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -49,15 +49,14 @@ export class TooltipTextMenu {
private fontSizeIndicator: HTMLSpanElement = document.createElement("span");
private link: HTMLAnchorElement;
+ private linkEditor?: HTMLDivElement;
+ private linkText?: HTMLDivElement;
+ private linkDrag?: HTMLImageElement;
//dropdown doms
private fontSizeDom?: Node;
private fontStyleDom?: Node;
private listTypeBtnDom?: Node;
- private linkEditor?: HTMLDivElement;
- private linkText?: HTMLDivElement;
- private linkDrag?: HTMLImageElement;
-
constructor(view: EditorView, editorProps: FieldViewProps) {
this.view = view;
this.state = view.state;
@@ -186,7 +185,7 @@ export class TooltipTextMenu {
this.linkText.style.width = "150px";
this.linkText.style.overflow = "hidden";
this.linkText.style.color = "white";
- this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); }
+ this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); };
let linkBtn = document.createElement("div");
linkBtn.textContent = ">>";
linkBtn.style.width = "20px";
@@ -203,8 +202,9 @@ export class TooltipTextMenu {
let docid = href.replace(DocServer.prepend("/doc/"), "");
DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
if (f instanceof Doc) {
- if (DocumentManager.Instance.getDocumentView(f))
+ if (DocumentManager.Instance.getDocumentView(f)) {
DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
+ }
else CollectionDockingView.Instance.AddRightSplit(f);
}
}));
@@ -212,7 +212,7 @@ export class TooltipTextMenu {
e.stopPropagation();
e.preventDefault();
}
- }
+ };
this.linkDrag = document.createElement("img");
this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
this.linkDrag.style.width = "20px";
@@ -232,7 +232,7 @@ export class TooltipTextMenu {
}),
},
hideSource: false
- })
+ });
};
this.linkEditor.appendChild(this.linkDrag);
this.linkEditor.appendChild(this.linkText);
@@ -250,7 +250,7 @@ export class TooltipTextMenu {
e.stopPropagation();
e.preventDefault();
}
- }
+ };
this.tooltip.appendChild(this.linkEditor);
}
@@ -351,7 +351,7 @@ export class TooltipTextMenu {
icon: icons.link,
css: "color:white;",
class: "menuicon",
- enable(state) { return !state.selection.empty },
+ enable(state) { return !state.selection.empty; },
run: (state, dispatch, view) => {
// to remove link
if (this.markActive(state, markType)) {
@@ -396,10 +396,10 @@ export class TooltipTextMenu {
}
markActive = function (state: EditorState<any>, type: MarkType<Schema<string, string>>) {
- let { from, $from, to, empty } = state.selection
- if (empty) return type.isInSet(state.storedMarks || $from.marks())
- else return state.doc.rangeHasMark(from, to, type)
- }
+ let { from, $from, to, empty } = state.selection;
+ if (empty) return type.isInSet(state.storedMarks || $from.marks());
+ else return state.doc.rangeHasMark(from, to, type);
+ };
// Helper function to create menu icons
icon(text: string, name: string) {
@@ -480,8 +480,8 @@ export class TooltipTextMenu {
let attributes = this.getMarksInSelection(this.view.state, [schema.marks.link])[0].attrs;
this.link.href = attributes.href;
this.link.textContent = attributes.title;
- this.link.style.visibility = "visible"
- } else this.link.style.visibility = "hidden"
+ this.link.style.visibility = "visible";
+ } else this.link.style.visibility = "hidden";
// Otherwise, reposition it and update its content
this.tooltip.style.display = "";
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 5c5c252e9..2430e8f6c 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -47,7 +47,7 @@ h1 {
}
.jsx-parser {
- width:100%;
+ width: 100%;
pointer-events: none;
border-radius: inherit;
}
@@ -184,6 +184,7 @@ button:hover {
overflow: scroll;
z-index: 1;
}
+
#mainContent-div {
width: 100%;
height: 100%;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 617580332..c9d5c395c 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -31,6 +31,7 @@ import "./Main.scss";
import { MainOverlayTextBox } from './MainOverlayTextBox';
import { DocumentView } from './nodes/DocumentView';
import { PreviewCursor } from './PreviewCursor';
+import { SearchBox } from './SearchBox';
import { SelectionManager } from '../util/SelectionManager';
import { FieldResult, Field, Doc, Opt } from '../../new_fields/Doc';
import { Cast, FieldValue, StrCast } from '../../new_fields/Types';
@@ -38,7 +39,6 @@ import { DocServer } from '../DocServer';
import { listSpec } from '../../new_fields/Schema';
import { Id } from '../../new_fields/RefField';
-
@observer
export class Main extends React.Component {
public static Instance: Main;
@@ -266,9 +266,11 @@ export class Main extends React.Component {
<button className="toolbar-button round-button" title="Redo" onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button>
<button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /></button>
</div >,
- <div className="main-buttonDiv" key="logout" style={{ top: '34px', right: '1px', position: 'absolute' }} ref={logoutRef}>
+ <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <SearchBox /> </div>,
+ <div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
<button onClick={() => request.get(DocServer.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
];
+
}
render() {
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index 94867b813..2a456324c 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -1,7 +1,7 @@
import { observer } from "mobx-react";
-import React = require("react")
+import React = require("react");
import { observable, action, runInAction, reaction } from "mobx";
-import "./PresentationView.scss"
+import "./PresentationView.scss";
import "./Main.tsx";
import { DocumentManager } from "../util/DocumentManager";
import { Utils } from "../../Utils";
@@ -141,7 +141,7 @@ export class PresentationView extends React.Component<PresViewProps> {
if (activeW && activeW instanceof Doc) {
PromiseValue(Cast(activeW.presentationView, Doc)).
then(pv => runInAction(() =>
- self.Document = pv ? pv : (activeW.presentationView = new Doc())))
+ self.Document = pv ? pv : (activeW.presentationView = new Doc())));
}
},
{ fireImmediately: true });
@@ -165,8 +165,9 @@ export class PresentationView extends React.Component<PresViewProps> {
}
render() {
- if (!this.Document)
+ if (!this.Document) {
return (null);
+ }
let titleStr = this.Document.Title;
let width = NumCast(this.Document.width);
diff --git a/src/client/views/SearchBox.scss b/src/client/views/SearchBox.scss
new file mode 100644
index 000000000..792d6dd3c
--- /dev/null
+++ b/src/client/views/SearchBox.scss
@@ -0,0 +1,99 @@
+@import "globalCssVariables";
+
+.searchBox {
+ height: 32px;
+ //display: flex;
+ //padding: 4px;
+ -webkit-transition: width 0.4s ease-in-out;
+ transition: width 0.4s ease-in-out;
+ align-items: center;
+
+
+
+ input[type=text] {
+ width: 130px;
+ -webkit-transition: width 0.4s;
+ transition: width 0.4s;
+ position: absolute;
+ right: 100px;
+ }
+
+ input[type=text]:focus {
+ width: 500px;
+ outline: 3px solid lightblue;
+ }
+
+ .filter-button {
+ position: absolute;
+ right: 30px;
+ }
+
+ .submit-search {
+ text-align: right;
+ color: $dark-color;
+ -webkit-transition: right 0.4s;
+ transition: right 0.4s;
+ }
+
+ .submit-search:hover {
+ color: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+ }
+}
+
+.filter-form {
+ background: $dark-color;
+ height: 400px;
+ width: 400px;
+ position: relative;
+ right: 1px;
+ color: $light-color;
+ padding: 10px;
+ flex-direction: column;
+}
+
+#header {
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 100%;
+ height: 40px;
+}
+
+#option {
+ height: 20px;
+}
+
+.results {
+ top: 300px;
+ display: flex;
+ flex-direction: column;
+
+ .search-item {
+ width: 500px;
+ height: 50px;
+ background: $light-color-secondary;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ transition: all 0.1s;
+ border-width: 0.11px;
+ border-style: none;
+ border-color: $intermediate-color;
+ border-bottom-style: solid;
+ padding: 10px;
+ white-space: nowrap;
+ font-size: 13px;
+ }
+
+ .search-item:hover {
+ transition: all 0.1s;
+ background: $lighter-alt-accent;
+ }
+
+ .search-title {
+ text-transform: uppercase;
+ text-align: left;
+ width: 8vw;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
new file mode 100644
index 000000000..7dd1af4e7
--- /dev/null
+++ b/src/client/views/SearchBox.tsx
@@ -0,0 +1,133 @@
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { observable, action, runInAction } from 'mobx';
+import { Utils } from '../../Utils';
+import { MessageStore } from '../../server/Message';
+import "./SearchBox.scss";
+import { faSearch } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { library } from '@fortawesome/fontawesome-svg-core';
+// const app = express();
+// import * as express from 'express';
+import { Search } from '../../server/Search';
+import * as rp from 'request-promise';
+import { SearchItem } from './SearchItem';
+import { isString } from 'util';
+import { constant } from 'async';
+import { DocServer } from '../DocServer';
+import { Doc } from '../../new_fields/Doc';
+import { Id } from '../../new_fields/RefField';
+import { DocumentManager } from '../util/DocumentManager';
+
+
+library.add(faSearch);
+
+@observer
+export class SearchBox extends React.Component {
+ @observable
+ searchString: string = "";
+
+ @observable private _open: boolean = false;
+ @observable private _resultsOpen: boolean = false;
+
+ @observable
+ private _results: Doc[] = [];
+
+ @action.bound
+ onChange(e: React.ChangeEvent<HTMLInputElement>) {
+ this.searchString = e.target.value;
+ }
+
+ @action
+ submitSearch = async () => {
+ runInAction(() => this._results = []);
+ let query = this.searchString;
+
+ let response = await rp.get('http://localhost:1050/search', {
+ qs: {
+ query
+ }
+ });
+ let results = JSON.parse(response);
+
+ //gets json result into a list of documents that can be used
+ this.getResults(results);
+
+ runInAction(() => { this._resultsOpen = true; });
+ }
+
+ @action
+ getResults = async (res: string[]) => {
+ res.map(async result => {
+ const doc = await DocServer.GetRefField(result);
+ if (doc instanceof Doc) {
+ runInAction(() => this._results.push(doc));
+ }
+ });
+ }
+
+ @action
+ handleClickFilter = (e: Event): void => {
+ var className = (e.target as any).className;
+ var id = (e.target as any).id;
+ if (className !== "filter-button" && className !== "filter-form") {
+ this._open = false;
+ }
+
+ }
+
+ @action
+ handleClickResults = (e: Event): void => {
+ var className = (e.target as any).className;
+ var id = (e.target as any).id;
+ if (id !== "result") {
+ this._resultsOpen = false;
+ }
+
+ }
+
+ componentWillMount() {
+ document.addEventListener('mousedown', this.handleClickFilter, false);
+ document.addEventListener('mousedown', this.handleClickResults, false);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('mousedown', this.handleClickFilter, false);
+ document.removeEventListener('mousedown', this.handleClickResults, false);
+ }
+
+ @action
+ toggleFilterDisplay = () => {
+ this._open = !this._open;
+ }
+
+ enter = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ if (e.key === "Enter") {
+ this.submitSearch();
+ }
+ }
+
+ render() {
+ return (
+ <div id="outer">
+ <div className="searchBox" id="outer">
+
+ <input value={this.searchString} onChange={this.onChange} type="text" placeholder="Search.." className="search" id="input" onKeyPress={this.enter} />
+ <button className="filter-button" onClick={this.toggleFilterDisplay}> Filter </button>
+ <div className="submit-search" id="submit" onClick={this.submitSearch}><FontAwesomeIcon style={{ height: "100%" }} icon="search" size="lg" /></div>
+ <div className="results" id="results" style={this._resultsOpen ? { display: "flex" } : { display: "none" }}>
+ {this._results.map(result => <SearchItem doc={result} key={result[Id]} />)}
+ </div>
+ </div>
+ <div className="filter-form" id="filter" style={this._open ? { display: "flex" } : { display: "none" }}>
+ <div className="filter-form" id="header">Filter Search Results</div>
+ <div className="filter-form" id="option">
+ filter by collection, key, type of node
+ </div>
+
+ </div>
+ </div>
+
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx
new file mode 100644
index 000000000..d30579907
--- /dev/null
+++ b/src/client/views/SearchItem.tsx
@@ -0,0 +1,52 @@
+import React = require("react");
+import { Doc } from "../../new_fields/Doc";
+import { DocumentManager } from "../util/DocumentManager";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Cast } from "../../new_fields/Types";
+import { FieldView, FieldViewProps } from './nodes/FieldView';
+import { computed } from "mobx";
+import { IconField } from "../../new_fields/IconField";
+
+
+export interface SearchProps {
+ doc: Doc;
+}
+
+library.add(faCaretUp);
+library.add(faObjectGroup);
+library.add(faStickyNote);
+library.add(faFilePdf);
+library.add(faFilm);
+
+export class SearchItem extends React.Component<SearchProps> {
+
+ onClick = () => {
+ DocumentManager.Instance.jumpToDocument(this.props.doc);
+ }
+
+ //needs help
+ // @computed get layout(): string { const field = Cast(this.props.doc[fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; }
+
+
+ public static DocumentIcon(layout: string) {
+ let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
+ layout.indexOf("ImageBox") !== -1 ? faImage :
+ layout.indexOf("Formatted") !== -1 ? faStickyNote :
+ layout.indexOf("Video") !== -1 ? faFilm :
+ layout.indexOf("Collection") !== -1 ? faObjectGroup :
+ faCaretUp;
+ return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
+ }
+
+ render() {
+ return (
+ <div className="search-item" id="result" onClick={this.onClick}>
+ <div className="search-title" id="result" >title: {this.props.doc.title}</div>
+ {/* <div className="search-type" id="result" >Type: {this.props.doc.layout}</div> */}
+ {/* <div className="search-type" >{SearchItem.DocumentIcon(this.layout)}</div> */}
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 02f9aa510..bbedc95f1 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -38,18 +38,9 @@ export class Template {
export namespace Templates {
// export const BasicLayout = new Template("Basic layout", "{layout}");
- export const OuterCaption = new Template("Outer caption", TemplatePosition.OutterBottom,
- `<div id="screenSpace" style="margin-top: 100%; font-size:14px; background:yellow; width:100%; position:absolute"><FormattedTextBox {...props} fieldKey={"caption"} /></div>`
+ export const Caption = new Template("Caption", TemplatePosition.OutterBottom,
+ `<div id="screenSpace" style="top: 100%; font-size:14px; background:yellow; width:100%; position:absolute"><FormattedTextBox {...props} fieldKey={"caption"} /></div>`
);
-
- export const InnerCaption = new Template("Inner caption", TemplatePosition.InnerBottom,
- `<div><div style="margin:auto; height:calc(100% - 50px); width:100%;">{layout}</div><div style="height:50px; width:100%; position:absolute"><FormattedTextBox {...props} fieldKey={"caption"}/></div></div>`
- );
-
- export const SideCaption = new Template("Side caption", TemplatePosition.OutterRight,
- `<div><div style="margin:auto; height:100%; width:100%;">{layout}</div><div style="height:100%; width:300px; position:absolute; top: 0; right: -300px;"><FormattedTextBox {...props} fieldKey={"caption"}/></div> </div>`
- );
-
export const TitleOverlay = new Template("TitleOverlay", TemplatePosition.InnerTop,
`<div><div style="height:100%; width:100%;position:absolute;">{layout}</div>
<div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; ">
@@ -72,7 +63,7 @@ export namespace Templates {
</div>`
);
- export const TemplateList: Template[] = [Title, TitleOverlay, OuterCaption, InnerCaption, SideCaption, Bullet];
+ export const TemplateList: Template[] = [Title, TitleOverlay, Caption, Bullet];
export function sortTemplates(a: Template, b: Template) {
if (a.Position < b.Position) { return -1; }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 1d4584cfe..62b1456cf 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -67,8 +67,8 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
if (srcBrushDocs === undefined) srcTarg.brushingDocs = srcBrushDocs = new List<Doc>();
else brushAction(srcBrushDocs);
}
- })
- })
+ });
+ });
});
}
componentWillUnmount() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 25a75904b..5aa74c703 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -176,7 +176,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && !this.isSelected()) {
return;
}
- this._hitIsBullet = (e.target && (e.target as any).id === "isBullet");
+ this._hitIsBullet = (e.target && (e.target as any).id === "isBullet") || Cast(this.props.Document.subBulletDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc).length > 0;
if (e.shiftKey && e.buttons === 1) {
if (this.props.isTopMost) {
this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined, this._hitIsBullet);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index fc58e513b..df3eb159b 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -184,7 +184,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
- image(node, view, getPos) { return new ImageResizeView(node, view, getPos) }
+ image(node, view, getPos) { return new ImageResizeView(node, view, getPos); }
}
});
let text = StrCast(this.props.Document.documentText);
@@ -232,8 +232,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
let docid = href.replace(DocServer.prepend("/doc/"), "");
DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
if (f instanceof Doc) {
- if (DocumentManager.Instance.getDocumentView(f))
+ if (DocumentManager.Instance.getDocumentView(f)) {
DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
+ }
else CollectionDockingView.Instance.AddRightSplit(f);
}
}));
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 08cfa590b..611cb66b6 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -31,28 +31,7 @@ export class LinkBox extends React.Component<Props> {
@undoBatch
onViewButtonPressed = async (e: React.PointerEvent): Promise<void> => {
e.stopPropagation();
- let docView = DocumentManager.Instance.getDocumentView(this.props.pairedDoc);
- if (docView) {
- docView.props.focus(docView.props.Document);
- } else {
- const contextDoc = await Cast(this.props.pairedDoc.annotationOn, Doc);
- if (!contextDoc) {
- CollectionDockingView.Instance.AddRightSplit(Doc.MakeDelegate(this.props.pairedDoc));
- } else {
- const page = NumCast(this.props.pairedDoc.page, undefined);
- const curPage = NumCast(contextDoc.curPage, undefined);
- if (page !== curPage) {
- contextDoc.curPage = page;
- }
- let contextView = DocumentManager.Instance.getDocumentView(contextDoc);
- if (contextView) {
- contextDoc.panTransformType = "Ease";
- contextView.props.focus(contextDoc);
- } else {
- CollectionDockingView.Instance.AddRightSplit(contextDoc);
- }
- }
- }
+ DocumentManager.Instance.jumpToDocument(this.props.pairedDoc);
}
onEditButtonPressed = (e: React.PointerEvent): void => {
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 04ef00722..0dca4b4b1 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -41,7 +41,7 @@ class Test extends React.Component {
doc.fields = "test";
doc.test = "hello doc";
doc.url = url;
- doc.testDoc = doc2;
+ //doc.testDoc = doc2;
const test1: TestDoc = TestDoc(doc);
@@ -70,7 +70,9 @@ class Test extends React.Component {
}
render() {
- return <button onClick={this.onClick}>Click me</button>;
+ return <div><button onClick={this.onClick}>Click me</button>
+ {/* <input onKeyPress={this.onEnter}></input> */}
+ </div>;
}
}
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index bbd8157f6..a5f5e368b 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -56,6 +56,7 @@ export function getter(target: any, prop: string | symbol | number, receiver: an
return getField(target, prop);
}
+//TODO The callback parameter is never being passed in currently, so we should be able to get rid of it.
export function getField(target: any, prop: string | number, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any {
const field = target.__fields[prop];
if (field instanceof ProxyField) {
diff --git a/src/server/Search.ts b/src/server/Search.ts
new file mode 100644
index 000000000..c3cb3c3e6
--- /dev/null
+++ b/src/server/Search.ts
@@ -0,0 +1,45 @@
+import * as rp from 'request-promise';
+import { Database } from './database';
+import { thisExpression } from 'babel-types';
+
+export class Search {
+ public static Instance = new Search();
+ private url = 'http://localhost:8983/solr/';
+
+ public async updateDocument(document: any) {
+ try {
+ return await rp.post(this.url + "dash/update", {
+ headers: { 'content-type': 'application/json' },
+ body: JSON.stringify([document])
+ });
+ } catch { }
+ }
+
+ public async search(query: string) {
+ try {
+ const searchResults = JSON.parse(await rp.get(this.url + "dash/select", {
+ qs: {
+ q: query
+ }
+ }));
+ const fields = searchResults.response.docs;
+ const ids = fields.map((field: any) => field.id);
+ return ids;
+ } catch {
+ return [];
+ }
+ }
+
+ public async clear() {
+ try {
+ return await rp.post(this.url + "dash/update", {
+ body: {
+ delete: {
+ query: "*:*"
+ }
+ },
+ json: true
+ });
+ } catch { }
+ }
+} \ No newline at end of file
diff --git a/src/server/index.ts b/src/server/index.ts
index 6801b3132..f816af48a 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -7,10 +7,10 @@ import * as expressValidator from 'express-validator';
import * as formidable from 'formidable';
import * as fs from 'fs';
import * as mobileDetect from 'mobile-detect';
-import { ObservableMap } from 'mobx';
import * as passport from 'passport';
import * as path from 'path';
import * as request from 'request';
+import * as rp from 'request-promise';
import * as io from 'socket.io';
import { Socket } from 'socket.io';
import * as webpack from 'webpack';
@@ -21,7 +21,7 @@ import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLo
import { DashUserModel } from './authentication/models/user_model';
import { Client } from './Client';
import { Database } from './database';
-import { MessageStore, Transferable, Diff } from "./Message";
+import { MessageStore, Transferable, Types, Diff } from "./Message";
import { RouteStore } from './RouteStore';
const app = express();
const config = require('../../webpack.config');
@@ -31,6 +31,9 @@ const serverPort = 4321;
import expressFlash = require('express-flash');
import flash = require('connect-flash');
import c = require("crypto");
+import { Search } from './Search';
+import { debug } from 'util';
+import _ = require('lodash');
const MongoStore = require('connect-mongo')(session);
const mongoose = require('mongoose');
@@ -117,8 +120,16 @@ app.get("/pull", (req, res) =>
res.redirect("/");
}));
+// SEARCH
+
// GETTERS
+app.get("/search", async (req, res) => {
+ let query = req.query.query || "hello";
+ let results = await Search.Instance.search(query);
+ res.send(results);
+});
+
// anyone attempting to navigate to localhost at this port will
// first have to login
addSecureRoute(
@@ -240,6 +251,7 @@ server.on("connection", function (socket: Socket) {
async function deleteFields() {
await Database.Instance.deleteAll();
+ await Search.Instance.clear();
await Database.Instance.deleteAll('newDocuments');
}
@@ -248,6 +260,7 @@ async function deleteAll() {
await Database.Instance.deleteAll('newDocuments');
await Database.Instance.deleteAll('sessions');
await Database.Instance.deleteAll('users');
+ await Search.Instance.clear();
}
function barReceived(guid: String) {
@@ -266,6 +279,10 @@ function getFields([ids, callback]: [string[], (result: Transferable[]) => void]
function setField(socket: Socket, newValue: Transferable) {
Database.Instance.update(newValue.id, newValue, () =>
socket.broadcast.emit(MessageStore.SetField.Message, newValue));
+ if (newValue.type === Types.Text) {
+ Search.Instance.updateDocument({ id: newValue.id, data: (newValue as any).data });
+ console.log("set field");
+ }
}
function GetRefField([id, callback]: [string, (result?: Transferable) => void]) {
@@ -276,9 +293,73 @@ function GetRefFields([ids, callback]: [string[], (result?: Transferable[]) => v
Database.Instance.getDocuments(ids, callback, "newDocuments");
}
+
+const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {
+ "number": "_n",
+ "string": "_t",
+ "image": ["_t", "url"],
+ "video": ["_t", "url"],
+ "pdf": ["_t", "url"],
+ "audio": ["_t", "url"],
+ "web": ["_t", "url"],
+ "date": ["_d", value => new Date(value.date).toISOString()],
+ "proxy": ["_i", "fieldId"],
+ "list": ["_l", list => {
+ const results = [];
+ for (const value of list.fields) {
+ const term = ToSearchTerm(value);
+ if (term) {
+ results.push(term.value);
+ }
+ }
+ return results.length ? results : null;
+ }]
+};
+
+function ToSearchTerm(val: any): { suffix: string, value: any } | undefined {
+ const type = val.__type || typeof val;
+ let suffix = suffixMap[type];
+ if (!suffix) {
+ return;
+ }
+
+ if (Array.isArray(suffix)) {
+ const accessor = suffix[1];
+ if (typeof accessor === "function") {
+ val = accessor(val);
+ } else {
+ val = val[accessor];
+ }
+ suffix = suffix[0];
+ }
+
+ return { suffix, value: val };
+}
+
function UpdateField(socket: Socket, diff: Diff) {
Database.Instance.update(diff.id, diff.diff,
() => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false, "newDocuments");
+ const docfield = diff.diff.$set;
+ if (!docfield) {
+ return;
+ }
+ const update: any = { id: diff.id };
+ let dynfield = false;
+ for (let key in docfield) {
+ if (!key.startsWith("fields.")) continue;
+ let val = docfield[key];
+ let term = ToSearchTerm(val);
+ if (term !== undefined) {
+ let { suffix, value } = term;
+ key = key.substring(7);
+ Object.values(suffixMap).forEach(suf => update[key + suf] = null);
+ update[key + suffix] = { set: value };
+ dynfield = true;
+ }
+ }
+ if (dynfield) {
+ Search.Instance.updateDocument(update);
+ }
}
function CreateField(newValue: any) {