aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts2
-rw-r--r--src/client/views/InkingControl.tsx4
-rw-r--r--src/client/views/SearchBox.tsx45
-rw-r--r--src/client/views/collections/CollectionSubView.tsx4
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx65
-rw-r--r--src/client/views/nodes/ImageBox.tsx14
-rw-r--r--src/client/views/nodes/VideoBox.tsx85
-rw-r--r--src/client/views/search/FieldFilters.tsx2
-rw-r--r--src/client/views/search/FilterBox.tsx15
-rw-r--r--src/client/views/search/SearchBox.scss4
-rw-r--r--src/client/views/search/SearchBox.tsx40
-rw-r--r--src/server/GarbageCollector.ts87
-rw-r--r--src/server/authentication/controllers/user_controller.ts6
-rw-r--r--src/server/authentication/models/user_model.ts4
-rw-r--r--src/server/index.ts2
15 files changed, 200 insertions, 179 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 85fc721da..af2b95659 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -235,7 +235,7 @@ export namespace Docs {
let title = prototypeId.toUpperCase().replace(upper, `_${upper}`);
// synthesize the default options, the type and title from computed values and
// whatever options pertain to this specific prototype
- let options = { title: title, type: type, ...defaultOptions, ...(template.options || {}) };
+ let options = { title: title, type: type, baseProto: true, ...defaultOptions, ...(template.options || {}) };
let primary = layout.view.LayoutString();
let collectionView = layout.collectionView;
if (collectionView) {
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 1910e409b..c7f7bdb66 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,5 +1,5 @@
import { observable, action, computed, runInAction } from "mobx";
-import { ColorState } from 'react-color';
+import { ColorResult } from 'react-color';
import React = require("react");
import { observer } from "mobx-react";
import "./InkingControl.scss";
@@ -41,7 +41,7 @@ export class InkingControl extends React.Component {
}
@undoBatch
- switchColor = action((color: ColorState): void => {
+ switchColor = action((color: ColorResult): void => {
this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
if (InkingControl.Instance.selectedTool === InkTool.None) {
if (MainOverlayTextBox.Instance.SetColor(color.hex)) return;
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
index 6995e3c7d..8fb43021a 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/SearchBox.tsx
@@ -1,27 +1,18 @@
-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, faObjectGroup } 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 { faObjectGroup, faSearch } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
import * as rp from 'request-promise';
-import { SearchItem } from './search/SearchItem';
-import { isString } from 'util';
-import { constant } from 'async';
-import { DocServer } from '../DocServer';
import { Doc } from '../../new_fields/Doc';
import { Id } from '../../new_fields/FieldSymbols';
-import { DocumentManager } from '../util/DocumentManager';
-import { SetupDrag } from '../util/DragManager';
-import { Docs } from '../documents/Documents';
-import { RouteStore } from '../../server/RouteStore';
import { NumCast } from '../../new_fields/Types';
+import { DocServer } from '../DocServer';
+import { Docs } from '../documents/Documents';
+import { SetupDrag } from '../util/DragManager';
+import { SearchItem } from './search/SearchItem';
+import "./SearchBox.scss";
library.add(faSearch);
library.add(faObjectGroup);
@@ -72,22 +63,6 @@ export class SearchBox extends React.Component {
}
return docs;
}
- public static async convertDataUri(imageUri: string, returnedFilename: string) {
- try {
- let posting = DocServer.prepend(RouteStore.dataUriToImage);
- const returnedUri = await rp.post(posting, {
- body: {
- uri: imageUri,
- name: returnedFilename
- },
- json: true,
- });
- return returnedUri;
-
- } catch (e) {
- console.log(e);
- }
- }
@action
handleClickFilter = (e: Event): void => {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 71f1908f0..8e8d5708b 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -179,8 +179,8 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
}
}
if (text && text.indexOf("www.youtube.com/watch") !== -1) {
- const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");// + "?enablejsapi=1";
- this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, width: 400, height: 315 }));
+ const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
+ this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, width: 400, height: 315 }));
return;
}
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index faf507496..d7d5773ba 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -1,20 +1,12 @@
-import { action, observable, trace } from "mobx";
-import * as htmlToImage from "html-to-image";
+import { action } from "mobx";
import { observer } from "mobx-react";
-import { ContextMenu } from "../ContextMenu";
-import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView";
-import React = require("react");
-import "./CollectionVideoView.scss";
-import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import { NumCast } from "../../../new_fields/Types";
import { FieldView, FieldViewProps } from "../nodes/FieldView";
-import { emptyFunction, Utils } from "../../../Utils";
-import { Id } from "../../../new_fields/FieldSymbols";
import { VideoBox } from "../nodes/VideoBox";
-import { NumCast, Cast, StrCast } from "../../../new_fields/Types";
-import { VideoField } from "../../../new_fields/URLField";
-import { SearchBox } from "../search/SearchBox";
-import { DocServer } from "../../DocServer";
-import { Docs, DocUtils } from "../../documents/Documents";
+import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from "./CollectionBaseView";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import "./CollectionVideoView.scss";
+import React = require("react");
@observer
@@ -68,49 +60,6 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
this.props.Document.curPage = 0;
}
}
-
- onContextMenu = (e: React.MouseEvent): void => {
- if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- }
-
- let field = Cast(this.props.Document[this.props.fieldKey], VideoField);
- if (field) {
- let url = field.url.href;
- ContextMenu.Instance.addItem({
- description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt"
- });
- }
- let width = NumCast(this.props.Document.width);
- let height = NumCast(this.props.Document.height);
- ContextMenu.Instance.addItem({
- description: "Take Snapshot", event: async () => {
- var canvas = document.createElement('canvas');
- canvas.width = 640;
- canvas.height = 640 * NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.nativeWidth);
- var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
- this._videoBox!.player && ctx && ctx.drawImage(this._videoBox!.player!, 0, 0, canvas.width, canvas.height);
-
- //convert to desired file format
- var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
- // if you want to preview the captured image,
-
- let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, "");
- SearchBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
- if (returnedFilename) {
- let url = DocServer.prepend(returnedFilename);
- let imageSummary = Docs.Create.ImageDocument(url, {
- x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
- width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
- });
- this.props.addDocument && this.props.addDocument(imageSummary, false);
- DocUtils.MakeLink(imageSummary, this.props.Document);
- }
- });
- },
- icon: "expand-arrows-alt"
- });
- }
-
setVideoBox = (videoBox: VideoBox) => { this._videoBox = videoBox; };
private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
@@ -123,7 +72,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
render() {
return (
- <CollectionBaseView {...this.props} className="collectionVideoView-cont" onContextMenu={this.onContextMenu}>
+ <CollectionBaseView {...this.props} className="collectionVideoView-cont" >
{this.subView}
</CollectionBaseView>);
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 1df955f1f..4c5ad7a7d 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -22,6 +22,7 @@ import "./ImageBox.scss";
import React = require("react");
import { RouteStore } from '../../../server/RouteStore';
import { Docs } from '../../documents/Documents';
+import { DocServer } from '../../DocServer';
var requestImageSize = require('../../util/request-image-size');
var path = require('path');
@@ -157,8 +158,15 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
}).then(function (stream) {
gumStream = stream;
recorder = new MediaRecorder(stream);
- recorder.ondataavailable = function (e: any) {
- var url = URL.createObjectURL(e.data);
+ recorder.ondataavailable = async function (e: any) {
+ const formData = new FormData();
+ formData.append("file", e.data);
+ const res = await fetch(DocServer.prepend(RouteStore.upload), {
+ method: 'POST',
+ body: formData
+ });
+ const files = await res.json();
+ const url = DocServer.prepend(files[0]);
// upload to server with known URL
let audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", x: NumCast(self.props.Document.x), y: NumCast(self.props.Document.y), width: 200, height: 32 });
audioDoc.embed = true;
@@ -174,7 +182,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
recorder.stop();
gumStream.getAudioTracks()[0].stop();
- }, 1000);
+ }, 5000);
});
}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 3ade3396e..9806b10b5 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,19 +1,24 @@
import React = require("react");
-import { action, IReactionDisposer, observable, reaction, trace, computed, runInAction, untracked } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked } from "mobx";
import { observer } from "mobx-react";
+import * as rp from 'request-promise';
+import { InkTool } from "../../../new_fields/InkField";
import { makeInterface } from "../../../new_fields/Schema";
import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
import { VideoField } from "../../../new_fields/URLField";
+import { RouteStore } from "../../../server/RouteStore";
+import { Utils } from "../../../Utils";
+import { DocServer } from "../../DocServer";
+import { Docs, DocUtils } from "../../documents/Documents";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { DocComponent } from "../DocComponent";
+import { DocumentDecorations } from "../DocumentDecorations";
import { InkingControl } from "../InkingControl";
import { positionSchema } from "./DocumentView";
import { FieldView, FieldViewProps } from './FieldView';
import { pageSchema } from "./ImageBox";
import "./VideoBox.scss";
-import { InkTool } from "../../../new_fields/InkField";
-import { DocumentDecorations } from "../DocumentDecorations";
type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
const VideoDocument = makeInterface(positionSchema, pageSchema);
@@ -52,21 +57,17 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
@action public Play = (update: boolean = true) => {
this.Playing = true;
update && this.player && this.player.play();
- console.log("PLAYING = " + update);
update && this._youtubePlayer && this._youtubePlayer.playVideo();
!this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 500));
this.updateTimecode();
}
@action public Seek(time: number) {
- console.log("Seeking " + time);
- //if (this._youtubePlayer && this._youtubePlayer.getPlayerState() === 5) return;
this._youtubePlayer && this._youtubePlayer.seekTo(Math.round(time), true);
}
@action public Pause = (update: boolean = true) => {
this.Playing = false;
- console.log("PAUSING = " + update);
update && this.player && this.player.pause();
update && this._youtubePlayer && this._youtubePlayer.pauseVideo();
this._playTimer && clearInterval(this._playTimer);
@@ -119,13 +120,62 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
}
}
+ public static async convertDataUri(imageUri: string, returnedFilename: string) {
+ try {
+ let posting = DocServer.prepend(RouteStore.dataUriToImage);
+ const returnedUri = await rp.post(posting, {
+ body: {
+ uri: imageUri,
+ name: returnedFilename
+ },
+ json: true,
+ });
+ return returnedUri;
+
+ } catch (e) {
+ console.log(e);
+ }
+ }
specificContextMenu = (e: React.MouseEvent): void => {
let field = Cast(this.Document[this.props.fieldKey], VideoField);
if (field) {
+ let url = field.url.href;
let subitems: ContextMenuProps[] = [];
+ subitems.push({ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt" });
subitems.push({ description: "Toggle Show Controls", event: action(() => VideoBox._showControls = !VideoBox._showControls), icon: "expand-arrows-alt" });
- subitems.push({ description: "GOTO 3", event: action(() => this.Seek(3)), icon: "expand-arrows-alt" });
- subitems.push({ description: "PLAY", event: action(() => this.Play()), icon: "expand-arrows-alt" });
+ let width = NumCast(this.props.Document.width);
+ let height = NumCast(this.props.Document.height);
+ subitems.push({
+ description: "Take Snapshot", event: async () => {
+ var canvas = document.createElement('canvas');
+ canvas.width = 640;
+ canvas.height = 640 * NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.nativeWidth);
+ var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
+ if (ctx) {
+ ctx.rect(0, 0, canvas.width, canvas.height);
+ ctx.fillStyle = "blue";
+ ctx.fill();
+ this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height);
+ }
+
+ //convert to desired file format
+ var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
+ // if you want to preview the captured image,
+ let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, "");
+ VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
+ if (returnedFilename) {
+ let url = DocServer.prepend(returnedFilename);
+ let imageSummary = Docs.Create.ImageDocument(url, {
+ x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
+ width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
+ });
+ this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false);
+ DocUtils.MakeLink(imageSummary, this.props.Document);
+ }
+ });
+ },
+ icon: "expand-arrows-alt"
+ });
ContextMenu.Instance.addItem({ description: "Video Funcs...", subitems: subitems });
}
}
@@ -155,13 +205,18 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
else this._youtubeContentCreated = false;
let iframe = e.target;
+ let started = true;
let onYoutubePlayerStateChange = (event: any) => runInAction(() => {
- console.log("Event " + event.data);
- if (event.data == YT.PlayerState.PLAYING && !this.Playing) this.Play(false);
- if (event.data == YT.PlayerState.PAUSED && this.Playing) this.Pause(false);
+ if (started && event.data === YT.PlayerState.PLAYING) {
+ started = false;
+ this._youtubePlayer.unMute();
+ this.Pause();
+ return;
+ }
+ if (event.data === YT.PlayerState.PLAYING && !this.Playing) this.Play(false);
+ if (event.data === YT.PlayerState.PAUSED && this.Playing) this.Pause(false);
});
let onYoutubePlayerReady = (event: any) => {
- console.log("READY!");
this._reactionDisposer && this._reactionDisposer();
this._youtubeReactionDisposer && this._youtubeReactionDisposer();
this._reactionDisposer = reaction(() => this.props.Document.curPage, () => !this.Playing && this.Seek(this.Document.curPage || 0));
@@ -169,7 +224,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected() && !DocumentDecorations.Instance.Interacting;
iframe.style.pointerEvents = interactive ? "all" : "none";
}, { fireImmediately: true });
- }
+ };
this._youtubePlayer = new YT.Player(`${this.youtubeVideoId + this._youtubeIframeId}-player`, {
events: {
'onReady': onYoutubePlayerReady,
@@ -186,7 +241,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
let start = untracked(() => Math.round(NumCast(this.props.Document.curPage)));
return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
onLoad={this.youtubeIframeLoaded} className={`${style}`} width="640" height="390"
- src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`}
+ src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`}
></iframe>;
}
diff --git a/src/client/views/search/FieldFilters.tsx b/src/client/views/search/FieldFilters.tsx
index 648aac20a..7a33282d2 100644
--- a/src/client/views/search/FieldFilters.tsx
+++ b/src/client/views/search/FieldFilters.tsx
@@ -34,7 +34,7 @@ export class FieldFilters extends React.Component<FieldFilterProps> {
<div className="field-filters">
<CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.titleFieldStatus} updateStatus={this.props.updateTitleStatus} title={Keys.TITLE} />
<CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.authorFieldStatus} updateStatus={this.props.updateAuthorStatus} title={Keys.AUTHOR} />
- <CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.dataFieldStatus} updateStatus={this.props.updateDataStatus} title={Keys.DATA} />
+ <CheckBox default={false} numCount={3} parent={this} originalStatus={this.props.dataFieldStatus} updateStatus={this.props.updateDataStatus} title={"Deleted Docs"} />
</div>
);
}
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index f11fb008c..706d1eb7f 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -40,7 +40,7 @@ export class FilterBox extends React.Component {
@observable private _icons: string[] = this._allIcons;
@observable private _titleFieldStatus: boolean = true;
@observable private _authorFieldStatus: boolean = true;
- @observable private _dataFieldStatus: boolean = true;
+ @observable public _deletedDocsStatus: boolean = false;
@observable private _collectionStatus = false;
@observable private _collectionSelfStatus = true;
@observable private _collectionParentStatus = true;
@@ -87,6 +87,9 @@ export class FilterBox extends React.Component {
}
});
+
+ let el = acc[i] as HTMLElement;
+ el.click();
}
});
}
@@ -161,13 +164,13 @@ export class FilterBox extends React.Component {
if (this._authorFieldStatus) {
finalQuery = finalQuery + this.basicFieldFilters(query, Keys.AUTHOR);
}
- if (this._dataFieldStatus) {
+ if (this._deletedDocsStatus) {
finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA);
}
return finalQuery;
}
- get fieldFiltersApplied() { return !(this._dataFieldStatus && this._authorFieldStatus && this._titleFieldStatus); }
+ get fieldFiltersApplied() { return !(this._authorFieldStatus && this._titleFieldStatus); }
//TODO: basically all of this
//gets all of the collections of all the docviews that are selected
@@ -305,7 +308,7 @@ export class FilterBox extends React.Component {
updateAuthorStatus(newStat: boolean) { this._authorFieldStatus = newStat; }
@action.bound
- updateDataStatus(newStat: boolean) { this._dataFieldStatus = newStat; }
+ updateDataStatus(newStat: boolean) { this._deletedDocsStatus = newStat; }
@action.bound
updateCollectionStatus(newStat: boolean) { this._collectionStatus = newStat; }
@@ -321,7 +324,7 @@ export class FilterBox extends React.Component {
getParentCollectionStatus() { return this._collectionParentStatus; }
getTitleStatus() { return this._titleFieldStatus; }
getAuthorStatus() { return this._authorFieldStatus; }
- getDataStatus() { return this._dataFieldStatus; }
+ getDataStatus() { return this._deletedDocsStatus; }
// Useful queries:
// Delegates of a document: {!join from=id to=proto_i}id:{protoId}
@@ -373,7 +376,7 @@ export class FilterBox extends React.Component {
<div style={{ marginLeft: "auto" }}><NaviconButton onClick={this.toggleFieldOpen} /></div>
</div>
<div className="filter-panel"><FieldFilters
- titleFieldStatus={this._titleFieldStatus} dataFieldStatus={this._dataFieldStatus} authorFieldStatus={this._authorFieldStatus}
+ titleFieldStatus={this._titleFieldStatus} dataFieldStatus={this._deletedDocsStatus} authorFieldStatus={this._authorFieldStatus}
updateAuthorStatus={this.updateAuthorStatus} updateDataStatus={this.updateDataStatus} updateTitleStatus={this.updateTitleStatus} /> </div>
</div>
</div>
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 324ba3063..109b88ac9 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -41,11 +41,10 @@
}
.searchBox-results {
- margin-right: 142px;
+ margin-right: 136px;
top: 300px;
display: flex;
flex-direction: column;
- margin-right: 72px;
max-height: 560px;
overflow: hidden;
overflow-y: auto;
@@ -60,5 +59,6 @@
text-transform: uppercase;
text-align: left;
font-weight: bold;
+ margin-left: 28px;
}
} \ No newline at end of file
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index ec778b346..d07df7e58 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -6,7 +6,7 @@ import "./FilterBox.scss";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { SetupDrag } from '../../util/DragManager';
import { Docs } from '../../documents/Documents';
-import { NumCast } from '../../../new_fields/Types';
+import { NumCast, Cast } from '../../../new_fields/Types';
import { Doc } from '../../../new_fields/Doc';
import { SearchItem } from './SearchItem';
import { DocServer } from '../../DocServer';
@@ -22,7 +22,9 @@ export class SearchBox extends React.Component {
@observable private _searchString: string = "";
@observable private _resultsOpen: boolean = false;
+ @observable private _searchbarOpen: boolean = false;
@observable private _results: Doc[] = [];
+ private _resultsSet = new Set<Doc>();
@observable private _openNoResults: boolean = false;
@observable private _visibleElements: JSX.Element[] = [];
@@ -60,6 +62,7 @@ export class SearchBox extends React.Component {
this._openNoResults = false;
this._results = [];
+ this._resultsSet.clear();
this._visibleElements = [];
this._numTotalResults = -1;
this._endIndex = -1;
@@ -91,8 +94,10 @@ export class SearchBox extends React.Component {
let query = this._searchString;
query = FilterBox.Instance.getFinalQuery(query);
this._results = [];
+ this._resultsSet.clear();
this._isSearch = [];
this._visibleElements = [];
+ FilterBox.Instance.closeFilter();
//if there is no query there should be no result
if (query === "") {
@@ -107,6 +112,7 @@ export class SearchBox extends React.Component {
runInAction(() => {
this._resultsOpen = true;
+ this._searchbarOpen = true;
this._openNoResults = true;
this.resultsScrolled();
});
@@ -118,7 +124,8 @@ export class SearchBox extends React.Component {
private get filterQuery() {
const types = FilterBox.Instance.filterTypes;
- return "proto_i:*" + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : "");
+ const includeDeleted = FilterBox.Instance.getDataStatus();
+ return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : "");
}
@@ -129,15 +136,24 @@ export class SearchBox extends React.Component {
}
this.lockPromise = new Promise(async res => {
while (this._results.length <= this._endIndex && (this._numTotalResults === -1 || this._maxSearchIndex < this._numTotalResults)) {
- this._curRequest = SearchUtil.Search(query, this.filterQuery, true, this._maxSearchIndex, 10).then(action((res: SearchUtil.DocSearchResult) => {
+ this._curRequest = SearchUtil.Search(query, this.filterQuery, true, this._maxSearchIndex, 10).then(action(async (res: SearchUtil.DocSearchResult) => {
// happens at the beginning
if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) {
this._numTotalResults = res.numFound;
}
- let filteredDocs = FilterBox.Instance.filterDocsByType(res.docs);
- this._results.push(...filteredDocs);
+ const docs = await Promise.all(res.docs.map(doc => Cast(doc.extendsDoc, Doc, doc as any)));
+ let filteredDocs = FilterBox.Instance.filterDocsByType(docs);
+ runInAction(() => {
+ // this._results.push(...filteredDocs);
+ filteredDocs.forEach(doc => {
+ if (!this._resultsSet.has(doc)) {
+ this._results.push(doc);
+ this._resultsSet.add(doc);
+ }
+ });
+ });
this._curRequest = undefined;
}));
@@ -198,6 +214,7 @@ export class SearchBox extends React.Component {
this._openNoResults = false;
FilterBox.Instance.closeFilter();
this._resultsOpen = true;
+ this._searchbarOpen = true;
FilterBox.Instance._pointerTime = e.timeStamp;
}
@@ -205,12 +222,14 @@ export class SearchBox extends React.Component {
closeSearch = () => {
FilterBox.Instance.closeFilter();
this.closeResults();
+ this._searchbarOpen = false;
}
@action.bound
closeResults() {
this._resultsOpen = false;
this._results = [];
+ this._resultsSet.clear();
this._visibleElements = [];
this._numTotalResults = -1;
this._endIndex = -1;
@@ -281,15 +300,10 @@ export class SearchBox extends React.Component {
}
@computed
- get resFull() {
- console.log(this._numTotalResults);
- return this._numTotalResults <= 8;
- }
+ get resFull() { return this._numTotalResults <= 8; }
@computed
- get resultHeight() {
- return this._numTotalResults * 70;
- }
+ get resultHeight() { return this._numTotalResults * 70; }
render() {
return (
@@ -300,7 +314,7 @@ export class SearchBox extends React.Component {
</span>
<input value={this._searchString} onChange={this.onChange} type="text" placeholder="Search..."
className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter}
- style={{ width: this._resultsOpen ? "500px" : "100px" }} />
+ style={{ width: this._searchbarOpen ? "500px" : "100px" }} />
<button className="searchBox-barChild searchBox-submit" onClick={this.submitSearch} onPointerDown={FilterBox.Instance.stopProp}>Submit</button>
<button className="searchBox-barChild searchBox-filter" onClick={FilterBox.Instance.openFilter} onPointerDown={FilterBox.Instance.stopProp}>Filter</button>
</div>
diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts
index 59682e51e..ea5388004 100644
--- a/src/server/GarbageCollector.ts
+++ b/src/server/GarbageCollector.ts
@@ -59,7 +59,9 @@ function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
}
}
-async function GarbageCollect() {
+async function GarbageCollect(full: boolean = true) {
+ console.log("start GC");
+ const start = Date.now();
// await new Promise(res => setTimeout(res, 3000));
const cursor = await Database.Instance.query({}, { userDocumentId: 1 }, 'users');
const users = await cursor.toArray();
@@ -68,7 +70,7 @@ async function GarbageCollect() {
const files: { [name: string]: string[] } = {};
while (ids.length) {
- const count = Math.min(ids.length, 100);
+ const count = Math.min(ids.length, 1000);
const index = ids.length - count;
const fetchIds = ids.splice(index, count).filter(id => !visited.has(id));
if (!fetchIds.length) {
@@ -91,43 +93,58 @@ async function GarbageCollect() {
cursor.close();
- const toDeleteCursor = await Database.Instance.query({ _id: { $nin: Array.from(visited) } }, { _id: 1 });
+ const notToDelete = Array.from(visited);
+ const toDeleteCursor = await Database.Instance.query({ _id: { $nin: notToDelete } }, { _id: 1 });
const toDelete: string[] = (await toDeleteCursor.toArray()).map(doc => doc._id);
toDeleteCursor.close();
- let i = 0;
- let deleted = 0;
- while (i < toDelete.length) {
- const count = Math.min(toDelete.length, 5000);
- const toDeleteDocs = toDelete.slice(i, i + count);
- i += count;
- const result = await Database.Instance.delete({ _id: { $in: toDeleteDocs } }, "newDocuments");
- deleted += result.deletedCount || 0;
- }
- // const result = await Database.Instance.delete({ _id: { $in: toDelete } }, "newDocuments");
- console.log(`${deleted} documents deleted`);
+ 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(
+ notToDelete.map<any>(id => ({
+ id, deleted: { set: null }
+ }))
+ .concat(toDelete.map(id => ({
+ id, deleted: { set: true }
+ })))));
+ console.log("Done with partial GC");
+ console.log(`Took ${(Date.now() - start) / 1000} seconds`);
+ } else {
+ let i = 0;
+ let deleted = 0;
+ while (i < toDelete.length) {
+ const count = Math.min(toDelete.length, 5000);
+ const toDeleteDocs = toDelete.slice(i, i + count);
+ i += count;
+ const result = await Database.Instance.delete({ _id: { $in: toDeleteDocs } }, "newDocuments");
+ deleted += result.deletedCount || 0;
+ }
+ // const result = await Database.Instance.delete({ _id: { $in: toDelete } }, "newDocuments");
+ console.log(`${deleted} documents deleted`);
- await Search.Instance.deleteDocuments(toDelete);
- console.log("Cleared search documents");
+ await Search.Instance.deleteDocuments(toDelete);
+ console.log("Cleared search documents");
- const folder = "./src/server/public/files/";
- fs.readdir(folder, (_, fileList) => {
- const filesToDelete = fileList.filter(file => {
- const ext = path.extname(file);
- let base = path.basename(file, ext);
- const existsInDb = (base in files || (base = base.substring(0, base.length - 2)) in files) && files[base].includes(ext);
- return file !== ".gitignore" && !existsInDb;
- });
- console.log(`Deleting ${filesToDelete.length} files`);
- filesToDelete.forEach(file => {
- console.log(`Deleting file ${file}`);
- try {
- fs.unlinkSync(folder + file);
- } catch {
- console.warn(`Couldn't delete file ${file}`);
- }
+ const folder = "./src/server/public/files/";
+ fs.readdir(folder, (_, fileList) => {
+ const filesToDelete = fileList.filter(file => {
+ const ext = path.extname(file);
+ let base = path.basename(file, ext);
+ const existsInDb = (base in files || (base = base.substring(0, base.length - 2)) in files) && files[base].includes(ext);
+ return file !== ".gitignore" && !existsInDb;
+ });
+ console.log(`Deleting ${filesToDelete.length} files`);
+ filesToDelete.forEach(file => {
+ console.log(`Deleting file ${file}`);
+ try {
+ fs.unlinkSync(folder + file);
+ } catch {
+ console.warn(`Couldn't delete file ${file}`);
+ }
+ });
+ console.log(`Deleted ${filesToDelete.length} files`);
});
- console.log(`Deleted ${filesToDelete.length} files`);
- });
+ }
}
-GarbageCollect();
+GarbageCollect(false);
diff --git a/src/server/authentication/controllers/user_controller.ts b/src/server/authentication/controllers/user_controller.ts
index fa1cd647d..0e431f1e6 100644
--- a/src/server/authentication/controllers/user_controller.ts
+++ b/src/server/authentication/controllers/user_controller.ts
@@ -52,7 +52,7 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => {
const password = req.body.password;
const model = {
- email: { type: email, unique: true },
+ email,
password,
userDocumentId: Utils.GenerateGuid()
} as Partial<DashUserModel>;
@@ -186,7 +186,7 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio
}
});
const mailOptions = {
- to: user.email.type,
+ to: user.email,
from: 'brownptcdash@gmail.com',
subject: 'Dash Password Reset',
text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' +
@@ -259,7 +259,7 @@ export let postReset = function (req: Request, res: Response) {
}
});
const mailOptions = {
- to: user.email.type,
+ to: user.email,
from: 'brownptcdash@gmail.com',
subject: 'Your password has been changed',
text: 'Hello,\n\n' +
diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/models/user_model.ts
index fb62de1c8..45fbf23b1 100644
--- a/src/server/authentication/models/user_model.ts
+++ b/src/server/authentication/models/user_model.ts
@@ -16,7 +16,7 @@ mongoose.connection.on('disconnected', function () {
console.log('connection closed');
});
export type DashUserModel = mongoose.Document & {
- email: { type: String, unique: true },
+ email: String,
password: string,
passwordResetToken?: string,
passwordResetExpires?: Date,
@@ -42,7 +42,7 @@ export type AuthToken = {
};
const userSchema = new mongoose.Schema({
- email: { type: String, unique: true },
+ email: String,
password: String,
passwordResetToken: String,
passwordResetExpires: Date,
diff --git a/src/server/index.ts b/src/server/index.ts
index 37eabf4c8..9c0ec13c4 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -110,7 +110,7 @@ function addSecureRoute(method: Method,
if (req.user) {
handler(req.user, res, req);
} else {
- req.session!.target = `http://localhost:${port}${req.originalUrl}`;
+ req.session!.target = `${req.headers.host}${req.originalUrl}`;
onRejection(res, req);
}
};