diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/goldenLayout.js | 1 | ||||
-rw-r--r-- | src/client/util/SearchUtil.ts | 25 | ||||
-rw-r--r-- | src/client/views/collections/CollectionDockingView.tsx | 18 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSubView.tsx | 6 | ||||
-rw-r--r-- | src/client/views/collections/ParentDocumentSelector.scss | 8 | ||||
-rw-r--r-- | src/client/views/collections/ParentDocumentSelector.tsx | 66 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 27 | ||||
-rw-r--r-- | src/new_fields/Doc.ts | 4 | ||||
-rw-r--r-- | src/server/Search.ts | 3 |
9 files changed, 144 insertions, 14 deletions
diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js index 56a71f1ac..ab2bcefed 100644 --- a/src/client/goldenLayout.js +++ b/src/client/goldenLayout.js @@ -2902,6 +2902,7 @@ * @returns {void} */ _$destroy: function () { + this._layoutManager.emit('tabDestroyed', this); this.element.off('mousedown touchstart', this._onTabClickFn); this.closeElement.off('click touchstart', this._onCloseClickFn); if (this._dragListener) { diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts new file mode 100644 index 000000000..4ccff0d1b --- /dev/null +++ b/src/client/util/SearchUtil.ts @@ -0,0 +1,25 @@ +import * as rp from 'request-promise'; +import { DocServer } from '../DocServer'; +import { Doc } from '../../new_fields/Doc'; +import { Id } from '../../new_fields/RefField'; + +export namespace SearchUtil { + export function Search(query: string, returnDocs: true): Promise<Doc[]>; + export function Search(query: string, returnDocs: false): Promise<string[]>; + export async function Search(query: string, returnDocs: boolean) { + const ids = JSON.parse(await rp.get(DocServer.prepend("/search"), { + qs: { query } + })); + if (!returnDocs) { + return ids; + } + const docMap = await DocServer.GetRefFields(ids); + return ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc); + } + + export async function GetAliasesOfDocument(doc: Doc): Promise<Doc[]> { + const proto = await Doc.GetT(doc, "proto", Doc, true); + const protoId = (proto || doc)[Id]; + return Search(`{!join from=id to=proto_i}id:${protoId}`, true); + } +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index efcee9c02..8739a213f 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -18,6 +18,7 @@ import { DocumentView } from "../nodes/DocumentView"; import "./CollectionDockingView.scss"; import { SubCollectionViewProps } from "./CollectionSubView"; import React = require("react"); +import { ParentDocSelector } from './ParentDocumentSelector'; @observer export class CollectionDockingView extends React.Component<SubCollectionViewProps> { @@ -157,6 +158,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp try { this._goldenLayout.unbind('itemDropped', this.itemDropped); this._goldenLayout.unbind('tabCreated', this.tabCreated); + this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); this._goldenLayout.unbind('stackCreated', this.stackCreated); } catch (e) { } this._goldenLayout.destroy(); @@ -164,6 +166,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp } this._goldenLayout.on('itemDropped', this.itemDropped); this._goldenLayout.on('tabCreated', this.tabCreated); + this._goldenLayout.on('tabDestroyed', this.tabDestroyed); this._goldenLayout.on('stackCreated', this.stackCreated); this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer); this._goldenLayout.container = this._containerRef.current; @@ -196,6 +199,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp this._goldenLayout.unbind('itemDropped', this.itemDropped); this._goldenLayout.unbind('tabCreated', this.tabCreated); this._goldenLayout.unbind('stackCreated', this.stackCreated); + this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); } catch (e) { } @@ -292,8 +296,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp } DocServer.GetRefField(tab.contentItem.config.props.documentId).then(async doc => { if (doc instanceof Doc) { - let counter: any = this.htmlToElement(`<div class="messageCounter">0</div>`); + let counter: any = this.htmlToElement(`<span class="messageCounter">0</div>`); tab.element.append(counter); + let upDiv = document.createElement("span"); + ReactDOM.render(<ParentDocSelector Document={doc} />, upDiv); + tab.reactComponents = [upDiv]; + tab.element.append(upDiv); counter.DashDocId = tab.contentItem.config.props.documentId; tab.reactionDisposer = reaction(() => [doc.linkedFromDocs, doc.LinkedToDocs, doc.title], () => { @@ -320,6 +328,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp tab.contentItem.remove(); }); } + + tabDestroyed = (tab: any) => { + if (tab.reactComponents) { + for (const ele of tab.reactComponents) { + ReactDOM.unmountComponentAtNode(ele); + } + } + } _removedDocs: Doc[] = []; stackCreated = (stack: any) => { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index a86d250bd..ffd3e0659 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -18,6 +18,7 @@ import { List } from "../../../new_fields/List"; import { DocServer } from "../../DocServer"; import { ObjectField } from "../../../new_fields/ObjectField"; import CursorField, { CursorPosition, CursorMetadata } from "../../../new_fields/CursorField"; +import { url } from "inspector"; export interface CollectionViewProps extends FieldViewProps { addDocument: (document: Doc, allowDuplicates?: boolean) => boolean; @@ -168,6 +169,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { this.props.addDocument(htmlDoc, false); return; } + if (text && text.indexOf("www.youtube.com/watch") !== -1) { + const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/"); + this.props.addDocument(Docs.WebDocument(url, { ...options, width: 300, height: 300 })); + return; + } let batch = UndoManager.StartBatch("collection view drop"); let promises: Promise<void>[] = []; diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss new file mode 100644 index 000000000..f3c605f3e --- /dev/null +++ b/src/client/views/collections/ParentDocumentSelector.scss @@ -0,0 +1,8 @@ +.PDS-flyout { + position: absolute; + z-index: 9999; + background-color: #d3d3d3; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + min-width: 150px; + color: black; +}
\ No newline at end of file diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx new file mode 100644 index 000000000..dd1516da7 --- /dev/null +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -0,0 +1,66 @@ +import * as React from "react"; +import './ParentDocumentSelector.scss'; +import { Doc } from "../../../new_fields/Doc"; +import { observer } from "mobx-react"; +import { observable, action, runInAction } from "mobx"; +import { Id } from "../../../new_fields/RefField"; +import { SearchUtil } from "../../util/SearchUtil"; +import { CollectionDockingView } from "./CollectionDockingView"; + +@observer +export class SelectorContextMenu extends React.Component<{ Document: Doc }> { + @observable private _docs: Doc[] = []; + + constructor(props: { Document: Doc }) { + super(props); + + this.fetchDocuments(); + } + + async fetchDocuments() { + const docs = await SearchUtil.Search(`data_l:${this.props.Document[Id]}`, true); + runInAction(() => this._docs = docs); + } + + render() { + return ( + <> + {this._docs.map(doc => <p><a onClick={() => CollectionDockingView.Instance.AddRightSplit(doc)}>{doc.title}</a></p>)} + </> + ); + } +} + +@observer +export class ParentDocSelector extends React.Component<{ Document: Doc }> { + @observable hover = false; + + @action + onMouseLeave = () => { + this.hover = false; + } + + @action + onMouseEnter = () => { + this.hover = true; + } + + render() { + let flyout; + if (this.hover) { + flyout = ( + <div className="PDS-flyout"> + <SelectorContextMenu Document={this.props.Document} /> + </div> + ); + } + return ( + <span style={{ position: "relative", display: "inline-block" }} + onMouseEnter={this.onMouseEnter} + onMouseLeave={this.onMouseLeave}> + <p>^</p> + {flyout} + </span> + ); + } +} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 90f67db7c..edc2158f0 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -26,6 +26,7 @@ import { CurrentUserUtils } from "../../../server/authentication/models/current_ import { DocServer } from "../../DocServer"; import { Id } from "../../../new_fields/RefField"; import { PresentationView } from "../PresentationView"; +import { SearchUtil } from "../../util/SearchUtil"; const linkSchema = createSchema({ title: "string", @@ -287,16 +288,22 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } e.preventDefault(); - ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }); - ContextMenu.Instance.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton }); - ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked }); - ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) }); - ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) }); - ContextMenu.Instance.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])) }); - ContextMenu.Instance.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]) }); - //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) }) - ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document) }); - ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked }); + const cm = ContextMenu.Instance; + cm.addItem({ description: "Full Screen", event: this.fullScreenClicked }); + cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton }); + cm.addItem({ description: "Fields", event: this.fieldsClicked }); + cm.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) }); + cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) }); + cm.addItem({ + description: "Find aliases", event: async () => { + const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document); + CollectionDockingView.Instance.AddRightSplit(Docs.SchemaDocument(aliases, {})); + } + }); + cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])) }); + cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]) }); + cm.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document) }); + cm.addItem({ description: "Delete", event: this.deleteClicked }); if (!this.topMost) { // DocumentViews should stop propagation of this event e.stopPropagation(); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 42d04e93f..625ba0d6a 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -130,8 +130,8 @@ export namespace Doc { const self = doc[Self]; return getField(self, key, ignoreProto); } - export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): T | null | undefined { - return Cast(Get(doc, key, ignoreProto), ctor) as T | null | undefined; + export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> { + return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>; } export async function SetOnPrototype(doc: Doc, key: string, value: Field) { const proto = doc.proto; diff --git a/src/server/Search.ts b/src/server/Search.ts index 0f7004bdf..5ca5578a7 100644 --- a/src/server/Search.ts +++ b/src/server/Search.ts @@ -22,7 +22,8 @@ export class Search { try { const searchResults = JSON.parse(await rp.get(this.url + "dash/select", { qs: { - q: query + q: query, + fl: "id" } })); const fields = searchResults.response.docs; |