aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/google_docs/GoogleApiClientUtils.ts27
-rw-r--r--src/client/views/DocumentDecorations.tsx32
-rw-r--r--src/client/views/collections/CollectionSubView.tsx14
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx74
4 files changed, 98 insertions, 49 deletions
diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index 9d78b632d..55b4a76f8 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -27,8 +27,8 @@ export namespace GoogleApiClientUtils {
export type CreationResult = Opt<DocumentId>;
export type RetrievalResult = Opt<docs_v1.Schema$Document>;
export type UpdateResult = Opt<docs_v1.Schema$BatchUpdateDocumentResponse>;
- export type ReadLinesResult = Opt<string[]>;
- export type ReadResult = Opt<string>;
+ export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>;
+ export type ReadResult = { title?: string, body?: string };
export interface CreateOptions {
handler: IdHandler; // callback to process the documentId of the newly created Google Doc
@@ -148,18 +148,27 @@ export namespace GoogleApiClientUtils {
};
export const read = async (options: ReadOptions): Promise<ReadResult> => {
- return retrieve(options).then(schema => {
- return schema ? Utils.extractText(schema, options.removeNewlines) : undefined;
+ return retrieve(options).then(document => {
+ let result: ReadResult = {};
+ if (document) {
+ let title = document.title;
+ let body = Utils.extractText(document, options.removeNewlines);
+ result = { title, body };
+ }
+ return result;
});
};
export const readLines = async (options: ReadOptions): Promise<ReadLinesResult> => {
- return retrieve(options).then(schema => {
- if (!schema) {
- return undefined;
+ return retrieve(options).then(document => {
+ let result: ReadLinesResult = {};
+ if (document) {
+ let title = document.title;
+ let bodyLines = Utils.extractText(document).split("\n");
+ options.removeNewlines && (bodyLines = bodyLines.filter(line => line.length));
+ result = { title, bodyLines };
}
- const lines = Utils.extractText(schema).split("\n");
- return options.removeNewlines ? lines.filter(line => line.length) : lines;
+ return result;
});
};
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 963722fe3..80d4ecb9b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,6 +1,5 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faLink, faTag } from '@fortawesome/free-solid-svg-icons';
-import * as fa from '@fortawesome/free-brands-svg-icons';
+import { faLink, faTag, faArrowAltCircleDown, faArrowAltCircleUp } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
@@ -37,7 +36,8 @@ export const Flyout = higflyout.default;
library.add(faLink);
library.add(faTag);
-library.add(fa.faGoogleDrive as any);
+library.add(faArrowAltCircleDown);
+library.add(faArrowAltCircleUp);
@observer
export class DocumentDecorations extends React.Component<{}, { value: string }> {
@@ -620,13 +620,28 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
);
}
- considerGoogleDoc = () => {
+ considerGoogleDocsPush = () => {
let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document;
- let canEmbed = thisDoc.data && thisDoc.data instanceof RichTextField;
- if (!canEmbed) return (null);
+ let canPush = thisDoc.data && thisDoc.data instanceof RichTextField;
+ if (!canPush) return (null);
+ return (
+ <div className={"linkButtonWrapper"}>
+ <div title="Push to Google Docs" className="linkButton-linker" onClick={() => thisDoc.pushToGoogleDocsTrigger = !thisDoc.pushToGoogleDocsTrigger}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="arrow-alt-circle-up" size="sm" />
+ </div>
+ </div>
+ );
+ }
+
+ considerGoogleDocsPull = () => {
+ let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document;
+ let canPull = thisDoc.data && thisDoc.data instanceof RichTextField;
+ if (!canPull) return (null);
return (
<div className={"linkButtonWrapper"}>
- <FontAwesomeIcon className="documentdecorations-icon" icon="image" size="sm" />
+ <div title="Pull From Google Docs" className="linkButton-linker" onClick={() => thisDoc.pullFromGoogleDocsTrigger = !thisDoc.pullFromGoogleDocsTrigger}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="arrow-alt-circle-down" size="sm" />
+ </div>
</div>
);
}
@@ -781,7 +796,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
</div>
{this.metadataMenu}
{this.considerEmbed()}
- {this.considerGoogleDoc()}
+ {this.considerGoogleDocsPush()}
+ {this.considerGoogleDocsPull()}
{/* {this.considerTooltip()} */}
</div>
</div >
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 077f3f941..70c0632d1 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -16,7 +16,7 @@ import { DragManager } from "../../util/DragManager";
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DocComponent } from "../DocComponent";
import { FieldViewProps } from "../nodes/FieldView";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
+import { FormattedTextBox, Blank } from "../nodes/FormattedTextBox";
import { CollectionPDFView } from "./CollectionPDFView";
import { CollectionVideoView } from "./CollectionVideoView";
import { CollectionView } from "./CollectionView";
@@ -206,7 +206,17 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, width: 400, height: 315, nativeWidth: 600, nativeHeight: 472.5 }));
return;
}
-
+ let matches: RegExpExecArray | null;
+ if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
+ let newBox = Docs.Create.TextDocument({ ...options, width: 600, height: 400, title: "Fetching title from Google Docs..." });
+ let proto = newBox.proto!;
+ proto.autoHeight = true;
+ proto.googleDocId = matches[2];
+ proto.data = "Fetching contents from Google Docs...";
+ proto.backgroundColor = "#eeeeff";
+ this.props.addDocument(newBox);
+ return;
+ }
let batch = UndoManager.StartBatch("collection view drop");
let promises: Promise<void>[] = [];
// tslint:disable-next-line:prefer-for-of
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index bc057bb5f..406c2c4a6 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,6 +1,6 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
-import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace } from "mobx";
+import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace, untracked } from "mobx";
import { observer } from "mobx-react";
import { baseKeymap } from "prosemirror-commands";
import { history } from "prosemirror-history";
@@ -47,6 +47,8 @@ library.add(faSmile, faTextHeight, faUpload);
// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
//
+export const Blank = `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`;
+
export interface FormattedTextBoxProps {
isOverlay?: boolean;
hideOnLeave?: boolean;
@@ -84,7 +86,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
private _proxyReactionDisposer: Opt<IReactionDisposer>;
private dropDisposer?: DragManager.DragDropDisposer;
public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
- private isOpening = false;
+ private isGoogleDocsUpdate = false;
@observable _entered = false;
@observable public static InputBoxOverlay?: FormattedTextBox = undefined;
@@ -290,28 +292,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
- componentWillMount() {
- this.pollExportedCounterpart();
- }
-
- pollExportedCounterpart = async () => {
- let dataDoc = Doc.GetProto(this.props.Document);
- let documentId = StrCast(dataDoc[googleDocId]);
- if (documentId) {
- let exportState = await GoogleApiClientUtils.Docs.read({ documentId });
- if (exportState) {
- let data = Cast(dataDoc.data, RichTextField);
- if (data) {
- this.isOpening = true;
- dataDoc.data = new RichTextField(data[FromGoogleDocText](exportState));
- }
- } else {
- delete dataDoc[googleDocId];
- }
- this.tryUpdateHeight();
- }
- }
-
componentDidMount() {
const config = {
schema,
@@ -347,23 +327,32 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
this._reactionDisposer = reaction(
() => {
const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined;
- return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`;
+ return field ? field.Data : Blank;
},
incomingValue => {
if (this._editorView && !this._applyingChange) {
let updatedState = JSON.parse(incomingValue);
this._editorView.updateState(EditorState.fromJSON(config, updatedState));
// manually sets cursor selection at the end of the text on focus
- if (this.isOpening) {
- this.isOpening = false;
+ if (this.isGoogleDocsUpdate) {
+ this.isGoogleDocsUpdate = false;
let end = this._editorView.state.doc.content.size - 1;
updatedState.selection = { type: "text", anchor: end, head: end };
this._editorView.updateState(EditorState.fromJSON(config, updatedState));
}
+ this.tryUpdateHeight();
}
}
);
+ reaction(() => this.props.Document.pullFromGoogleDocsTrigger, () => {
+ this.pullFromGoogleDoc();
+ });
+
+ reaction(() => this.props.Document.pushToGoogleDocsTrigger, () => {
+ this.pushToGoogleDoc();
+ });
+
this._textReactionDisposer = reaction(
() => this.extensionDoc,
() => {
@@ -391,6 +380,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
this.unhighlightSearchTerms();
}
}, { fireImmediately: true });
+
+ ["pushToGoogleDocsTrigger", "pullFromGoogleDocsTrigger"].map(key => {
+ let doc = this.props.Document;
+ if (doc[key] === undefined) {
+ untracked(() => doc[key] = false);
+ }
+ });
}
clipboardTextSerializer = (slice: Slice): string => {
@@ -644,7 +640,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
this._undoTyping.end();
this._undoTyping = undefined;
}
- this.updateGoogleDoc();
}
public _undoTyping?: UndoManager.Batch;
onKeyPress = (e: React.KeyboardEvent) => {
@@ -701,13 +696,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (!(googleDocId in Doc.GetProto(this.props.Document))) {
ContextMenu.Instance.addItem({
description: "Export to Google Doc...",
- event: this.updateGoogleDoc,
+ event: this.pushToGoogleDoc,
icon: "upload"
});
}
}
- updateGoogleDoc = () => {
+ pushToGoogleDoc = () => {
let modes = GoogleApiClientUtils.Docs.WriteMode;
let mode = modes.Replace;
let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[googleDocId], "string");
@@ -725,6 +720,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
+ pullFromGoogleDoc = async () => {
+ let dataDoc = Doc.GetProto(this.props.Document);
+ let documentId = StrCast(dataDoc[googleDocId]);
+ if (documentId) {
+ let exportState = await GoogleApiClientUtils.Docs.read({ documentId });
+ if (exportState && exportState.body && exportState.title) {
+ let data = Cast(dataDoc.data, RichTextField);
+ if (data) {
+ this.isGoogleDocsUpdate = true;
+ dataDoc.data = new RichTextField(data[FromGoogleDocText](exportState.body));
+ dataDoc.title = exportState.title;
+ }
+ } else {
+ delete dataDoc[googleDocId];
+ }
+ }
+ }
+
+
render() {
let self = this;
let style = this.props.isOverlay ? "scroll" : "hidden";