aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/apis/google_docs/GoogleApiClientUtils.ts185
-rw-r--r--src/client/views/MainView.tsx11
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx24
-rw-r--r--src/new_fields/RichTextField.ts13
-rw-r--r--src/server/index.ts25
5 files changed, 162 insertions, 96 deletions
diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index 5e974b2e7..dda36f05a 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -1,7 +1,8 @@
import { docs_v1 } from "googleapis";
import { PostToServer } from "../../../Utils";
import { RouteStore } from "../../../server/RouteStore";
-import { Opt } from "../../../new_fields/Doc";
+import { Opt, Doc } from "../../../new_fields/Doc";
+import { isArray } from "util";
export namespace GoogleApiClientUtils {
@@ -9,15 +10,12 @@ export namespace GoogleApiClientUtils {
export enum Actions {
Create = "create",
- Retrieve = "retrieve"
+ Retrieve = "retrieve",
+ Update = "update"
}
export namespace Utils {
- export const fromRgb = (red: number, green: number, blue: number) => {
- return { color: { rgbColor: { red, green, blue } } };
- };
-
export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false) => {
let fragments: string[] = [];
if (document.body && document.body.content) {
@@ -36,55 +34,36 @@ export namespace GoogleApiClientUtils {
return removeNewlines ? text.ReplaceAll("\n", "") : text;
};
- }
-
- export const ExampleDocumentSchema = {
- title: "This is a Google Doc Created From Dash Web",
- body: {
- content: [
- {
- endIndex: 1,
- sectionBreak: {
- sectionStyle: {
- columnSeparatorStyle: "NONE",
- contentDirection: "LEFT_TO_RIGHT"
+ export const EndOf = (schema: docs_v1.Schema$Document): Opt<number> => {
+ if (schema.body && schema.body.content) {
+ let paragraphs = schema.body.content.filter(el => el.paragraph);
+ if (paragraphs.length) {
+ let target = paragraphs[paragraphs.length - 1];
+ if (target.paragraph && target.paragraph.elements) {
+ length = target.paragraph.elements.length;
+ if (length) {
+ let final = target.paragraph.elements[length - 1];
+ return final.endIndex ? final.endIndex - 1 : undefined;
}
}
- },
- {
- paragraph: {
- elements: [
- {
- textRun: {
- content: "And this is its bold, blue text!!!\n",
- textStyle: {
- bold: true,
- backgroundColor: Utils.fromRgb(0, 0, 1)
- }
- }
- }
- ]
- }
- },
- {
- paragraph: {
- elements: [
- {
- textRun: {
- content: "And this is its bold, blue text!!!\n",
- textStyle: {
- bold: true,
- backgroundColor: Utils.fromRgb(0, 0, 1)
- }
- }
- }
- ]
- }
- },
+ }
+ }
+ };
- ] as docs_v1.Schema$StructuralElement[]
- }
- } as docs_v1.Schema$Document;
+ }
+
+ export interface ReadOptions {
+ documentId: string;
+ removeNewlines?: boolean;
+ }
+
+ export interface WriteOptions {
+ documentId?: string;
+ title?: string;
+ content: string | string[];
+ index?: number;
+ store?: { receiver: Doc, key: string };
+ }
/**
* After following the authentication routine, which connects this API call to the current signed in account
@@ -96,45 +75,95 @@ export namespace GoogleApiClientUtils {
* actual document body and its styling!
* @returns the documentId of the newly generated document, or undefined if the creation process fails.
*/
- export const Create = async (schema?: docs_v1.Schema$Document): Promise<string | undefined> => {
+ const Create = async (title?: string): Promise<string | undefined> => {
let path = RouteStore.googleDocs + Actions.Create;
- let parameters = { requestBody: schema || ExampleDocumentSchema };
- let generatedId: string | undefined;
+ let parameters = {
+ requestBody: {
+ title: title || `Dash Export (${new Date().toDateString()})`
+ }
+ };
try {
- generatedId = await PostToServer(path, parameters);
- } catch (e) {
- console.error(e);
- generatedId = undefined;
- } finally {
- return generatedId;
+ let schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
+ return schema.documentId;
+ } catch {
+ return undefined;
}
};
- export interface ReadOptions {
- documentId: string;
- removeNewlines?: boolean;
- }
+ const Retrieve = async (documentId: string): Promise<docs_v1.Schema$Document | undefined> => {
+ let path = RouteStore.googleDocs + Actions.Retrieve;
+ let parameters = {
+ documentId
+ };
+ try {
+ let schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
+ return schema;
+ } catch {
+ return undefined;
+ }
+ };
+
+ const Update = async (documentId: string, requests: docs_v1.Schema$Request[]): Promise<docs_v1.Schema$BatchUpdateDocumentResponse | undefined> => {
+ let path = RouteStore.googleDocs + Actions.Update;
+ let parameters = {
+ documentId,
+ requestBody: {
+ requests
+ }
+ };
+ try {
+ let replies: docs_v1.Schema$BatchUpdateDocumentResponse = await PostToServer(path, parameters);
+ console.log(replies);
+ return replies;
+ } catch {
+ return undefined;
+ }
+ };
- export const Read = async (options: ReadOptions): Promise<Opt<string>> => {
+ export const Read = async (options: ReadOptions): Promise<string | undefined> => {
return Retrieve(options.documentId).then(schema => {
return schema ? Utils.extractText(schema, options.removeNewlines) : undefined;
});
};
- export const Retrieve = async (documentId: string): Promise<Opt<docs_v1.Schema$Document>> => {
- let path = RouteStore.googleDocs + Actions.Retrieve;
- let parameters = { documentId };
- let schema: Opt<docs_v1.Schema$Document>;
- try {
- schema = await PostToServer(path, parameters);
- } catch (e) {
- console.error(e);
- schema = undefined;
- } finally {
- return schema;
- }
+ export const ReadLines = async (options: ReadOptions) => {
+ return Retrieve(options.documentId).then(schema => {
+ if (!schema) {
+ return undefined;
+ }
+ let lines = Utils.extractText(schema).split("\n");
+ return options.removeNewlines ? lines.filter(line => line.length) : lines;
+ });
};
+ export const Write = async (options: WriteOptions): Promise<docs_v1.Schema$BatchUpdateDocumentResponse | undefined> => {
+ let target = options.documentId;
+ if (!target) {
+ if (!(target = await Create(options.title))) {
+ return undefined;
+ }
+ }
+ let index = options.index;
+ if (!index) {
+ let schema = await Retrieve(target);
+ if (!schema || !(index = Utils.EndOf(schema))) {
+ return undefined;
+ }
+ }
+ let text = options.content;
+ let request = {
+ insertText: {
+ text: isArray(text) ? text.join("\n") : text,
+ location: { index }
+ }
+ };
+ return Update(target, [request]).then(res => {
+ if (res && options.store) {
+ options.store.receiver[options.store.key] = res.documentId;
+ }
+ return res;
+ });
+ };
}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 705eacc0c..7b15e9624 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -87,11 +87,6 @@ export class MainView extends React.Component {
if (!("presentationView" in doc)) {
doc.presentationView = new List<Doc>([Docs.Create.TreeDocument([], { title: "Presentation" })]);
}
- if (!("googleDocId" in doc)) {
- GoogleApiClientUtils.Docs.Create().then(id => {
- id && (doc.googleDocId = id);
- });
- }
CurrentUserUtils.UserDocument.activeWorkspace = doc;
}
}
@@ -158,9 +153,9 @@ export class MainView extends React.Component {
reaction(() => this.mainContainer, () => {
let main = this.mainContainer, documentId;
if (main && (documentId = StrCast(main.googleDocId))) {
- GoogleApiClientUtils.Docs.Read({ documentId }).then(text => {
- text && Utils.CopyText(text);
- console.log(text);
+ let options = { documentId, removeNewlines: true };
+ GoogleApiClientUtils.Docs.ReadLines(options).then(lines => {
+ console.log(lines);
});
}
});
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 44b5d2c21..8c2af7c9e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,5 +1,5 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit, faSmile, faTextHeight } from '@fortawesome/free-solid-svg-icons';
+import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace } from "mobx";
import { observer } from "mobx-react";
import { baseKeymap } from "prosemirror-commands";
@@ -38,9 +38,10 @@ import { For } from 'babel-types';
import { DateField } from '../../../new_fields/DateField';
import { Utils } from '../../../Utils';
import { MainOverlayTextBox } from '../MainOverlayTextBox';
+import { GoogleApiClientUtils } from '../../apis/google_docs/GoogleApiClientUtils';
library.add(faEdit);
-library.add(faSmile, faTextHeight);
+library.add(faSmile, faTextHeight, faUpload);
// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
//
@@ -58,6 +59,8 @@ const richTextSchema = createSchema({
documentText: "string"
});
+const googleDocKey = "googleDocId";
+
type RichTextDocument = makeInterface<[typeof richTextSchema]>;
const RichTextDocument = makeInterface(richTextSchema);
@@ -661,7 +664,24 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight)), icon: "expand-arrows-alt"
});
ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" });
+ if (!(googleDocKey in Doc.GetProto(this.props.Document))) {
+ ContextMenu.Instance.addItem({ description: "Export to Google Doc...", event: this.exportToGoogleDoc, icon: "upload" });
+ }
}
+
+ exportToGoogleDoc = () => {
+ let dataDoc = Doc.GetProto(this.props.Document);
+ let data = Cast(dataDoc.data, RichTextField);
+ let content: string | undefined;
+ if (data && (content = data.plainText())) {
+ GoogleApiClientUtils.Docs.Write({
+ title: StrCast(dataDoc.title),
+ store: { receiver: dataDoc, key: googleDocKey },
+ content
+ });
+ }
+ }
+
render() {
let self = this;
let style = this.props.isOverlay ? "scroll" : "hidden";
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index 89799b2af..dc66813e0 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -9,6 +9,7 @@ import { scriptingGlobal } from "../client/util/Scripting";
export class RichTextField extends ObjectField {
@serializable(true)
readonly Data: string;
+ private Extractor = /,\"text\":\"([^\"\}]*)\"\}/g;
constructor(data: string) {
super();
@@ -22,4 +23,16 @@ export class RichTextField extends ObjectField {
[ToScriptString]() {
return `new RichTextField("${this.Data}")`;
}
+
+ plainText = () => {
+ let contents = "";
+ let matches: RegExpExecArray | null;
+ let considering = this.Data;
+ while ((matches = this.Extractor.exec(considering)) !== null) {
+ contents += matches[1];
+ considering = considering.substring(matches.index + matches[0].length);
+ this.Extractor.lastIndex = 0;
+ }
+ return contents.length ? contents : undefined;
+ }
} \ No newline at end of file
diff --git a/src/server/index.ts b/src/server/index.ts
index 4ccb61681..abaa29658 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -42,6 +42,7 @@ import AdmZip from 'adm-zip';
import * as YoutubeApi from "./apis/youtube/youtubeApiSample";
import { Response } from 'express-serve-static-core';
import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils";
+import { GaxiosResponse } from 'gaxios';
const MongoStore = require('connect-mongo')(session);
const mongoose = require('mongoose');
const probe = require("probe-image-size");
@@ -170,6 +171,13 @@ const read_text_file = (relativePath: string) => {
});
};
+const write_text_file = (relativePath: string, contents: any) => {
+ let target = path.join(__dirname, relativePath);
+ return new Promise<void>((resolve, reject) => {
+ fs.writeFile(target, contents, (err) => err ? reject(err) : resolve());
+ });
+};
+
app.get("/version", (req, res) => {
exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout, stderr) => {
if (err) {
@@ -790,21 +798,22 @@ const credentials = path.join(__dirname, "./credentials/google_docs_credentials.
const token = path.join(__dirname, "./credentials/google_docs_token.json");
app.post(RouteStore.googleDocs + ":action", (req, res) => {
- let parameters = req.body;
-
GoogleApiServerUtils.Docs.GetEndpoint({ credentials, token }).then(endpoint => {
- let results: Promise<any> | undefined;
+ let results: Promise<GaxiosResponse> | undefined;
+ let documents = endpoint.documents;
+ let parameters = req.body;
switch (req.params.action) {
case "create":
- results = endpoint.documents.create(parameters).then(generated => generated.data.documentId);
+ results = documents.create(parameters);
break;
case "retrieve":
- results = endpoint.documents.get(parameters).then(response => response.data);
+ results = documents.get(parameters);
+ break;
+ case "update":
+ results = documents.batchUpdate(parameters);
break;
- default:
- results = undefined;
}
- results && results.then(final => res.send(final));
+ !results ? res.send(undefined) : results.then(response => res.send(response.data));
});
});