aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSam Wilkins <samwilkins333@gmail.com>2019-08-19 14:58:02 -0400
committerSam Wilkins <samwilkins333@gmail.com>2019-08-19 14:58:02 -0400
commit7ea6b44b10e1bf23287ba33e0081cacbfc595780 (patch)
tree2c8269dddb06bd2ed956bbf8d7f624504cdc2660 /src
parent6592660b944d745b7368f478cd9f5d9e778537ed (diff)
seemed to fix syncing with remote google doc
Diffstat (limited to 'src')
-rw-r--r--src/client/views/DocumentDecorations.tsx14
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx41
-rw-r--r--src/new_fields/RichTextField.ts52
3 files changed, 63 insertions, 44 deletions
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6616d5d58..963722fe3 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,5 +1,6 @@
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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
@@ -36,6 +37,7 @@ export const Flyout = higflyout.default;
library.add(faLink);
library.add(faTag);
+library.add(fa.faGoogleDrive as any);
@observer
export class DocumentDecorations extends React.Component<{}, { value: string }> {
@@ -618,6 +620,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
);
}
+ considerGoogleDoc = () => {
+ let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document;
+ let canEmbed = thisDoc.data && thisDoc.data instanceof RichTextField;
+ if (!canEmbed) return (null);
+ return (
+ <div className={"linkButtonWrapper"}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon="image" size="sm" />
+ </div>
+ );
+ }
+
considerTooltip = () => {
let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document;
let isTextDoc = thisDoc.data && thisDoc.data instanceof RichTextField;
@@ -768,6 +781,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
</div>
{this.metadataMenu}
{this.considerEmbed()}
+ {this.considerGoogleDoc()}
{/* {this.considerTooltip()} */}
</div>
</div >
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index ad29ce775..bc057bb5f 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -60,7 +60,7 @@ const richTextSchema = createSchema({
documentText: "string"
});
-const googleDocKey = "googleDocId";
+const googleDocId = "googleDocId";
type RichTextDocument = makeInterface<[typeof richTextSchema]>;
const RichTextDocument = makeInterface(richTextSchema);
@@ -84,6 +84,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;
@observable _entered = false;
@observable public static InputBoxOverlay?: FormattedTextBox = undefined;
@@ -295,17 +296,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
pollExportedCounterpart = async () => {
let dataDoc = Doc.GetProto(this.props.Document);
- let documentId = StrCast(dataDoc[googleDocKey]);
+ 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[googleDocKey];
+ delete dataDoc[googleDocId];
}
+ this.tryUpdateHeight();
}
}
@@ -346,9 +349,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
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}}`;
},
- field2 => {
- this._editorView && !this._applyingChange &&
- this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field2)));
+ 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;
+ let end = this._editorView.state.doc.content.size - 1;
+ updatedState.selection = { type: "text", anchor: end, head: end };
+ this._editorView.updateState(EditorState.fromJSON(config, updatedState));
+ }
+ }
}
);
@@ -686,7 +698,7 @@ 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))) {
+ if (!(googleDocId in Doc.GetProto(this.props.Document))) {
ContextMenu.Instance.addItem({
description: "Export to Google Doc...",
event: this.updateGoogleDoc,
@@ -698,21 +710,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
updateGoogleDoc = () => {
let modes = GoogleApiClientUtils.Docs.WriteMode;
let mode = modes.Replace;
- let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[googleDocKey], "string");
+ let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[googleDocId], "string");
if (!reference) {
mode = modes.Insert;
reference = {
title: StrCast(this.dataDoc.title),
- handler: id => this.dataDoc[googleDocKey] = id
+ handler: id => this.dataDoc[googleDocId] = id
};
}
- const data = Cast(this.dataDoc.data, RichTextField);
- if (data) {
- GoogleApiClientUtils.Docs.write({
- mode,
- content: data[ToGoogleDocText](),
- reference
- });
+ if (this._editorView) {
+ let data = Cast(this.dataDoc.data, RichTextField);
+ let content = data ? data[ToGoogleDocText]() : this._editorView.state.doc.textContent;
+ GoogleApiClientUtils.Docs.write({ reference, content, mode });
}
}
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index 8963682c3..ec08293e9 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -6,6 +6,8 @@ import { scriptingGlobal } from "../client/util/Scripting";
export const ToGoogleDocText = Symbol("PlainText");
export const FromGoogleDocText = Symbol("PlainText");
+const delimiter = "\n";
+const joiner = "";
@scriptingGlobal
@Deserializable("RichTextField")
@@ -27,45 +29,39 @@ export class RichTextField extends ObjectField {
}
[ToGoogleDocText]() {
- let state = JSON.parse(this.Data);
- let text = state.doc.textBetween(0, state.doc.content.size, "\n\n");
- console.log(text);
+ // Because we're working with plain text, just concatenate all paragraphs
let content = JSON.parse(this.Data).doc.content;
let paragraphs = content.filter((item: any) => item.type === "paragraph");
- let output = "";
- for (let i = 0; i < paragraphs.length; i++) {
- let paragraph = paragraphs[i];
- let addNewLine = i > 0 ? paragraphs[i - 1].content : false;
- if (paragraph.content) {
- output += paragraph.content.map((block: any) => block.text).join("");
- } else {
- output += i === 0 ? "" : "\n";
- }
- addNewLine && (output += "\n");
- }
- return output;
+
+ // Functions to flatten ProseMirror paragraph objects (and their components) to plain text
+ // While this function already exists in state.doc.textBeteen(), it doesn't account for newlines
+ let blockText = (block: any) => block.text;
+ let concatenateParagraph = (p: any) => (p.content ? p.content.map(blockText).join(joiner) : "") + delimiter;
+
+ // Concatentate paragraphs and string the result together. Trim the last newline, an artifact.
+ let textParagraphs = paragraphs.map(concatenateParagraph);
+ return textParagraphs.join(joiner).trimEnd(delimiter);
}
[FromGoogleDocText](plainText: string) {
- let elements = plainText.split("\n");
+ // Remap the text, creating blocks split on newlines
+ let elements = plainText.split(delimiter);
+
+ // Google Docs adds in an extra carriage return automatically, so this counteracts it
!elements[elements.length - 1].length && elements.pop();
+
+ // Preserve the current state, but re-write the content to be the blocks
let parsed = JSON.parse(this.Data);
parsed.doc.content = elements.map(text => {
let paragraph: any = { type: "paragraph" };
- if (text.length) {
- paragraph.content = [{
- type: "text",
- marks: [],
- text
- }];
- }
+ text.length && (paragraph.content = [{ type: "text", marks: [], text }]); // An empty paragraph gets treated as a line break
return paragraph;
});
- parsed.selection = {
- type: "text",
- anchor: 1,
- head: 1
- };
+
+ // If the new content is shorter than the previous content and selection is unchanged, may throw an out of bounds exception, so we reset it
+ parsed.selection = { type: "text", anchor: 1, head: 1 };
+
+ // Export the ProseMirror-compatible state object we've jsut built
return JSON.stringify(parsed);
}