aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBob Zeleznik <zzzman@gmail.com>2019-12-10 18:49:03 -0500
committerBob Zeleznik <zzzman@gmail.com>2019-12-10 18:49:03 -0500
commit1dbb45826d4414ed7a1acb5daff730b6e79e97c2 (patch)
tree9258a346834abccc9ce4881664ccb956f53ae9f7 /src
parent4ab742c54d600fb62b02268f48e711258558924b (diff)
parent68ccde3251622fdb51ef3d21282fddd8207da3c1 (diff)
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web
Diffstat (limited to 'src')
-rw-r--r--src/client/util/ParagraphNodeSpec.ts6
-rw-r--r--src/client/util/RichTextRules.ts18
-rw-r--r--src/client/util/RichTextSchema.tsx16
-rw-r--r--src/client/views/EditableView.tsx2
-rw-r--r--src/client/views/OverlayView.tsx4
-rw-r--r--src/client/views/collections/CollectionTreeView.scss1
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx10
-rw-r--r--src/client/views/nodes/DocumentView.tsx22
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx28
-rw-r--r--src/client/views/nodes/FormattedTextBoxComment.tsx5
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx4
-rw-r--r--src/client/views/search/SearchItem.tsx4
-rw-r--r--src/server/ActionUtilities.ts27
-rw-r--r--src/server/ApiManagers/SearchManager.ts2
-rw-r--r--src/server/GarbageCollector.ts4
-rw-r--r--src/server/Search.ts28
-rw-r--r--src/server/Websocket/Websocket.ts15
-rw-r--r--src/server/authentication/models/user_model.ts4
-rw-r--r--src/server/remapUrl.ts2
-rw-r--r--src/server/updateSearch.ts114
20 files changed, 235 insertions, 81 deletions
diff --git a/src/client/util/ParagraphNodeSpec.ts b/src/client/util/ParagraphNodeSpec.ts
index 593aec498..fceb8c00f 100644
--- a/src/client/util/ParagraphNodeSpec.ts
+++ b/src/client/util/ParagraphNodeSpec.ts
@@ -105,6 +105,10 @@ function toDOM(node: Node): DOMOutputSpec {
style += `padding-bottom: ${paddingBottom};`;
}
+ if (indent) {
+ style += `text-indent: ${indent}; padding-left: ${indent < 0 ? -indent : undefined};`;
+ }
+
style && (attrs.style = style);
if (indent) {
@@ -115,7 +119,7 @@ function toDOM(node: Node): DOMOutputSpec {
attrs.id = id;
}
- return ['p', { ...attrs, ...{ style: `text-indent: ${indent}; padding-left: ${indent < 0 ? -indent : undefined};` } }, 0];
+ return ['p', attrs, 0];
}
export const toParagraphDOM = toDOM;
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 4e60976d5..5f2d67a3e 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -64,10 +64,10 @@ export const inpRules = {
new RegExp(/^#([0-9]+)\s$/),
(state, match, start, end) => {
const size = Number(match[1]);
- const ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
- const heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+ const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
+ const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
if (ruleProvider && heading) {
- (Cast(FormattedTextBox.InputBoxOverlay!.props.Document, Doc) as Doc).heading = size;
+ (Cast(FormattedTextBox.FocusedBox!.props.Document, Doc) as Doc).heading = size;
return state.tr.deleteRange(start, end);
}
return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
@@ -163,8 +163,8 @@ export const inpRules = {
(state, match, start, end) => {
const node = (state.doc.resolve(start) as any).nodeAfter;
const sm = state.storedMarks || undefined;
- const ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
- const heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+ const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
+ const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
if (ruleProvider && heading) {
ruleProvider["ruleAlign_" + heading] = "center";
return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
@@ -178,8 +178,8 @@ export const inpRules = {
(state, match, start, end) => {
const node = (state.doc.resolve(start) as any).nodeAfter;
const sm = state.storedMarks || undefined;
- const ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
- const heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+ const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
+ const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
if (ruleProvider && heading) {
ruleProvider["ruleAlign_" + heading] = "left";
return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
@@ -193,8 +193,8 @@ export const inpRules = {
(state, match, start, end) => {
const node = (state.doc.resolve(start) as any).nodeAfter;
const sm = state.storedMarks || undefined;
- const ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
- const heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+ const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
+ const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
if (ruleProvider && heading) {
ruleProvider["ruleAlign_" + heading] = "right";
return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 4369be1ee..fac8f4027 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -177,18 +177,7 @@ export const nodes: { [index: string]: NodeSpec } = {
docid: { default: "" },
},
group: "inline",
- draggable: true,
- // parseDOM: [{
- // tag: "img[src]", getAttrs(dom: any) {
- // return {
- // src: dom.getAttribute("src"),
- // title: dom.getAttribute("title"),
- // alt: dom.getAttribute("alt"),
- // width: Math.min(100, Number(dom.getAttribute("width"))),
- // };
- // }
- // }],
- // TODO if we don't define toDom, dragging the image crashes. Why?
+ draggable: false,
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
return ["div", { ...node.attrs, ...attrs }];
@@ -704,6 +693,7 @@ export class DashDocView {
this._dashSpan = document.createElement("div");
this._outer = document.createElement("span");
this._outer.style.position = "relative";
+ this._outer.style.textIndent = "0";
this._outer.style.width = node.attrs.width;
this._outer.style.height = node.attrs.height;
this._outer.style.display = node.attrs.hidden ? "none" : "inline-block";
@@ -772,7 +762,7 @@ export class DashDocView {
}
});
const self = this;
- this._dashSpan.onkeydown = function (e: any) { e.stopPropagation(); };
+ this._dashSpan.onkeydown = function (e: any) { };
this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); };
this._dashSpan.onwheel = function (e: any) { e.preventDefault(); };
this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); };
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index ea9d548a1..d0cecf03d 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -120,7 +120,9 @@ export class EditableView extends React.Component<EditableProps> {
@action
setIsFocused = (value: boolean) => {
+ let wasFocused = this._editing;
this._editing = value;
+ return wasFocused !== this._editing;
}
render() {
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 9b42d199c..cd330d492 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,7 +1,7 @@
import * as React from "react";
import { observer } from "mobx-react";
import { observable, action, trace, computed } from "mobx";
-import { Utils, emptyFunction, returnOne, returnTrue, returnEmptyString, returnZero, returnFalse } from "../../Utils";
+import { Utils, emptyFunction, returnOne, returnTrue, returnEmptyString, returnZero, returnFalse, emptyPath } from "../../Utils";
import './OverlayView.scss';
import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
@@ -174,7 +174,7 @@ export class OverlayView extends React.Component {
return <div className="overlayView-doc" key={d[Id]} onPointerDown={onPointerDown} style={{ transform: `translate(${d.x}px, ${d.y}px)`, display: d.isMinimized ? "none" : "" }}>
<DocumentView
Document={d}
- LibraryPath={[]}
+ LibraryPath={emptyPath}
ChromeHeight={returnZero}
// isSelected={returnFalse}
// select={emptyFunction}
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 8b12395a7..0b9dc2eb2 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -15,6 +15,7 @@
background: $light-color-secondary;
font-size: 13px;
overflow: auto;
+ user-select: none;
cursor: default;
ul {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index bf612e4f1..a0ddc8884 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -9,7 +9,7 @@ import { List } from '../../../new_fields/List';
import { Document, listSpec } from '../../../new_fields/Schema';
import { ComputedField, ScriptField } from '../../../new_fields/ScriptField';
import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types';
-import { emptyFunction, Utils, returnFalse } from '../../../Utils';
+import { emptyFunction, Utils, returnFalse, emptyPath } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from '../../util/DocumentManager';
@@ -344,8 +344,8 @@ class TreeView extends React.Component<TreeViewProps> {
<ContentFittingDocumentView
Document={layoutDoc}
DataDocument={this.templateDataDoc}
- LibraryPath={[]}
- renderDepth={this.props.renderDepth}
+ LibraryPath={emptyPath}
+ renderDepth={this.props.renderDepth + 1}
showOverlays={this.noOverlays}
ruleProvider={this.props.document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.document : this.props.ruleProvider}
fitToBox={this.boundsOfCollectionDocument !== undefined}
@@ -626,8 +626,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
const addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, false, false, false);
const moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
return !this.childDocs ? (null) : (
- <div id="body" className="collectionTreeView-dropTarget"
- style={{ overflow: "auto", background: StrCast(this.props.Document.backgroundColor, "lightgray"), paddingTop: `${NumCast(this.props.Document.yMargin, 20)}px` }}
+ <div className="collectionTreeView-dropTarget" id="body"
+ style={{ background: StrCast(this.props.Document.backgroundColor, "lightgray"), paddingTop: `${NumCast(this.props.Document.yMargin, 20)}px` }}
onContextMenu={this.onContextMenu}
onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
onDrop={this.onTreeDrop}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c6b134d6c..9219da80b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -92,6 +92,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
private _hitTemplateDrag = false;
private _mainCont = React.createRef<HTMLDivElement>();
private _dropDisposer?: DragManager.DragDropDisposer;
+ private _titleRef = React.createRef<EditableView>();
public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
public get ContentDiv() { return this._mainCont.current; }
@@ -138,6 +139,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
+ onKeyDown = (e: React.KeyboardEvent) => {
+ if (e.altKey && e.key === "t" && !(e.nativeEvent as any).StopPropagationForReal) {
+ (e.nativeEvent as any).StopPropagationForReal = true; // e.stopPropagation() doesn't seem to work...
+ e.stopPropagation();
+ if (!StrCast(this.Document.showTitle)) this.Document.showTitle = "title";
+ if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true), 0);
+ else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text...
+ {
+ this._titleRef.current?.setIsFocused(false);
+ let any = (this._mainCont.current?.getElementsByClassName("ProseMirror")?.[0] as any);
+ any.keeplocation = true;
+ any?.focus();
+ }
+ }
+ }
+ }
+
onClick = async (e: React.MouseEvent) => {
if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
@@ -622,7 +640,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
position: showTextTitle ? "relative" : "absolute",
pointerEvents: SelectionManager.GetIsDragging() ? "none" : "all",
}}>
- <EditableView
+ <EditableView ref={this._titleRef}
contents={this.Document[showTitle]}
display={"block"} height={72} fontSize={12}
GetValue={() => StrCast(this.Document[showTitle])}
@@ -694,7 +712,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"];
const highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc.viewType !== CollectionViewType.Linear;
- return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont}
+ return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} onKeyDown={this.onKeyDown}
onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
onPointerEnter={e => Doc.BrushDoc(this.props.Document)} onPointerLeave={e => Doc.UnBrushDoc(this.props.Document)}
style={{
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 6d3c1434a..70fa4974d 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -80,7 +80,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
private _proseRef?: HTMLDivElement;
private _editorView: Opt<EditorView>;
private _applyingChange: boolean = false;
- private _nodeClicked: any;
private _searchIndex = 0;
private _sidebarMovement = 0;
private _lastX = 0;
@@ -101,6 +100,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
@observable private _ruleFontFamily = "Arial";
@observable private _fontAlign = "";
@observable private _entered = false;
+
+ public static FocusedBox: FormattedTextBox | undefined;
public static SelectOnLoad = "";
public static IsFragment(html: string) {
return html.indexOf("data-pm-slice") !== -1;
@@ -828,7 +829,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
});
- if (startup) {
+ if (startup && this._editorView) {
Doc.GetProto(doc).documentText = undefined;
this._editorView.dispatch(this._editorView.state.tr.insertText(startup));
}
@@ -869,10 +870,11 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
this._buttonBarReactionDisposer && this._buttonBarReactionDisposer();
this._editorView && this._editorView.destroy();
}
+
+ static _downEvent: any;
onPointerDown = (e: React.PointerEvent): void => {
+ FormattedTextBox._downEvent = true;
FormattedTextBoxComment.textBox = this;
- const pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
- pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos));
if (this.props.onClick && e.button === 0) {
e.preventDefault();
}
@@ -885,6 +887,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
onPointerUp = (e: React.PointerEvent): void => {
+ if (!FormattedTextBox._downEvent) return;
+ FormattedTextBox._downEvent = false;
if (!(e.nativeEvent as any).formattedHandled) {
FormattedTextBoxComment.textBox = this;
FormattedTextBoxComment.update(this._editorView!);
@@ -896,11 +900,17 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
}
- static InputBoxOverlay: FormattedTextBox | undefined;
@action
onFocused = (e: React.FocusEvent): void => {
- FormattedTextBox.InputBoxOverlay = this;
+ FormattedTextBox.FocusedBox = this;
this.tryUpdateHeight();
+
+ // see if we need to preserve the insertion point
+ let prosediv = this._proseRef?.children?.[0] as any;
+ let keeplocation = prosediv?.keeplocation;
+ prosediv && (prosediv.keeplocation = undefined);
+ let pos = this._editorView?.state.selection.$from.pos || 1;
+ keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
}
onPointerWheel = (e: React.WheelEvent): void => {
// if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
@@ -1043,10 +1053,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
this._undoTyping.end();
this._undoTyping = undefined;
}
- this.doLinkOnDeselect();
+ this.doLinkOnDeselect(); 6
}
onKeyPress = (e: React.KeyboardEvent) => {
+ if (e.altKey) {
+ e.preventDefault();
+ return;
+ }
if (!this._editorView!.state.selection.empty && e.key === "%") {
(this._editorView!.state as any).EnteringStyle = true;
e.preventDefault();
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 9f1dd4aec..409229c1a 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -4,7 +4,7 @@ import { EditorView } from "prosemirror-view";
import * as ReactDOM from 'react-dom';
import { Doc } from "../../../new_fields/Doc";
import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, Utils } from "../../../Utils";
+import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { DocumentManager } from "../../util/DocumentManager";
import { schema } from "../../util/RichTextSchema";
@@ -94,6 +94,7 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark,
FormattedTextBoxComment.opened, keep);
e.stopPropagation();
+ e.preventDefault();
};
root && root.appendChild(FormattedTextBoxComment.tooltip);
}
@@ -178,7 +179,7 @@ export class FormattedTextBoxComment {
if (target) {
ReactDOM.render(<ContentFittingDocumentView
Document={target}
- LibraryPath={[]}
+ LibraryPath={emptyPath}
fitToBox={true}
moveDocument={returnFalse}
getTransform={Transform.Identity}
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index cfb9319bb..37c837414 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -9,7 +9,7 @@ import { documentSchema } from '../../../new_fields/documentSchemas';
import { Id } from "../../../new_fields/FieldSymbols";
import { createSchema, makeInterface } from '../../../new_fields/Schema';
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, returnFalse } from "../../../Utils";
+import { emptyFunction, returnFalse, emptyPath } from "../../../Utils";
import { DocumentType } from "../../documents/DocumentTypes";
import { Transform } from "../../util/Transform";
import { CollectionViewType } from '../collections/CollectionView';
@@ -171,7 +171,7 @@ export class PresElementBox extends DocComponent<FieldViewProps, PresDocument>(P
}}>
<ContentFittingDocumentView
Document={this.targetDoc}
- LibraryPath={[]}
+ LibraryPath={emptyPath}
fitToBox={StrCast(this.targetDoc.type).indexOf(DocumentType.COL) !== -1}
addDocument={returnFalse}
removeDocument={returnFalse}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 0be583358..1007102f6 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -7,7 +7,7 @@ import { observer } from "mobx-react";
import { Doc } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../../Utils";
+import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, emptyPath } from "../../../Utils";
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager, SetupDrag } from "../../util/DragManager";
@@ -160,7 +160,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
onPointerLeave={action(() => this._displayDim = 50)} >
<DocumentView
Document={this.props.doc}
- LibraryPath={[]}
+ LibraryPath={emptyPath}
fitToBox={StrCast(this.props.doc.type).indexOf(DocumentType.COL) !== -1}
addDocument={returnFalse}
removeDocument={returnFalse}
diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts
index 94008e171..4667254d8 100644
--- a/src/server/ActionUtilities.ts
+++ b/src/server/ActionUtilities.ts
@@ -3,7 +3,7 @@ import { ExecOptions } from 'shelljs';
import { exec } from 'child_process';
import * as path from 'path';
import * as rimraf from "rimraf";
-import { yellow } from 'colors';
+import { yellow, Color } from 'colors';
export const command_line = (command: string, fromDirectory?: string) => {
return new Promise<string>((resolve, reject) => {
@@ -29,18 +29,29 @@ export const write_text_file = (relativePath: string, contents: any) => {
});
};
-export interface LogData {
+export interface LogData<T> {
startMessage: string;
endMessage: string;
- action: () => void | Promise<void>;
+ action: () => T | Promise<T>;
+ color?: Color;
}
let current = Math.ceil(Math.random() * 20);
-export async function log_execution({ startMessage, endMessage, action }: LogData) {
- const color = `\x1b[${31 + current++ % 6}m%s\x1b[0m`;
- console.log(color, `${startMessage}...`);
- await action();
- console.log(color, endMessage);
+export async function log_execution<T>({ startMessage, endMessage, action, color }: LogData<T>): Promise<T> {
+ let result: T;
+ const formattedStart = `${startMessage}...`;
+ const formattedEnd = `${endMessage}.`;
+ if (color) {
+ console.log(color(formattedStart));
+ result = await action();
+ console.log(color(formattedEnd));
+ } else {
+ const color = `\x1b[${31 + current++ % 6}m%s\x1b[0m`;
+ console.log(color, formattedStart);
+ result = await action();
+ console.log(color, formattedEnd);
+ }
+ return result;
}
export function logPort(listener: string, port: number) {
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index ccd0896bd..ccfd570b8 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -57,7 +57,7 @@ export class SearchManager extends ApiManager {
res.send([]);
return;
}
- const results = await Search.Instance.search(solrQuery);
+ const results = await Search.search(solrQuery);
res.send(results);
}
});
diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts
index 09b52eadf..5729c3ee5 100644
--- a/src/server/GarbageCollector.ts
+++ b/src/server/GarbageCollector.ts
@@ -100,7 +100,7 @@ async function GarbageCollect(full: boolean = true) {
if (!full) {
await Database.Instance.updateMany({ _id: { $nin: notToDelete } }, { $set: { "deleted": true } });
await Database.Instance.updateMany({ _id: { $in: notToDelete } }, { $unset: { "deleted": true } });
- console.log(await Search.Instance.updateDocuments(
+ console.log(await Search.updateDocuments(
notToDelete.map<any>(id => ({
id, deleted: { set: null }
}))
@@ -122,7 +122,7 @@ async function GarbageCollect(full: boolean = true) {
// const result = await Database.Instance.delete({ _id: { $in: toDelete } }, "newDocuments");
console.log(`${deleted} documents deleted`);
- await Search.Instance.deleteDocuments(toDelete);
+ await Search.deleteDocuments(toDelete);
console.log("Cleared search documents");
const folder = "./src/server/public/files/";
diff --git a/src/server/Search.ts b/src/server/Search.ts
index 723dc101b..2b59c14b1 100644
--- a/src/server/Search.ts
+++ b/src/server/Search.ts
@@ -1,14 +1,12 @@
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/';
+const pathTo = (relative: string) => `http://localhost:8983/solr/dash/${relative}`;
- public async updateDocument(document: any) {
+export namespace Search {
+
+ export async function updateDocument(document: any) {
try {
- const res = await rp.post(this.url + "dash/update", {
+ const res = await rp.post(pathTo("update"), {
headers: { 'content-type': 'application/json' },
body: JSON.stringify([document])
});
@@ -18,9 +16,9 @@ export class Search {
}
}
- public async updateDocuments(documents: any[]) {
+ export async function updateDocuments(documents: any[]) {
try {
- const res = await rp.post(this.url + "dash/update", {
+ const res = await rp.post(pathTo("update"), {
headers: { 'content-type': 'application/json' },
body: JSON.stringify(documents)
});
@@ -30,9 +28,9 @@ export class Search {
}
}
- public async search(query: any) {
+ export async function search(query: any) {
try {
- const searchResults = JSON.parse(await rp.get(this.url + "dash/select", {
+ const searchResults = JSON.parse(await rp.get(pathTo("select"), {
qs: query
}));
const { docs, numFound } = searchResults.response;
@@ -43,9 +41,9 @@ export class Search {
}
}
- public async clear() {
+ export async function clear() {
try {
- return await rp.post(this.url + "dash/update", {
+ return rp.post(pathTo("update"), {
body: {
delete: {
query: "*:*"
@@ -56,7 +54,7 @@ export class Search {
} catch { }
}
- public deleteDocuments(docs: string[]) {
+ export async function deleteDocuments(docs: string[]) {
const promises: rp.RequestPromise[] = [];
const nToDelete = 1000;
let index = 0;
@@ -64,7 +62,7 @@ export class Search {
const count = Math.min(docs.length - index, nToDelete);
const deleteIds = docs.slice(index, index + count);
index += count;
- promises.push(rp.post(this.url + "dash/update", {
+ promises.push(rp.post(pathTo("update"), {
body: {
delete: {
query: deleteIds.map(id => `id:"${id}"`).join(" ")
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index 5c0bb508b..76e02122b 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -10,7 +10,6 @@ import { GoogleCredentialsLoader } from "../credentials/CredentialsLoader";
import { logPort } from "../ActionUtilities";
import { timeMap } from "../ApiManagers/UserManager";
import { green } from "colors";
-import { SolrManager } from "../ApiManagers/SearchManager";
export namespace WebSocket {
@@ -80,7 +79,7 @@ export namespace WebSocket {
export async function deleteFields() {
await Database.Instance.deleteAll();
- await Search.Instance.clear();
+ await Search.clear();
await Database.Instance.deleteAll('newDocuments');
}
@@ -89,7 +88,7 @@ export namespace WebSocket {
await Database.Instance.deleteAll('newDocuments');
await Database.Instance.deleteAll('sessions');
await Database.Instance.deleteAll('users');
- await Search.Instance.clear();
+ await Search.clear();
}
function barReceived(socket: SocketIO.Socket, userEmail: string) {
@@ -111,7 +110,7 @@ export namespace WebSocket {
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 });
+ Search.updateDocument({ id: newValue.id, data: (newValue as any).data });
console.log("set field");
console.log("checking in");
}
@@ -197,7 +196,7 @@ export namespace WebSocket {
}
}
if (dynfield) {
- Search.Instance.updateDocument(update);
+ Search.updateDocument(update);
}
}
@@ -206,16 +205,14 @@ export namespace WebSocket {
socket.broadcast.emit(MessageStore.DeleteField.Message, id);
});
- Search.Instance.deleteDocuments([id]);
+ Search.deleteDocuments([id]);
}
function DeleteFields(socket: Socket, ids: string[]) {
Database.Instance.delete({ _id: { $in: ids } }, "newDocuments").then(() => {
socket.broadcast.emit(MessageStore.DeleteFields.Message, ids);
});
-
- Search.Instance.deleteDocuments(ids);
-
+ Search.deleteDocuments(ids);
}
function CreateField(newValue: any) {
diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/models/user_model.ts
index cc670a03a..78e39dbc1 100644
--- a/src/server/authentication/models/user_model.ts
+++ b/src/server/authentication/models/user_model.ts
@@ -73,7 +73,11 @@ userSchema.pre("save", function save(next) {
});
const comparePassword: comparePasswordFunction = function (this: DashUserModel, candidatePassword, cb) {
+ // Choose one of the following bodies for authentication logic.
+ // secure
bcrypt.compare(candidatePassword, this.password, cb);
+ // bypass password
+ // cb(undefined, true);
};
userSchema.methods.comparePassword = comparePassword;
diff --git a/src/server/remapUrl.ts b/src/server/remapUrl.ts
index 5218a239a..45d2fdd33 100644
--- a/src/server/remapUrl.ts
+++ b/src/server/remapUrl.ts
@@ -54,7 +54,7 @@ async function update() {
}));
console.log("Done");
// await Promise.all(updates.map(update => {
- // return limit(() => Search.Instance.updateDocument(update));
+ // return limit(() => Search.updateDocument(update));
// }));
cursor.close();
}
diff --git a/src/server/updateSearch.ts b/src/server/updateSearch.ts
new file mode 100644
index 000000000..5ae6885c5
--- /dev/null
+++ b/src/server/updateSearch.ts
@@ -0,0 +1,114 @@
+import { Database } from "./database";
+import { Search } from "./Search";
+import { log_execution } from "./ActionUtilities";
+import { cyan, green, yellow, red } from "colors";
+
+const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {
+ "number": "_n",
+ "string": "_t",
+ "boolean": "_b",
+ "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 {
+ if (val === null || val === undefined) {
+ return;
+ }
+ 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 };
+}
+
+async function update() {
+ console.log(green("Beginning update..."));
+ await log_execution<void>({
+ startMessage: "Clearing existing Solr information...",
+ endMessage: "Solr information successfully cleared",
+ action: Search.clear,
+ color: cyan
+ });
+ const cursor = await log_execution({
+ startMessage: "Connecting to and querying for all documents from database...",
+ endMessage: "Connection successful and query complete",
+ action: () => Database.Instance.query({}),
+ color: yellow
+ });
+ const updates: any[] = [];
+ let numDocs = 0;
+ function updateDoc(doc: any) {
+ numDocs++;
+ if ((numDocs % 50) === 0) {
+ console.log(`Batch of 50 complete, total of ${numDocs}`);
+ }
+ if (doc.__type !== "Doc") {
+ return;
+ }
+ const fields = doc.fields;
+ if (!fields) {
+ return;
+ }
+ const update: any = { id: doc._id };
+ let dynfield = false;
+ for (const key in fields) {
+ const value = fields[key];
+ const term = ToSearchTerm(value);
+ if (term !== undefined) {
+ const { suffix, value } = term;
+ update[key + suffix] = value;
+ dynfield = true;
+ }
+ }
+ if (dynfield) {
+ updates.push(update);
+ }
+ }
+ await cursor.forEach(updateDoc);
+ const result = await log_execution({
+ startMessage: `Dispatching updates for ${updates.length} documents`,
+ endMessage: "Dispatched updates complete",
+ action: () => Search.updateDocuments(updates),
+ color: cyan
+ });
+ try {
+ const { status } = JSON.parse(result).responseHeader;
+ console.log(status ? red(`Failed with status code (${status})`) : green("Success!"));
+ } catch {
+ console.log(red("Error:"));
+ console.log(result);
+ console.log("\n");
+ }
+ await cursor.close();
+ process.exit(0);
+}
+
+update(); \ No newline at end of file