From 57d4c562b6f0ef39dcd68f2985a56c4a683fcf49 Mon Sep 17 00:00:00 2001 From: geireann <60007097+geireann@users.noreply.github.com> Date: Thu, 18 Jun 2020 04:38:08 +0800 Subject: pdf changes + radial menu + create new workspaces (bug with ink) --- src/server/ApiManagers/PDFManager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/server/ApiManagers') diff --git a/src/server/ApiManagers/PDFManager.ts b/src/server/ApiManagers/PDFManager.ts index d2a9e9cce..b0b5a484a 100644 --- a/src/server/ApiManagers/PDFManager.ts +++ b/src/server/ApiManagers/PDFManager.ts @@ -55,7 +55,8 @@ async function CreateThumbnail(coreFilename: string, pageNum: number, res: expre const documentProxy = await Pdfjs.getDocument(sourcePath).promise; const factory = new NodeCanvasFactory(); const page = await documentProxy.getPage(pageNum); - const viewport = page.getViewport(1 as any); + const scale = 1; + const viewport = page.getViewport(scale as any); const { canvas, context } = factory.create(viewport.width, viewport.height); const renderContext = { canvasContext: context, -- cgit v1.2.3-70-g09d2 From 0438137cd435c47ce334b15a4ad00cbd70d80662 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 7 Jul 2020 00:23:24 -0400 Subject: made docFilters apply to links. added hypothesis api token input --- .../apis/HypothesisAuthenticationManager.scss | 26 ++++ .../apis/HypothesisAuthenticationManager.tsx | 158 +++++++++++++++++++++ src/client/documents/Documents.ts | 41 ++++++ src/client/util/CurrentUserUtils.ts | 1 + src/client/views/GlobalKeyHandler.ts | 2 + src/client/views/MainView.tsx | 2 + src/client/views/collections/CollectionSubView.tsx | 43 +----- src/client/views/nodes/DocumentView.tsx | 2 +- src/server/ApiManagers/HypothesisManager.ts | 44 ++++++ src/server/ApiManagers/UploadManager.ts | 3 +- src/server/apis/google/GoogleApiServerUtils.ts | 3 +- src/server/index.ts | 3 +- 12 files changed, 285 insertions(+), 43 deletions(-) create mode 100644 src/client/apis/HypothesisAuthenticationManager.scss create mode 100644 src/client/apis/HypothesisAuthenticationManager.tsx create mode 100644 src/server/ApiManagers/HypothesisManager.ts (limited to 'src/server/ApiManagers') diff --git a/src/client/apis/HypothesisAuthenticationManager.scss b/src/client/apis/HypothesisAuthenticationManager.scss new file mode 100644 index 000000000..bd30dd94f --- /dev/null +++ b/src/client/apis/HypothesisAuthenticationManager.scss @@ -0,0 +1,26 @@ +.authorize-container { + display: flex; + flex-direction: column; + align-items: center; + + .paste-target { + padding: 5px; + width: 100%; + } + + .avatar { + border-radius: 50%; + } + + .welcome { + font-style: italic; + margin-top: 15px; + } + + .disconnect { + font-size: 10px; + margin-top: 20px; + color: red; + cursor: grab; + } +} \ No newline at end of file diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx new file mode 100644 index 000000000..cffb87227 --- /dev/null +++ b/src/client/apis/HypothesisAuthenticationManager.tsx @@ -0,0 +1,158 @@ +import { observable, action, reaction, runInAction, IReactionDisposer } from "mobx"; +import { observer } from "mobx-react"; +import * as React from "react"; +import MainViewModal from "../views/MainViewModal"; +import { Opt } from "../../fields/Doc"; +import { Networking } from "../Network"; +import "./HypothesisAuthenticationManager.scss"; +import { Scripting } from "../util/Scripting"; + +const prompt = "Paste authorization code here..."; + +@observer +export default class HypothesisAuthenticationManager extends React.Component<{}> { + public static Instance: HypothesisAuthenticationManager; + private authenticationLink: Opt = undefined; + @observable private openState = false; + @observable private authenticationCode: Opt = undefined; + @observable private showPasteTargetState = false; + @observable private success: Opt = undefined; + @observable private displayLauncher = true; + @observable private credentials: string; + private disposer: Opt; + + private set isOpen(value: boolean) { + runInAction(() => this.openState = value); + } + + private set shouldShowPasteTarget(value: boolean) { + runInAction(() => this.showPasteTargetState = value); + } + + public cancel() { + this.openState && this.resetState(0, 0); + } + + public fetchAccessToken = async (displayIfFound = false) => { + let response: any = await Networking.FetchFromServer("/readHypothesisAccessToken"); + // if this is an authentication url, activate the UI to register the new access token + if (!response) { // new RegExp(AuthenticationUrl).test(response)) { + this.isOpen = true; + this.authenticationLink = response; + return new Promise(async resolve => { + this.disposer?.(); + this.disposer = reaction( + () => this.authenticationCode, + async authenticationCode => { + if (authenticationCode) { + this.disposer?.(); + Networking.PostToServer("/writeHypothesisAccessToken", { authenticationCode }); + runInAction(() => { + this.success = true; + this.credentials = response; + }); + this.resetState(); + resolve(authenticationCode); + } + } + ); + }); + } + + if (displayIfFound) { + runInAction(() => { + this.success = true; + this.credentials = response; + }); + this.resetState(-1, -1); + this.isOpen = true; + } + return response.access_token; + } + + resetState = action((visibleForMS: number = 3000, fadesOutInMS: number = 500) => { + if (!visibleForMS && !fadesOutInMS) { + runInAction(() => { + this.isOpen = false; + this.success = undefined; + this.displayLauncher = true; + this.credentials = ""; + this.shouldShowPasteTarget = false; + this.authenticationCode = undefined; + }); + return; + } + this.authenticationCode = undefined; + this.displayLauncher = false; + this.shouldShowPasteTarget = false; + if (visibleForMS > 0 && fadesOutInMS > 0) { + setTimeout(action(() => { + this.isOpen = false; + setTimeout(action(() => { + this.success = undefined; + this.displayLauncher = true; + this.credentials = ""; + }), fadesOutInMS); + }), visibleForMS); + } + }); + + constructor(props: {}) { + super(props); + HypothesisAuthenticationManager.Instance = this; + } + + private get renderPrompt() { + return ( +
+ + {this.displayLauncher ? : (null)} + {this.showPasteTargetState ? this.authenticationCode = e.currentTarget.value)} + placeholder={prompt} + /> : (null)} + {this.credentials ? + <> + Welcome to Dash, {this.credentials} + +
{ + await Networking.FetchFromServer("/revokeHypothesisAccessToken"); + this.resetState(0, 0); + }} + >Disconnect Account
+ : (null)} +
+ ); + } + + private get dialogueBoxStyle() { + const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red"; + return { borderColor, transition: "0.2s borderColor ease" }; + } + + render() { + return ( + + ); + } + +} + +Scripting.addGlobal("HypothesisAuthenticationManager", HypothesisAuthenticationManager); \ No newline at end of file diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f81c25bab..3355c0091 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -829,6 +829,47 @@ export namespace Docs { } export namespace DocUtils { + export function FilterDocs(docs: Doc[], docFilters: string[], docRangeFilters: string[], viewSpecScript?: ScriptField) { + const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + + const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields + for (let i = 0; i < docFilters.length; i += 3) { + const [key, value, modifiers] = docFilters.slice(i, i + 3); + if (!filterFacets[key]) { + filterFacets[key] = {}; + } + filterFacets[key][value] = modifiers; + } + + const filteredDocs = docFilters.length ? childDocs.filter(d => { + for (const facetKey of Object.keys(filterFacets)) { + const facet = filterFacets[facetKey]; + const satisfiesFacet = Object.keys(facet).some(value => { + if (facet[value] === "match") { + return d[facetKey] === undefined || Field.toString(d[facetKey] as Field).includes(value); + } + return (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value); + }); + if (!satisfiesFacet) { + return false; + } + } + return true; + }) : childDocs; + const rangeFilteredDocs = filteredDocs.filter(d => { + for (let i = 0; i < docRangeFilters.length; i += 3) { + const key = docRangeFilters[i]; + const min = Number(docRangeFilters[i + 1]); + const max = Number(docRangeFilters[i + 2]); + const val = Cast(d[key], "number", null); + if (val !== undefined && (val < min || val > max)) { + return false; + } + } + return true; + }); + return rangeFilteredDocs; + } export function Publish(promoteDoc: Doc, targetID: string, addDoc: any, remDoc: any) { targetID = targetID.replace(/^-/, "").replace(/\([0-9]*\)$/, ""); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 4276e04e4..9f04aab04 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -424,6 +424,7 @@ export class CurrentUserUtils { { title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc }, { title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, { title: "Connect a Google Account", label: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' }, + { title: "Connect a Hypothesis Account", label: "Hypothesis Account", icon: "houzz", click: 'HypothesisAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' }, ]; } diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 34f666f62..a3a023164 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -7,6 +7,7 @@ import { List } from "../../fields/List"; import { ScriptField } from "../../fields/ScriptField"; import { Cast, PromiseValue } from "../../fields/Types"; import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager"; +import HypothesisAuthenticationManager from "../apis/HypothesisAuthenticationManager"; import { DocServer } from "../DocServer"; import { DocumentType } from "../documents/DocumentTypes"; import { DictationManager } from "../util/DictationManager"; @@ -104,6 +105,7 @@ export default class KeyManager { DictationManager.Controls.stop(); // RecommendationsBox.Instance.closeMenu(); GoogleAuthenticationManager.Instance.cancel(); + HypothesisAuthenticationManager.Instance.cancel(); SharingManager.Instance.close(); break; case "delete": diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 15f818d1f..eba9bb344 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -61,6 +61,7 @@ import { LinkMenu } from './linking/LinkMenu'; import { LinkDocPreview } from './nodes/LinkDocPreview'; import { Fade } from '@material-ui/core'; import { LinkCreatedBox } from './nodes/LinkCreatedBox'; +import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager'; @observer export class MainView extends React.Component { @@ -601,6 +602,7 @@ export class MainView extends React.Component { + diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index ed8535ecb..ce6872695 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -106,16 +106,6 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: [...this.props.docFilters(), ...Cast(this.props.Document._docFilters, listSpec("string"), [])]; } @computed get childDocs() { - const docFilters = this.docFilters(); - const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []); - const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields - for (let i = 0; i < docFilters.length; i += 3) { - const [key, value, modifiers] = docFilters.slice(i, i + 3); - if (!filterFacets[key]) { - filterFacets[key] = {}; - } - filterFacets[key][value] = modifiers; - } let rawdocs: (Doc | Promise)[] = []; if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list; @@ -128,38 +118,13 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: const rootDoc = Cast(this.props.Document.rootDocument, Doc, null); rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : []; } + const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc); + const docFilters = this.docFilters(); const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); - const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []); - const filteredDocs = docFilters.length && !this.props.dontRegisterView ? childDocs.filter(d => { - for (const facetKey of Object.keys(filterFacets)) { - const facet = filterFacets[facetKey]; - const satisfiesFacet = Object.keys(facet).some(value => { - if (facet[value] === "match") { - return d[facetKey] === undefined || Field.toString(d[facetKey] as Field).includes(value); - } - return (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value); - }); - if (!satisfiesFacet) { - return false; - } - } - return true; - }) : childDocs; - const rangeFilteredDocs = filteredDocs.filter(d => { - for (let i = 0; i < docRangeFilters.length; i += 3) { - const key = docRangeFilters[i]; - const min = Number(docRangeFilters[i + 1]); - const max = Number(docRangeFilters[i + 2]); - const val = Cast(d[key], "number", null); - if (val !== undefined && (val < min || val > max)) { - return false; - } - } - return true; - }); - return rangeFilteredDocs; + return this.props.Document.dontRegisterView ? docs : DocUtils.FilterDocs(docs, docFilters, docRangeFilters, viewSpecScript); } @action diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index b38db9a1e..3311bfa33 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1094,7 +1094,7 @@ export class DocumentView extends DocComponent(Docu return (this.props.treeViewDoc && this.props.LayoutTemplateString) || // render nothing for: tree view anchor dots this.layoutDoc.presBox || // presentationbox nodes this.props.dontRegisterView ? (null) : // view that are not registered - DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) => + DocUtils.FilterDocs(DocListCast(this.Document.links), this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) => { + if (existsSync(serverPathToFile(Directory.hypothesis, user.id))) { + const read = readFileSync(serverPathToFile(Directory.hypothesis, user.id), "base64") || ""; + console.log("READ = " + read); + res.send(read); + } else res.send(""); + } + }); + + register({ + method: Method.POST, + subscription: "/writeHypothesisAccessToken", + secureHandler: async ({ user, req, res }) => { + const write = req.body.authenticationCode; + console.log("WRITE = " + write); + res.send(await writeFile(serverPathToFile(Directory.hypothesis, user.id), write, "base64", () => { })); + } + }); + + register({ + method: Method.GET, + subscription: "/revokeHypothesisAccessToken", + secureHandler: async ({ user, res }) => { + await Database.Auxiliary.GoogleAccessToken.Revoke("dash-hyp-" + user.id); + res.send(); + } + }); + + } +} \ No newline at end of file diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index fe39b84e6..55ceab9fb 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -24,7 +24,8 @@ export enum Directory { pdfs = "pdfs", text = "text", pdf_thumbnails = "pdf_thumbnails", - audio = "audio" + audio = "audio", + hypothesis = "hypothesis" } export function serverPathToFile(directory: Directory, filename: string) { diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index 20f96f432..b0157a85f 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -39,7 +39,8 @@ export namespace GoogleApiServerUtils { */ export enum Service { Documents = "Documents", - Slides = "Slides" + Slides = "Slides", + Hypothesis = "Hypothesis" } /** diff --git a/src/server/index.ts b/src/server/index.ts index 083173bb5..9af4b00bc 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -18,10 +18,10 @@ import PDFManager from "./ApiManagers/PDFManager"; import UploadManager from "./ApiManagers/UploadManager"; import { log_execution } from "./ActionUtilities"; import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager"; +import HypothesisManager from "./ApiManagers/HypothesisManager"; import GooglePhotosManager from "./ApiManagers/GooglePhotosManager"; import { Logger } from "./ProcessFactory"; import { yellow } from "colors"; -import { DashSessionAgent } from "./DashSession/DashSessionAgent"; import SessionManager from "./ApiManagers/SessionManager"; import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent"; @@ -72,6 +72,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: new DeleteManager(), new UtilManager(), new GeneralGoogleManager(), + new HypothesisManager(), new GooglePhotosManager(), ]; -- cgit v1.2.3-70-g09d2 From 828a28e112db3357f52d3923987070129fd64b21 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 22 Jul 2020 11:33:28 -0400 Subject: restored download document. fixed clone() to clone to be async and handles lists properly. --- src/client/util/CurrentUserUtils.ts | 6 +++++ src/client/util/LinkManager.ts | 12 ++------- src/client/util/Scripting.ts | 10 +++----- src/client/views/GlobalKeyHandler.ts | 4 +-- src/client/views/PreviewCursor.tsx | 4 +-- .../collections/collectionFreeForm/MarqueeView.tsx | 8 +++--- src/client/views/linking/LinkEditor.tsx | 4 +-- src/client/views/linking/LinkMenuItem.tsx | 3 ++- src/client/views/nodes/DocumentView.tsx | 14 ++++------ src/client/views/nodes/LinkDocPreview.tsx | 9 +------ .../formattedText/FormattedTextBoxComment.tsx | 2 ++ .../views/nodes/formattedText/RichTextMenu.tsx | 4 ++- src/fields/Doc.ts | 30 +++++++++++----------- src/server/ApiManagers/DownloadManager.ts | 12 +++------ src/server/ApiManagers/UploadManager.ts | 14 +++------- 15 files changed, 56 insertions(+), 80 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 03a75381a..23b8f09de 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -22,6 +22,7 @@ import { DocumentType } from "../documents/DocumentTypes"; import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; import { LabelBox } from "../views/nodes/LabelBox"; +import { LinkManager } from "./LinkManager"; export class CurrentUserUtils { private static curr_id: string; @@ -888,3 +889,8 @@ export class CurrentUserUtils { Scripting.addGlobal(function createNewWorkspace() { return MainView.Instance.createNewWorkspace(); }, "creates a new workspace when called"); + +Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, + "returns all the links to the document or its annotations", "(doc: any)"); +Scripting.addGlobal(function directLinks(doc: any) { return new List(LinkManager.Instance.getAllDirectLinks(doc)); }, + "returns all the links directly to the document", "(doc: any)"); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 974744344..223f0e7ef 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -2,8 +2,6 @@ import { Doc, DocListCast, Opt } from "../../fields/Doc"; import { List } from "../../fields/List"; import { listSpec } from "../../fields/Schema"; import { Cast, StrCast } from "../../fields/Types"; -import { Scripting } from "./Scripting"; -import { undoBatch } from "./UndoManager"; /* * link doc: @@ -25,12 +23,12 @@ export class LinkManager { private static _instance: LinkManager; - public static currentLink: Opt; public static get Instance(): LinkManager { return this._instance || (this._instance = new this()); } + private constructor() { } @@ -53,7 +51,6 @@ export class LinkManager { return false; } - @undoBatch public deleteLink(linkDoc: Doc): boolean { if (LinkManager.Instance.LinkManagerDoc && linkDoc instanceof Doc) { Doc.RemoveDocFromList(LinkManager.Instance.LinkManagerDoc, "data", linkDoc); @@ -210,9 +207,4 @@ export class LinkManager { if (Doc.AreProtosEqual(anchor, a2)) return a1; if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc; } -} - -Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, - "returns all the links to the document or its annotations", "(doc: any)"); -Scripting.addGlobal(function directLinks(doc: any) { return new List(LinkManager.Instance.getAllDirectLinks(doc)); }, - "returns all the links directly to the document", "(doc: any)"); \ No newline at end of file +} \ No newline at end of file diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index e6cf50de3..f1e6155d2 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -10,8 +10,6 @@ export { ts }; // @ts-ignore import * as typescriptlib from '!!raw-loader!./type_decls.d'; import { Doc, Field } from '../../fields/Doc'; -import { Cast } from "../../fields/Types"; -import { listSpec } from "../../fields/Schema"; export interface ScriptSucccess { success: true; @@ -95,10 +93,10 @@ export namespace Scripting { export function removeGlobal(name: string) { if (getGlobals().includes(name)) { delete _scriptingGlobals[name]; - if (_scriptingDescriptions[name]){ + if (_scriptingDescriptions[name]) { delete _scriptingDescriptions[name]; } - if (_scriptingParams[name]){ + if (_scriptingParams[name]) { delete _scriptingParams[name]; } return true; @@ -123,11 +121,11 @@ export namespace Scripting { return _scriptingGlobals; } - export function getDescriptions(){ + export function getDescriptions() { return _scriptingDescriptions; } - export function getParameters(){ + export function getParameters() { return _scriptingParams; } } diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 4dfa7aec8..b63537b5f 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -309,13 +309,13 @@ export default class KeyManager { const targetDataDoc = Doc.GetProto(first.props.Document); const fieldKey = Doc.LayoutFieldKey(first.props.Document); const docList = DocListCast(targetDataDoc[fieldKey]); - docids.map((did, i) => i && DocServer.GetRefField(did).then(doc => { + docids.map((did, i) => i && DocServer.GetRefField(did).then(async doc => { count++; if (doc instanceof Doc) { list.push(doc); } if (count === docids.length) { - const added = list.filter(d => !docList.includes(d)).map(d => clone ? Doc.MakeClone(d) : d); + const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => clone ? await Doc.MakeClone(d) : d)); if (added.length) { added.map(doc => doc.context = targetDataDoc); undoBatch(() => { diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 6583589f3..2d51403d7 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -69,11 +69,11 @@ export class PreviewCursor extends React.Component<{}> { const list: Doc[] = []; let first: Doc | undefined; - docids.map((did, i) => i && DocServer.GetRefField(did).then(doc => { + docids.map((did, i) => i && DocServer.GetRefField(did).then(async doc => { count++; if (doc instanceof Doc) { i === 1 && (first = doc); - const alias = clone ? Doc.MakeClone(doc) : doc; + const alias = clone ? await Doc.MakeClone(doc) : doc; const deltaX = NumCast(doc.x) - NumCast(first!.x) - ptx; const deltaY = NumCast(doc.y) - NumCast(first!.y) - pty; alias.x = newPoint[0] + deltaX; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 6db8c8992..84719b2c9 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -189,8 +189,8 @@ export class MarqueeView extends React.Component { this._downX = this._lastX = e.clientX; this._downY = this._lastY = e.clientY; - if (!(e as any).marqueeHit) { - (e as any).marqueeHit = true; + if (!(e.nativeEvent as any).marqueeHit) { + (e.nativeEvent as any).marqueeHit = true; // allow marquee if right click OR alt+left click OR space bar + left click if (e.button === 2 || (e.button === 0 && (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))))) { // if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) { @@ -291,8 +291,8 @@ export class MarqueeView extends React.Component { //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION"; + @undoBatch @action deleteLink = (): void => { LinkManager.Instance.deleteLink(this.props.linkDoc); @@ -422,8 +424,6 @@ export class LinkEditor extends React.Component {

Editing Link to: { destination.proto?.title ?? destination.title ?? "untitled"}

- {/* */}
Show more link information
} placement="top">
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 0e847632b..d8ba39f09 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -17,6 +17,7 @@ import { DocumentLinksButton } from '../nodes/DocumentLinksButton'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; import { Tooltip } from '@material-ui/core'; import { DocumentType } from '../../documents/DocumentTypes'; +import { undoBatch } from '../../util/UndoManager'; library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt, faEyeSlash); @@ -163,10 +164,10 @@ export class LinkMenuItem extends React.Component { } } + @undoBatch @action deleteLink = (): void => { LinkManager.Instance.deleteLink(this.props.linkDoc); - //this.props.showLinks(); LinkDocPreview.LinkInfo = undefined; DocumentLinksButton.EditLink = undefined; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 0b5bd707b..12d9890c9 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -794,16 +794,12 @@ export class DocumentView extends DocComponent(Docu } moreItems.push({ description: "Download document", icon: "download", event: async () => { - const response = await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), { - qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' } - }); - console.log(response ? JSON.parse(response) : undefined); + const a = document.createElement("a"); + const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); + a.href = url; + a.download = `DocExport-${this.props.Document[Id]}.zip`; + a.click(); } - // const a = document.createElement("a"); - // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); - // a.href = url; - // a.download = `DocExport-${this.props.Document[Id]}.zip`; - // a.click(); }); moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" }); } diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 1caa82380..ebb916307 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -15,6 +15,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { LinkManager } from '../../util/LinkManager'; import { DocumentLinksButton } from './DocumentLinksButton'; import { ContextMenu } from '../ContextMenu'; +import { undoBatch } from '../../util/UndoManager'; interface Props { linkDoc?: Doc; @@ -31,14 +32,6 @@ export class LinkDocPreview extends React.Component { @observable _toolTipText = ""; _editRef = React.createRef(); - @action - deleteLink = (): void => { - this.props.linkDoc ? LinkManager.Instance.deleteLink(this.props.linkDoc) : null; - //this.props.showLinks(); - LinkDocPreview.LinkInfo = undefined; - DocumentLinksButton.EditLink = undefined; - } - @action onContextMenu = (e: React.MouseEvent) => { DocumentLinksButton.EditLink = undefined; diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 3687d5513..6f3984f39 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -22,6 +22,7 @@ import { LinkManager } from "../../../util/LinkManager"; import { LinkDocPreview } from "../LinkDocPreview"; import { DocumentLinksButton } from "../DocumentLinksButton"; import { Tooltip } from "@material-ui/core"; +import { undoBatch } from "../../../util/UndoManager"; export let formattedTextBoxCommentPlugin = new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } @@ -143,6 +144,7 @@ export class FormattedTextBoxComment { } } + @undoBatch @action deleteLink = () => { FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null; diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 2e0b0e659..47a4911b8 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -23,7 +23,7 @@ import { updateBullets } from "./ProsemirrorExampleTransfer"; import "./RichTextMenu.scss"; import { schema } from "./schema_rts"; import { TraceMobx } from "../../../../fields/util"; -import { UndoManager } from "../../../util/UndoManager"; +import { UndoManager, undoBatch } from "../../../util/UndoManager"; import { Tooltip } from "@material-ui/core"; const { toggleMark } = require("prosemirror-commands"); @@ -831,6 +831,8 @@ export default class RichTextMenu extends AntimodeMenu { ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRight", "", target); } + @undoBatch + @action deleteLink = () => { if (this.view) { const link = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 2f3b7025e..16ade5912 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -483,25 +483,25 @@ export namespace Doc { return alias; } - - - export function makeClone(doc: Doc, cloneMap: Map, rtfs: { copy: Doc, key: string, field: RichTextField }[]): Doc { + export async function makeClone(doc: Doc, cloneMap: Map, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[]): Promise { if (Doc.IsBaseProto(doc)) return doc; if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; const copy = new Doc(undefined, true); cloneMap.set(doc[Id], copy); if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy); - const exclude = Cast(doc.excludeFields, listSpec("string"), []); - Object.keys(doc).forEach(key => { - if (exclude.includes(key)) return; + const filter = Cast(doc.cloneFieldFilter, listSpec("string"), exclusions); + Object.keys(doc).forEach(async key => { + if (filter.includes(key)) return; const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); const field = ProxyField.WithoutProxy(() => doc[key]); - const copyObjectField = (field: ObjectField) => { - const list = Cast(doc[key], listSpec(Doc)); - if (list !== undefined && !(list instanceof Promise)) { - copy[key] = new List(list.filter(d => d instanceof Doc).map(d => Doc.makeClone(d as Doc, cloneMap, rtfs))); + const copyObjectField = async (field: ObjectField) => { + const list = await Cast(doc[key], listSpec(Doc)); + const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc); + if (docs !== undefined && docs.length) { + const clones = docs.map(async d => await Doc.makeClone(d as Doc, cloneMap, rtfs, exclusions)); + copy[key] = new List(await Promise.all(clones)); } else if (doc[key] instanceof Doc) { - copy[key] = key.includes("layout[") ? undefined : Doc.makeClone(doc[key] as Doc, cloneMap, rtfs); // reference documents except copy documents that are expanded teplate fields + copy[key] = key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, rtfs, exclusions); // reference documents except copy documents that are expanded teplate fields } else { copy[key] = ObjectField.MakeCopy(field); if (field instanceof RichTextField) { @@ -513,7 +513,7 @@ export namespace Doc { }; if (key === "proto") { if (doc[key] instanceof Doc) { - copy[key] = Doc.makeClone(doc[key]!, cloneMap, rtfs); + copy[key] = await Doc.makeClone(doc[key]!, cloneMap, rtfs, exclusions); } } else { if (field instanceof RefField) { @@ -535,10 +535,10 @@ export namespace Doc { cloneMap.set(doc[Id], copy); return copy; } - export function MakeClone(doc: Doc): Doc { + export async function MakeClone(doc: Doc): Promise { const cloneMap = new Map(); const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = []; - const copy = Doc.makeClone(doc, cloneMap, rtfMap); + const copy = await Doc.makeClone(doc, cloneMap, rtfMap, ["context", "annotationOn", "cloneOf"]); rtfMap.map(({ copy, key, field }) => { const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { const mapped = cloneMap.get(id); @@ -657,7 +657,7 @@ export namespace Doc { export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string): Doc { const copy = new Doc(copyProtoId, true); - const exclude = Cast(doc.excludeFields, listSpec("string"), []); + const exclude = Cast(doc.cloneFieldFilter, listSpec("string"), []); Object.keys(doc).forEach(key => { if (exclude.includes(key)) return; const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); diff --git a/src/server/ApiManagers/DownloadManager.ts b/src/server/ApiManagers/DownloadManager.ts index c5f3ca717..0d4472fdc 100644 --- a/src/server/ApiManagers/DownloadManager.ts +++ b/src/server/ApiManagers/DownloadManager.ts @@ -80,20 +80,14 @@ async function getDocs(id: string) { } const ids: string[] = []; for (const key in doc.fields) { - if (!doc.fields.hasOwnProperty(key)) { - continue; - } + if (!doc.fields.hasOwnProperty(key)) { continue; } const field = doc.fields[key]; - if (field === undefined || field === null) { - continue; - } + if (field === undefined || field === null) { continue; } if (field.__type === "proxy" || field.__type === "prefetch_proxy") { ids.push(field.fieldId); } else if (field.__type === "script" || field.__type === "computed") { - if (field.captures) { - ids.push(field.captures.fieldId); - } + field.captures && ids.push(field.captures.fieldId); } else if (field.__type === "list") { ids.push(...fn(field)); } else if (typeof field === "string") { diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 55ceab9fb..0b9e999ac 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -139,13 +139,9 @@ export default class UploadManager extends ApiManager { doc.id = getId(doc.id); } for (const key in doc.fields) { - if (!doc.fields.hasOwnProperty(key)) { - continue; - } + if (!doc.fields.hasOwnProperty(key)) { continue; } const field = doc.fields[key]; - if (field === undefined || field === null) { - continue; - } + if (field === undefined || field === null) { continue; } if (field.__type === "proxy" || field.__type === "prefetch_proxy") { field.fieldId = getId(field.fieldId); @@ -208,11 +204,7 @@ export default class UploadManager extends ApiManager { } catch (e) { console.log(e); } unlink(path_2, () => { }); } - if (id) { - res.send(JSON.stringify(getId(id))); - } else { - res.send(JSON.stringify("error")); - } + res.send(JSON.stringify(id ? getId(id) : "error")); } catch (e) { console.log(e); } resolve(); }); -- cgit v1.2.3-70-g09d2 From 8f95d52ed2b93930716ab6ed0ba9aab5c438876c Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 22 Jul 2020 20:16:53 -0400 Subject: fixed document download/import to capture links as well. --- package-lock.json | 56 ++++++++++++ package.json | 3 + src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/PreviewCursor.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 7 ++ src/client/views/nodes/DocHolderBox.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 12 +-- src/fields/Doc.ts | 101 ++++++++++++++++----- src/server/ApiManagers/UploadManager.ts | 2 + 9 files changed, 155 insertions(+), 32 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/package-lock.json b/package-lock.json index 1b39905cf..698bd60cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -711,6 +711,12 @@ "express-validator": "*" } }, + "@types/file-saver": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.1.tgz", + "integrity": "sha512-g1QUuhYVVAamfCifK7oB7G3aIl4BbOyzDOqVyUfEr4tfBKrXfeH+M+Tg7HKCXSrbzxYdhyCP7z9WbKo0R2hBCw==", + "dev": true + }, "@types/formidable": { "version": "1.0.31", "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-1.0.31.tgz", @@ -6069,6 +6075,11 @@ } } }, + "file-saver": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz", + "integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw==" + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -7280,6 +7291,11 @@ } } }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, "import-fresh": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", @@ -8160,6 +8176,33 @@ "promise": "^7.0.1" } }, + "jszip": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz", + "integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } + } + }, "jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -8300,6 +8343,14 @@ } } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -15142,6 +15193,11 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", diff --git a/package.json b/package.json index cb083020f..6c466825e 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@types/express-flash": "0.0.0", "@types/express-session": "^1.15.16", "@types/express-validator": "^3.0.0", + "@types/file-saver": "^2.0.1", "@types/formidable": "^1.0.31", "@types/google-maps-react": "^2.0.5", "@types/jquery": "^3.5.0", @@ -154,6 +155,7 @@ "express-session": "^1.17.0", "express-validator": "^5.3.1", "expressjs": "^1.0.1", + "file-saver": "^2.0.2", "find-in-files": "^0.5.0", "fit-curve": "^0.1.7", "flexlayout-react": "^0.3.11", @@ -172,6 +174,7 @@ "image-size-stream": "^1.1.0", "js-datepicker": "^4.6.6", "jsonschema": "^1.2.5", + "jszip": "^3.5.0", "libxmljs": "^0.19.7", "lodash": "^4.17.15", "material-ui": "^0.20.2", diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index b63537b5f..086085db5 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -315,7 +315,7 @@ export default class KeyManager { list.push(doc); } if (count === docids.length) { - const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => clone ? await Doc.MakeClone(d) : d)); + const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => clone ? (await Doc.MakeClone(d)).clone : d)); if (added.length) { added.map(doc => doc.context = targetDataDoc); undoBatch(() => { diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 2d51403d7..b4116e980 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -73,7 +73,7 @@ export class PreviewCursor extends React.Component<{}> { count++; if (doc instanceof Doc) { i === 1 && (first = doc); - const alias = clone ? await Doc.MakeClone(doc) : doc; + const alias = clone ? (await Doc.MakeClone(doc)).clone : doc; const deltaX = NumCast(doc.x) - NumCast(first!.x) - ptx; const deltaY = NumCast(doc.y) - NumCast(first!.y) - pty; alias.x = newPoint[0] + deltaX; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 412f91417..bee9e7009 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -46,6 +46,8 @@ import "./CollectionFreeFormView.scss"; import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); +import { SearchUtil } from "../../../util/SearchUtil"; +import { LinkManager } from "../../../util/LinkManager"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -1276,6 +1278,11 @@ export class CollectionFreeFormView extends CollectionSubView { + SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => { + docs.docs.forEach(d => LinkManager.Instance.addLink(d)); + }) + }, 2000); // need to give solr some time to update so that this query will find any link docs we've added. } } } diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx index 0cf5505cc..0c4242172 100644 --- a/src/client/views/nodes/DocHolderBox.tsx +++ b/src/client/views/nodes/DocHolderBox.tsx @@ -180,7 +180,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent(Docu } moreItems.push({ description: "Download document", icon: "download", event: async () => { - const a = document.createElement("a"); - const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); - a.href = url; - a.download = `DocExport-${this.props.Document[Id]}.zip`; - a.click(); + Doc.Zip(this.props.Document); + // const a = document.createElement("a"); + // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); + // a.href = url; + // a.download = `DocExport-${this.props.Document[Id]}.zip`; + // a.click(); } }); moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" }); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 16ade5912..917a6853c 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1,6 +1,6 @@ import { action, computed, observable, ObservableMap, runInAction, untracked } from "mobx"; import { computedFn } from "mobx-utils"; -import { alias, map, serializable } from "serializr"; +import { alias, map, serializable, list } from "serializr"; import { DocServer } from "../client/DocServer"; import { DocumentType } from "../client/documents/DocumentTypes"; import { Scripting, scriptingGlobal } from "../client/util/Scripting"; @@ -14,12 +14,16 @@ import { ObjectField } from "./ObjectField"; import { PrefetchProxy, ProxyField } from "./Proxy"; import { FieldId, RefField } from "./RefField"; import { RichTextField } from "./RichTextField"; +import { ImageField, VideoField, WebField, AudioField, PdfField } from "./URLField"; +import { DateField } from "./DateField"; import { listSpec } from "./Schema"; import { ComputedField } from "./ScriptField"; import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, GetEffectiveAcl } from "./util"; import { LinkManager } from "../client/util/LinkManager"; import { SharingPermissions } from "../client/util/SharingManager"; +import JSZip = require("jszip"); +import { saveAs } from "file-saver"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -93,6 +97,7 @@ export const WidthSym = Symbol("Width"); export const HeightSym = Symbol("Height"); export const DataSym = Symbol("Data"); export const LayoutSym = Symbol("Layout"); +export const FieldsSym = Symbol("Fields"); export const AclSym = Symbol("Acl"); export const AclPrivate = Symbol("AclOwnerOnly"); export const AclReadonly = Symbol("AclReadOnly"); @@ -180,7 +185,6 @@ export class Doc extends RefField { } @observable - //{ [key: string]: Field | FieldWaiting | undefined } private ___fields: any = {}; private [UpdatingFromServer]: boolean = false; @@ -191,6 +195,7 @@ export class Doc extends RefField { private [Self] = this; private [SelfProxy]: any; + public [FieldsSym] = () => this.___fields; public [AclSym]: { [key: string]: symbol }; public [WidthSym] = () => NumCast(this[SelfProxy]._width); public [HeightSym] = () => NumCast(this[SelfProxy]._height); @@ -483,27 +488,28 @@ export namespace Doc { return alias; } - export async function makeClone(doc: Doc, cloneMap: Map, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[]): Promise { + export async function makeClone(doc: Doc, cloneMap: Map, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[], dontCreate: boolean): Promise { if (Doc.IsBaseProto(doc)) return doc; if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; - const copy = new Doc(undefined, true); + const copy = dontCreate ? doc : new Doc(undefined, true); cloneMap.set(doc[Id], copy); if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy); const filter = Cast(doc.cloneFieldFilter, listSpec("string"), exclusions); - Object.keys(doc).forEach(async key => { + await Promise.all(Object.keys(doc).map(async key => { if (filter.includes(key)) return; + const assignKey = (val: any) => !dontCreate && (copy[key] = val); const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); const field = ProxyField.WithoutProxy(() => doc[key]); const copyObjectField = async (field: ObjectField) => { const list = await Cast(doc[key], listSpec(Doc)); const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc); if (docs !== undefined && docs.length) { - const clones = docs.map(async d => await Doc.makeClone(d as Doc, cloneMap, rtfs, exclusions)); - copy[key] = new List(await Promise.all(clones)); + const clones = await Promise.all(docs.map(async d => await Doc.makeClone(d as Doc, cloneMap, rtfs, exclusions, dontCreate))); + !dontCreate && assignKey(new List(clones)); } else if (doc[key] instanceof Doc) { - copy[key] = key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, rtfs, exclusions); // reference documents except copy documents that are expanded teplate fields + assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, rtfs, exclusions, dontCreate)); // reference documents except copy documents that are expanded teplate fields } else { - copy[key] = ObjectField.MakeCopy(field); + assignKey(ObjectField.MakeCopy(field)); if (field instanceof RichTextField) { if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) { rtfs.push({ copy, key, field }); @@ -513,32 +519,34 @@ export namespace Doc { }; if (key === "proto") { if (doc[key] instanceof Doc) { - copy[key] = await Doc.makeClone(doc[key]!, cloneMap, rtfs, exclusions); + assignKey(await Doc.makeClone(doc[key]!, cloneMap, rtfs, exclusions, dontCreate)); } } else { if (field instanceof RefField) { - copy[key] = field; + assignKey(field); } else if (cfield instanceof ComputedField) { - copy[key] = ComputedField.MakeFunction(cfield.script.originalScript); - (key === "links" && field instanceof ObjectField) && copyObjectField(field); + !dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript)); + (key === "links" && field instanceof ObjectField) && await copyObjectField(field); } else if (field instanceof ObjectField) { - copyObjectField(field); + await copyObjectField(field); } else if (field instanceof Promise) { debugger; //This shouldn't happend... } else { - copy[key] = field; + assignKey(field); } } - }); - Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true); - copy.cloneOf = doc; - cloneMap.set(doc[Id], copy); + })); + if (!dontCreate) { + Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true); + copy.cloneOf = doc; + cloneMap.set(doc[Id], copy); + } return copy; } - export async function MakeClone(doc: Doc): Promise { + export async function MakeClone(doc: Doc, dontCreate: boolean = false) { const cloneMap = new Map(); const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = []; - const copy = await Doc.makeClone(doc, cloneMap, rtfMap, ["context", "annotationOn", "cloneOf"]); + const copy = await Doc.makeClone(doc, cloneMap, rtfMap, ["context", "annotationOn", "cloneOf"], dontCreate); rtfMap.map(({ copy, key, field }) => { const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { const mapped = cloneMap.get(id); @@ -552,9 +560,56 @@ export namespace Doc { const re = new RegExp(regex, "g"); copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text); }); - return copy; - } + return { clone: copy, map: cloneMap }; + } + + export async function Zip(doc: Doc) { + const { clone, map } = await Doc.MakeClone(doc, true); + function replacer(key: any, value: any) { + console.log("Checkin: " + key); + if (["cloneOf", "context", "cursors"].includes(key)) return undefined; + else if (value instanceof Doc) { + if (key !== "field" && Number.isNaN(Number(key))) { + const __fields = value[FieldsSym](); + return { id: value[Id], __type: "Doc", fields: __fields }; + } else { + return { fieldId: value[Id], __type: "proxy" }; + } + } + else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: "RichTextField" }; + else if (value instanceof ImageField) return { url: value.url.href, __type: "image" }; + else if (value instanceof PdfField) return { url: value.url.href, __type: "pdf" }; + else if (value instanceof AudioField) return { url: value.url.href, __type: "audio" }; + else if (value instanceof VideoField) return { url: value.url.href, __type: "video" }; + else if (value instanceof WebField) return { url: value.url.href, __type: "web" }; + else if (value instanceof DateField) return { date: value.toString(), __type: "date" }; + else if (value instanceof ProxyField) return { fieldId: value.fieldId, __type: "proxy" }; + else if (value instanceof Array && key !== "fields") return { fields: value, __type: "list" }; + else if (value instanceof ComputedField) return { script: value.script, __type: "computed" }; + else return value; + } + + const docs: { [id: string]: any } = {}; + Array.from(map.entries()).forEach(f => docs[f[0]] = f[1]); + const docString = JSON.stringify({ id: doc[Id], docs }, replacer); + + var zip = new JSZip(); + zip.file("doc.json", docString); + + // // Generate a directory within the Zip file structure + // var img = zip.folder("images"); + + // // Add a file to the directory, in this case an image with data URI as contents + // img.file("smile.gif", imgData, {base64: true}); + + // Generate the zip file asynchronously + zip.generateAsync({ type: "blob" }) + .then((content: any) => { + // Force down of the Zip file + saveAs(content, "download.zip"); + }); + } // // Determines whether the layout needs to be expanded (as a template). // template expansion is rquired when the layout is a template doc/field and there's a datadoc which isn't equal to the layout template diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 0b9e999ac..4455d11eb 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -16,6 +16,7 @@ const imageDataUri = require('image-data-uri'); import { isWebUri } from "valid-url"; import { launch } from "puppeteer"; import { Opt } from "../../fields/Doc"; +import { SolrManager } from "./SearchManager"; export enum Directory { parsed_files = "parsed_files", @@ -204,6 +205,7 @@ export default class UploadManager extends ApiManager { } catch (e) { console.log(e); } unlink(path_2, () => { }); } + SolrManager.update(); res.send(JSON.stringify(id ? getId(id) : "error")); } catch (e) { console.log(e); } resolve(); -- cgit v1.2.3-70-g09d2