diff options
author | tschicke-brown <tyler_schicke@brown.edu> | 2019-07-16 14:38:34 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-07-16 14:38:34 -0400 |
commit | 98c3a06256d2cbd720b2c193e5aa282c5dc25350 (patch) | |
tree | 5bb7e5a0fa44183bd3824ce94971d2715631a445 /src | |
parent | c6b933c6e89d5f438c434e8676cce9a6a577edbc (diff) | |
parent | 520fbe435330c8e426e2d504585ba1559300eadb (diff) |
Merge branch 'master' into search_virt
Diffstat (limited to 'src')
-rw-r--r-- | src/client/documents/Documents.ts | 4 | ||||
-rw-r--r-- | src/client/util/TooltipTextMenu.tsx | 1 | ||||
-rw-r--r-- | src/client/views/Main.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/CollectionDockingView.tsx | 4 | ||||
-rw-r--r-- | src/client/views/collections/CollectionTreeView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/CollectionVideoView.tsx | 4 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 16 | ||||
-rw-r--r-- | src/client/views/nodes/LinkMenuItem.tsx | 5 | ||||
-rw-r--r-- | src/client/views/nodes/VideoBox.tsx | 134 | ||||
-rw-r--r-- | src/client/views/nodes/WebBox.tsx | 2 | ||||
-rw-r--r-- | src/client/views/search/SearchBox.tsx | 13 | ||||
-rw-r--r-- | src/new_fields/Doc.ts | 6 | ||||
-rw-r--r-- | src/server/authentication/controllers/user_controller.ts | 19 | ||||
-rw-r--r-- | src/server/authentication/models/user_model.ts | 4 | ||||
-rw-r--r-- | src/server/index.ts | 4 |
16 files changed, 123 insertions, 99 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ada9f3610..5bbfe1c49 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -98,7 +98,7 @@ export namespace Docs { type LayoutSource = { LayoutString: () => string }; type CollectionLayoutSource = { LayoutString: (fieldStr: string, fieldExt?: string) => string }; - type CollectionViewType = [CollectionLayoutSource, string, string?] + type CollectionViewType = [CollectionLayoutSource, string, string?]; type PrototypeTemplate = { layout: { view: LayoutSource, @@ -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/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index fe7e0b97f..3e804651c 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -239,6 +239,7 @@ export class TooltipTextMenu { this.linkDrag.onpointerdown = (e: PointerEvent) => { let dragData = new DragManager.LinkDragData(this.editorProps.Document); dragData.dontClearTextBox = true; + e.stopPropagation(); DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY, { handlers: { diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 80399e24b..86578af3e 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -26,7 +26,7 @@ let swapDocs = async () => { } CurrentUserUtils.UserDocument.linkManagerDoc = undefined; } -} +}; (async () => { const info = await CurrentUserUtils.loadCurrentUser(); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 1069ebbdb..a193ff677 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -301,7 +301,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp this._flush = false; setTimeout(() => { CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig()); - this.stateChanged() + this.stateChanged(); }, 10); } } @@ -606,7 +606,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { addDocTab={this.addDocTab} ContainingCollectionView={undefined} zoomToScale={emptyFunction} - getScale={returnOne} /> + getScale={returnOne} />; } @computed get content() { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 0196fecff..c212cc97c 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -153,7 +153,7 @@ class TreeView extends React.Component<TreeViewProps> { let docList = Cast(this.resolvedDataDoc[this.fieldKey], listSpec(Doc)); let doc = Cast(this.resolvedDataDoc[this.fieldKey], Doc); let isDoc = doc instanceof Doc || docList; - let c + let c; return <div className="bullet" onClick={action(() => this._collapsed = !this._collapsed)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}> {<FontAwesomeIcon icon={this._collapsed ? (isDoc ? "caret-square-right" : "caret-right") : (isDoc ? "caret-square-down" : "caret-down")} />} </div>; diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx index f731c4cef..096e7e9d1 100644 --- a/src/client/views/collections/CollectionVideoView.tsx +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -88,14 +88,14 @@ export class CollectionVideoView extends React.Component<FieldViewProps> { 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 - ctx && ctx.drawImage(this._videoBox!.player!, 0, 0, canvas.width, canvas.height); + 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) => { + SearchBox.convertDataUri(dataUrl, filename).then(returnedFilename => { if (returnedFilename) { let url = DocServer.prepend(returnedFilename); let imageSummary = Docs.Create.ImageDocument(url, { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index a4a6881f8..b765517a2 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -147,7 +147,6 @@ export class MarqueeView extends React.Component<MarqueeViewProps> PreviewCursor.Visible = false; this.cleanupInteractions(true); if (e.button === 2 || (e.button === 0 && e.altKey)) { - if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]); document.addEventListener("pointermove", this.onPointerMove, true); document.addEventListener("pointerup", this.onPointerUp, true); document.addEventListener("keydown", this.marqueeCommand, true); @@ -181,6 +180,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> @action onPointerUp = (e: PointerEvent): void => { + if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]); // console.log("pointer up!"); if (this._visible) { // console.log("visible"); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 73ae8955d..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'); @@ -33,7 +34,7 @@ export const pageSchema = createSchema({ curPage: "number", }); -interface window { +interface Window { MediaRecorder: MediaRecorder; } @@ -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/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx index 9728671c0..6a18a4e7b 100644 --- a/src/client/views/nodes/LinkMenuItem.tsx +++ b/src/client/views/nodes/LinkMenuItem.tsx @@ -7,7 +7,7 @@ import { undoBatch } from "../../util/UndoManager"; import './LinkMenu.scss'; import React = require("react"); import { Doc } from '../../../new_fields/Doc'; -import { StrCast, Cast, BoolCast, FieldValue } from '../../../new_fields/Types'; +import { StrCast, Cast, BoolCast, FieldValue, NumCast } from '../../../new_fields/Types'; import { observable, action } from 'mobx'; import { LinkManager } from '../../util/LinkManager'; import { DragLinkAsDocument } from '../../util/DragManager'; @@ -38,7 +38,8 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> { jumpToDoc = pdfDoc; } if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey); + let self = this; + DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page))); } else { CollectionDockingView.Instance.AddRightSplit(jumpToDoc, undefined); } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 66844cdd6..972e6875d 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -1,5 +1,5 @@ import React = require("react"); -import { action, IReactionDisposer, observable, reaction, trace, computed } from "mobx"; +import { action, IReactionDisposer, observable, reaction, trace, computed, runInAction, untracked } from "mobx"; import { observer } from "mobx-react"; import { makeInterface } from "../../../new_fields/Schema"; import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; @@ -24,6 +24,11 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD private _youtubeReactionDisposer?: IReactionDisposer; private _youtubePlayer: any = undefined; private _videoRef: HTMLVideoElement | null = null; + private _youtubeIframeId: number = -1; + private _youtubeContentCreated = false; + static _youtubeIframeCounter: number = 0; + @observable _forceCreateYouTubeIFrame = false; + @observable static _showControls: boolean; @observable _playTimer?: NodeJS.Timeout = undefined; @observable _fullScreen = false; @observable public Playing: boolean = false; @@ -44,29 +49,29 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD } } - @action public Play = () => { + @action public Play = (update: boolean = true) => { this.Playing = true; - this.player && this.player.play(); - this._youtubePlayer && this._youtubePlayer.playVideo(); + 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._youtubeSeekTo = false; this.updateTimecode(); } @action public Seek(time: number) { - if (this._youtubePlayer && !this.Playing) { - this._youtubeSeekTo = true; - this._youtubePlayer.seekTo(time); - } + console.log("Seeking " + time); + //if (this._youtubePlayer && this._youtubePlayer.getPlayerState() === 5) return; + this._youtubePlayer && this._youtubePlayer.seekTo(Math.round(time), true); } - @action public Pause = () => { + @action public Pause = (update: boolean = true) => { this.Playing = false; - this.player && this.player.pause(); - this._youtubePlayer && this._youtubePlayer.pauseVideo(); + console.log("PAUSING = " + update); + update && this.player && this.player.pause(); + update && this._youtubePlayer && this._youtubePlayer.pauseVideo(); this._playTimer && clearInterval(this._playTimer); this._playTimer = undefined; - this._youtubeSeekTo = false; + this.updateTimecode(); } @action public FullScreen() { @@ -80,12 +85,11 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD this.player && (this.props.Document.curPage = this.player.currentTime); this._youtubePlayer && (this.props.Document.curPage = this._youtubePlayer.getCurrentTime()); } + componentDidMount() { if (this.props.setVideoBox) this.props.setVideoBox(this); - let field = Cast(this.Document[this.props.fieldKey], VideoField); - let videoid = field && field.url.href.indexOf("youtube") !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split("/")) : ""; - if (videoid) { + if (this.youtubeVideoId) { let youtubeaspect = 400 / 315; var nativeWidth = FieldValue(this.Document.nativeWidth, 0); var nativeHeight = FieldValue(this.Document.nativeHeight, 0); @@ -94,47 +98,6 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD this.Document.nativeHeight = this.Document.nativeWidth / youtubeaspect; this.Document.height = FieldValue(this.Document.width, 0) / youtubeaspect; } - this._youtubePlayer = new YT.Player(`${videoid}-player`, { - height: `${NumCast(this.props.Document.height)}`, - width: `${NumCast(this.props.Document.width)}`, - videoId: videoid.toString(), - playerVars: { 'controls': VideoBox._showControls ? 1 : 0 }, - events: { - 'onStateChange': this.onYoutubePlayerStateChange, - 'onReady': this.onYoutubePlayerReady, - } - }); - this._reactionDisposer = reaction(() => this.props.Document.curPage, () => this.Seek(this.Document.curPage || 0), { fireImmediately: true }); - this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => { - let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected() && !DocumentDecorations.Instance.Interacting; - this._youtubePlayer.getIframe().style.pointerEvents = interactive ? "all" : "none"; - }, { fireImmediately: true }) - // let iframe = $(document.getElementById(`${videoid}-player`)!); - // iframe.on("load", function () { - // iframe.contents().find("head") - // .append($("<style type='text/css'> .ytp-pause-overlay, .ytp-scroll-min { opacity : 0 !important; } </style>")); - // }) - } - } - - @action - onYoutubePlayerStateChange = (event: any) => { - console.log("event.data = " + event.data); - this.Playing = event.data == YT.PlayerState.PLAYING; - if (this._youtubeSeekTo && this.Playing) { - this._youtubePlayer.pauseVideo(); - this._youtubeSeekTo = false; - } else this.Playing && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 500)); - event.data === YT.PlayerState.PAUSED && this._playTimer && clearInterval(this._playTimer); - } - _youtubeSeekTo = false; - @action - onYoutubePlayerReady = (event: any) => { - this.Playing = false; - this._youtubePlayer && (this._youtubePlayer.getIframe().style.pointerEvents = "none"); - if (this.Document.curPage) { - this.Seek(this.Document.curPage); - this._youtubeSeekTo = true; } } @@ -156,18 +119,13 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD } } - @observable static _showControls: boolean; - - @computed get youtubeVideoId() { - let field = Cast(this.Document[this.props.fieldKey], VideoField); - return field && field.url.href.indexOf("youtube") !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split("/")) : ""; - } - specificContextMenu = (e: React.MouseEvent): void => { let field = Cast(this.Document[this.props.fieldKey], VideoField); if (field) { let subitems: ContextMenuProps[] = []; 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" }); ContextMenu.Instance.addItem({ description: "Video Funcs...", subitems: subitems }); } } @@ -177,15 +135,59 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD let interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive"; let style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive; return !field ? <div>Loading</div> : - <video className={`${style}`} ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls} onPlay={this.Play} onSeeked={this.updateTimecode} onPause={this.Pause}> + <video className={`${style}`} ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls} + onPlay={() => this.Play()} onSeeked={this.updateTimecode} onPause={() => this.Pause()}> <source src={field.url.href} type="video/mp4" /> Not supported. </video>; } + @computed get youtubeVideoId() { + let field = Cast(this.Document[this.props.fieldKey], VideoField); + return field && field.url.href.indexOf("youtube") !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split("/")) : ""; + } + + @action youtubeIframeLoaded = (e: any) => { + if (!this._youtubeContentCreated) { + this._forceCreateYouTubeIFrame = !this._forceCreateYouTubeIFrame; + return; + } + else this._youtubeContentCreated = false; + + let iframe = e.target; + 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); + }); + 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)); + this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => { + 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, + 'onStateChange': onYoutubePlayerStateChange, + } + }); + + } + @computed get youtubeContent() { + this._youtubeIframeId = VideoBox._youtubeIframeCounter++; + this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true; let style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : ""); - return <div id={`${this.youtubeVideoId}-player`} className={`${style}`} />; + 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}`} + ></iframe>; } render() { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 96b972a1c..f0a9ec6d8 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -19,7 +19,7 @@ export function onYouTubeIframeAPIReady() { }); } // must cast as any to set property on window -const _global = (window /* browser */ || global /* node */) as any +const _global = (window /* browser */ || global /* node */) as any; _global.onYouTubeIframeAPIReady = onYouTubeIframeAPIReady; function onPlayerReady(event: any) { diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 661c9c10b..ba68ccd54 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'; @@ -120,7 +120,7 @@ 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(" ")})` : ""); + return "NOT baseProto_b:true" + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : ""); } @@ -131,15 +131,18 @@ 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); + }); this._curRequest = undefined; })); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index c5f9e7adf..0d9fa540f 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -312,16 +312,20 @@ export namespace Doc { } export function UpdateDocumentExtensionForField(doc: Doc, fieldKey: string) { - if (doc[fieldKey + "_ext"] === undefined) { + let extensionDoc = doc[fieldKey + "_ext"]; + if (extensionDoc === undefined) { setTimeout(() => { let docExtensionForField = new Doc(doc[Id] + fieldKey, true); docExtensionForField.title = "Extension of " + doc.title + "'s field:" + fieldKey; + docExtensionForField.extendsDoc = doc; let proto: Doc | undefined = doc; while (proto && !Doc.IsPrototype(proto)) { proto = proto.proto; } (proto ? proto : doc)[fieldKey + "_ext"] = docExtensionForField; }, 0); + } else if (extensionDoc instanceof Doc && extensionDoc.extendsDoc === undefined) { + setTimeout(() => (extensionDoc as Doc).extendsDoc = doc, 0); } } export function MakeAlias(doc: Doc) { diff --git a/src/server/authentication/controllers/user_controller.ts b/src/server/authentication/controllers/user_controller.ts index ca4fc171c..0e431f1e6 100644 --- a/src/server/authentication/controllers/user_controller.ts +++ b/src/server/authentication/controllers/user_controller.ts @@ -12,6 +12,9 @@ import * as nodemailer from 'nodemailer'; import c = require("crypto"); import { RouteStore } from "../../RouteStore"; import { Utils } from "../../../Utils"; +import { Schema } from "mongoose"; +import { Opt } from "../../../new_fields/Doc"; +import { MailOptions } from "nodemailer/lib/stream-transport"; /** * GET /signup @@ -45,21 +48,23 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => { return res.redirect(RouteStore.signup); } - const email = req.body.email; + const email = req.body.email as String; const password = req.body.password; - const user = new User({ + const model = { email, password, userDocumentId: Utils.GenerateGuid() - }); + } as Partial<DashUserModel>; + + const user = new User(model); User.findOne({ email }, (err, existingUser) => { if (err) { return next(err); } if (existingUser) { return res.redirect(RouteStore.login); } - user.save((err) => { + user.save((err: any) => { if (err) { return next(err); } req.logIn(user, (err) => { if (err) { return next(err); } @@ -188,8 +193,8 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio 'Please click on the following link, or paste this into your browser to complete the process:\n\n' + 'http://' + req.headers.host + '/reset/' + token + '\n\n' + 'If you did not request this, please ignore this email and your password will remain unchanged.\n' - }; - smtpTransport.sendMail(mailOptions, function (err) { + } as MailOptions; + smtpTransport.sendMail(mailOptions, function (err: Error | null) { // req.flash('info', 'An e-mail has been sent to ' + user.email + ' with further instructions.'); done(null, err, 'done'); }); @@ -259,7 +264,7 @@ export let postReset = function (req: Request, res: Response) { subject: 'Your password has been changed', text: 'Hello,\n\n' + 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n' - }; + } as MailOptions; smtpTransport.sendMail(mailOptions, function (err) { done(null, err); }); diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/models/user_model.ts index ee85e1c05..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: string, + 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 1c0dec05b..2cca7a35b 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -58,7 +58,7 @@ clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode" fs.writeFileSync("./src/client/util/ClientUtils.ts", clientUtils, "utf8"); const mongoUrl = 'mongodb://localhost:27017/Dash'; -mongoose.connect(mongoUrl); +mongoose.connection.readyState === 0 && mongoose.connect(mongoUrl); mongoose.connection.on('connected', () => console.log("connected")); // SESSION MANAGEMENT AND AUTHENTICATION MIDDLEWARE @@ -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); } }; |