aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/apis/google_docs/GoogleApiClientUtils.ts189
-rw-r--r--src/client/util/DictationManager.ts66
-rw-r--r--src/client/views/DocumentDecorations.tsx7
-rw-r--r--src/client/views/GlobalKeyHandler.ts2
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx114
-rw-r--r--src/client/views/nodes/VideoBox.tsx6
6 files changed, 258 insertions, 126 deletions
diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index 821c52270..798886def 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -1,4 +1,4 @@
-import { docs_v1 } from "googleapis";
+import { docs_v1, slides_v1 } from "googleapis";
import { PostToServer } from "../../../Utils";
import { RouteStore } from "../../../server/RouteStore";
import { Opt } from "../../../new_fields/Doc";
@@ -9,50 +9,84 @@ export const Pushes = "googleDocsPushCount";
export namespace GoogleApiClientUtils {
- export namespace Docs {
+ export enum Service {
+ Documents = "Documents",
+ Slides = "Slides"
+ }
- export enum Actions {
- Create = "create",
- Retrieve = "retrieve",
- Update = "update"
- }
+ export enum Actions {
+ Create = "create",
+ Retrieve = "retrieve",
+ Update = "update"
+ }
- export enum WriteMode {
- Insert,
- Replace
- }
+ export enum WriteMode {
+ Insert,
+ Replace
+ }
- export type DocumentId = string;
- export type Reference = DocumentId | CreateOptions;
- export type TextContent = string | string[];
- export type IdHandler = (id: DocumentId) => any;
+ export type Identifier = string;
+ export type Reference = Identifier | CreateOptions;
+ export type TextContent = string | string[];
+ export type IdHandler = (id: Identifier) => any;
+ export type CreationResult = Opt<Identifier>;
+ export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>;
+ export type ReadResult = { title?: string, body?: string };
- 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<{ title?: string, bodyLines?: string[] }>;
- export type ReadResult = { title?: string, body?: string };
+ export interface CreateOptions {
+ service: Service;
+ title?: string; // if excluded, will use a default title annotated with the current date
+ }
- export interface CreateOptions {
- handler: IdHandler; // callback to process the documentId of the newly created Google Doc
- title?: string; // if excluded, will use a default title annotated with the current date
- }
+ export interface RetrieveOptions {
+ service: Service;
+ identifier: Identifier;
+ }
- export interface RetrieveOptions {
- documentId: DocumentId;
- }
+ export interface ReadOptions {
+ identifier: Identifier;
+ removeNewlines?: boolean;
+ }
- export type ReadOptions = RetrieveOptions & { removeNewlines?: boolean };
+ export interface WriteOptions {
+ mode: WriteMode;
+ content: TextContent;
+ reference: Reference;
+ index?: number; // if excluded, will compute the last index of the document and append the content there
+ }
- export interface WriteOptions {
- mode: WriteMode;
- content: TextContent;
- reference: Reference;
- index?: number; // if excluded, will compute the last index of the document and append the content there
+ /**
+ * After following the authentication routine, which connects this API call to the current signed in account
+ * and grants the appropriate permissions, this function programmatically creates an arbitrary Google Doc which
+ * should appear in the user's Google Doc library instantaneously.
+ *
+ * @param options the title to assign to the new document, and the information necessary
+ * to store the new documentId returned from the creation process
+ * @returns the documentId of the newly generated document, or undefined if the creation process fails.
+ */
+ export const create = async (options: CreateOptions): Promise<CreationResult> => {
+ const path = `${RouteStore.googleDocs}/${options.service}/${Actions.Create}`;
+ const parameters = {
+ requestBody: {
+ title: options.title || `Dash Export (${new Date().toDateString()})`
+ }
+ };
+ try {
+ const schema: any = await PostToServer(path, parameters);
+ let key = ["document", "presentation"].find(prefix => `${prefix}Id` in schema) + "Id";
+ return schema[key];
+ } catch {
+ return undefined;
}
+ };
+
+ export namespace Docs {
+
+ export type RetrievalResult = Opt<docs_v1.Schema$Document | slides_v1.Schema$Presentation>;
+ export type UpdateResult = Opt<docs_v1.Schema$BatchUpdateDocumentResponse>;
export interface UpdateOptions {
- documentId: DocumentId;
+ documentId: Identifier;
requests: docs_v1.Schema$Request[];
}
@@ -96,46 +130,27 @@ export namespace GoogleApiClientUtils {
}
- /**
- * After following the authentication routine, which connects this API call to the current signed in account
- * and grants the appropriate permissions, this function programmatically creates an arbitrary Google Doc which
- * should appear in the user's Google Doc library instantaneously.
- *
- * @param options the title to assign to the new document, and the information necessary
- * to store the new documentId returned from the creation process
- * @returns the documentId of the newly generated document, or undefined if the creation process fails.
- */
- export const create = async (options: CreateOptions): Promise<CreationResult> => {
- const path = RouteStore.googleDocs + Actions.Create;
- const parameters = {
- requestBody: {
- title: options.title || `Dash Export (${new Date().toDateString()})`
- }
- };
- try {
- const schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
- const generatedId = schema.documentId;
- if (generatedId) {
- options.handler(generatedId);
- return generatedId;
- }
- } catch {
- return undefined;
- }
- };
+ const KeyMapping = new Map<Service, string>([
+ [Service.Documents, "documentId"],
+ [Service.Slides, "presentationId"]
+ ]);
export const retrieve = async (options: RetrieveOptions): Promise<RetrievalResult> => {
- const path = RouteStore.googleDocs + Actions.Retrieve;
+ const path = `${RouteStore.googleDocs}/${options.service}/${Actions.Retrieve}`;
try {
- const schema: RetrievalResult = await PostToServer(path, options);
- return schema;
+ let parameters: any = {}, key: string | undefined;
+ if ((key = KeyMapping.get(options.service))) {
+ parameters[key] = options.identifier;
+ const schema: RetrievalResult = await PostToServer(path, parameters);
+ return schema;
+ }
} catch {
return undefined;
}
};
export const update = async (options: UpdateOptions): Promise<UpdateResult> => {
- const path = RouteStore.googleDocs + Actions.Update;
+ const path = `${RouteStore.googleDocs}/${Service.Documents}/${Actions.Update}`;
const parameters = {
documentId: options.documentId,
requestBody: {
@@ -151,7 +166,7 @@ export namespace GoogleApiClientUtils {
};
export const read = async (options: ReadOptions): Promise<ReadResult> => {
- return retrieve(options).then(document => {
+ return retrieve({ ...options, service: Service.Documents }).then(document => {
let result: ReadResult = {};
if (document) {
let title = document.title;
@@ -163,7 +178,7 @@ export namespace GoogleApiClientUtils {
};
export const readLines = async (options: ReadOptions): Promise<ReadLinesResult> => {
- return retrieve(options).then(document => {
+ return retrieve({ ...options, service: Service.Documents }).then(document => {
let result: ReadLinesResult = {};
if (document) {
let title = document.title;
@@ -177,14 +192,14 @@ export namespace GoogleApiClientUtils {
export const write = async (options: WriteOptions): Promise<UpdateResult> => {
const requests: docs_v1.Schema$Request[] = [];
- const documentId = await Utils.initialize(options.reference);
- if (!documentId) {
+ const identifier = await Utils.initialize(options.reference);
+ if (!identifier) {
return undefined;
}
let index = options.index;
const mode = options.mode;
if (!(index && mode === WriteMode.Insert)) {
- let schema = await retrieve({ documentId });
+ let schema = await retrieve({ identifier, service: Service.Documents });
if (!schema || !(index = Utils.endOf(schema))) {
return undefined;
}
@@ -210,7 +225,7 @@ export namespace GoogleApiClientUtils {
if (!requests.length) {
return undefined;
}
- let replies: any = await update({ documentId, requests });
+ let replies: any = await update({ documentId: identifier, requests });
let errors = "errors";
if (errors in replies) {
console.log("Write operation failed:");
@@ -221,4 +236,36 @@ export namespace GoogleApiClientUtils {
}
+ export namespace Slides {
+
+ export namespace Utils {
+
+ export const extractTextBoxes = (slides: slides_v1.Schema$Page[]) => {
+ slides.map(slide => {
+ let elements = slide.pageElements;
+ if (elements) {
+ let textboxes: slides_v1.Schema$TextContent[] = [];
+ for (let element of elements) {
+ if (element && element.shape && element.shape.shapeType === "TEXT_BOX" && element.shape.text) {
+ textboxes.push(element.shape.text);
+ }
+ }
+ textboxes.map(text => {
+ if (text.textElements) {
+ text.textElements.map(element => {
+
+ });
+ }
+ if (text.lists) {
+
+ }
+ });
+ }
+ });
+ };
+
+ }
+
+ }
+
} \ No newline at end of file
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 488a146bf..fb3c15cea 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -45,7 +45,7 @@ export namespace DictationManager {
export namespace Controls {
- const infringe = "unable to process: dictation manager still involved in previous session";
+ export const Infringed = "unable to process: dictation manager still involved in previous session";
const intraSession = ". ";
const interSession = " ... ";
@@ -64,35 +64,45 @@ export namespace DictationManager {
export type ListeningUIStatus = { interim: boolean } | false;
export interface ListeningOptions {
+ useOverlay: boolean;
language: string;
continuous: ContinuityArgs;
delimiters: DelimiterArgs;
interimHandler: InterimResultHandler;
tryExecute: boolean;
+ terminators: string[];
}
export const listen = async (options?: Partial<ListeningOptions>) => {
let results: string | undefined;
let main = MainView.Instance;
- main.dictationOverlayVisible = true;
- main.isListening = { interim: false };
+ let overlay = options !== undefined && options.useOverlay;
+ if (overlay) {
+ main.dictationOverlayVisible = true;
+ main.isListening = { interim: false };
+ }
try {
results = await listenImpl(options);
if (results) {
Utils.CopyText(results);
- main.isListening = false;
- let execute = options && options.tryExecute;
- main.dictatedPhrase = execute ? results.toLowerCase() : results;
- main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
+ if (overlay) {
+ main.isListening = false;
+ let execute = options && options.tryExecute;
+ main.dictatedPhrase = execute ? results.toLowerCase() : results;
+ main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
+ }
+ options && options.tryExecute && await DictationManager.Commands.execute(results);
}
} catch (e) {
- main.isListening = false;
- main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`;
- main.dictationSuccess = false;
+ if (overlay) {
+ main.isListening = false;
+ main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`;
+ main.dictationSuccess = false;
+ }
} finally {
- main.initiateDictationFade();
+ overlay && main.initiateDictationFade();
}
return results;
@@ -100,7 +110,7 @@ export namespace DictationManager {
const listenImpl = (options?: Partial<ListeningOptions>) => {
if (isListening) {
- return infringe;
+ return Infringed;
}
isListening = true;
@@ -128,6 +138,12 @@ export namespace DictationManager {
recognizer.onresult = (e: SpeechRecognitionEvent) => {
current = synthesize(e, intra);
+ let matchedTerminator: string | undefined;
+ if (options && options.terminators && (matchedTerminator = options.terminators.find(end => current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false))) {
+ current = matchedTerminator;
+ recognizer.abort();
+ return complete();
+ }
handler && handler(current);
isManuallyStopped && complete();
};
@@ -163,13 +179,13 @@ export namespace DictationManager {
}
isManuallyStopped = true;
salvageSession ? recognizer.stop() : recognizer.abort();
- let main = MainView.Instance;
- if (main.dictationOverlayVisible) {
- main.cancelDictationFade();
- main.dictationOverlayVisible = false;
- main.dictationSuccess = undefined;
- setTimeout(() => main.dictatedPhrase = placeholder, 500);
- }
+ // let main = MainView.Instance;
+ // if (main.dictationOverlayVisible) {
+ // main.cancelDictationFade();
+ // main.dictationOverlayVisible = false;
+ // main.dictationSuccess = undefined;
+ // setTimeout(() => main.dictatedPhrase = placeholder, 500);
+ // }
};
const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => {
@@ -301,11 +317,16 @@ export namespace DictationManager {
}
}],
- ["create bulleted note", {
+ ["new outline", {
action: (target: DocumentView) => {
let newBox = Docs.Create.TextDocument({ width: 400, height: 200, title: "My Outline" });
+ newBox.autoHeight = true;
let proto = newBox.proto!;
- let proseMirrorState = '"{"doc":{"type":"doc","content":[{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":""}]}]}]}]},"selection":{"type":"text","anchor":1,"head":1}}"';
+ proto.page = -1;
+ let prompt = "Press alt + r to start dictating here...";
+ let head = 3;
+ let anchor = head + prompt.length;
+ let proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
proto.data = new RichTextField(proseMirrorState);
proto.backgroundColor = "#eeffff";
target.props.addDocTab(newBox, proto, "onRight");
@@ -323,6 +344,9 @@ export namespace DictationManager {
let what = matches[2];
let dataDoc = Doc.GetProto(target.props.Document);
let fieldKey = "data";
+ if (isNaN(count)) {
+ return;
+ }
for (let i = 0; i < count; i++) {
let created: Doc | undefined;
switch (what) {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 891fd7847..a28088032 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -714,7 +714,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField;
let dataDoc = Doc.GetProto(this.targetDoc);
if (!canPull || !dataDoc[GoogleRef]) return (null);
- let icon = !dataDoc.unchanged ? (this.pullIcon as any) : fetch;
+ let icon = dataDoc.unchanged === false ? (this.pullIcon as any) : fetch;
icon = this.openHover ? "share" : icon;
let animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none";
let title = `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`;
@@ -727,10 +727,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
backgroundColor: this.pullColor,
transition: "0.2s ease all"
}}
- onPointerEnter={e => e.ctrlKey && runInAction(() => this.openHover = true)}
+ onPointerEnter={e => e.altKey && runInAction(() => this.openHover = true)}
onPointerLeave={() => runInAction(() => this.openHover = false)}
onClick={e => {
- if (e.ctrlKey) {
+ if (e.altKey) {
+ e.preventDefault();
window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`);
} else {
this.clearPullColor();
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 8a7295c65..d0464bd5f 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -105,7 +105,7 @@ export default class KeyManager {
switch (keyname) {
case " ":
- DictationManager.Controls.listen({ tryExecute: true });
+ DictationManager.Controls.listen({ useOverlay: true, tryExecute: true });
stopPropagation = true;
preventDefault = true;
}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 44a481953..5492a457d 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -6,7 +6,7 @@ import { baseKeymap } from "prosemirror-commands";
import { history } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
import { Fragment, Node, Node as ProsNode, NodeType, Slice } from "prosemirror-model";
-import { EditorState, Plugin, Transaction } from "prosemirror-state";
+import { EditorState, Plugin, Transaction, TextSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../new_fields/DateField';
import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc";
@@ -35,6 +35,8 @@ import React = require("react");
import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
import { DocumentDecorations } from '../DocumentDecorations';
import { MainOverlayTextBox } from '../MainOverlayTextBox';
+import { DictationManager } from '../../util/DictationManager';
+import { ReplaceStep } from 'prosemirror-transform';
library.add(faEdit);
library.add(faSmile, faTextHeight, faUpload);
@@ -62,7 +64,7 @@ export const GoogleRef = "googleDocId";
type RichTextDocument = makeInterface<[typeof richTextSchema]>;
const RichTextDocument = makeInterface(richTextSchema);
-type PullHandler = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => void;
+type PullHandler = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => void;
@observer
export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
@@ -85,7 +87,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
private pushReactionDisposer: Opt<IReactionDisposer>;
private dropDisposer?: DragManager.DragDropDisposer;
public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
- private isGoogleDocsUpdate = false;
@observable _entered = false;
@observable public static InputBoxOverlay?: FormattedTextBox = undefined;
@@ -180,6 +181,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
const marks = tx.storedMarks;
if (marks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(marks); }
}
+
this._applyingChange = true;
const fieldkey = "preview";
if (this.extensionDoc) this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n");
@@ -265,6 +267,64 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
+ recordKeyHandler = (e: KeyboardEvent) => {
+ if (this.props.Document !== SelectionManager.SelectedDocuments()[0].props.Document) {
+ return;
+ }
+ if (e.key === "R" && e.altKey) {
+ e.stopPropagation();
+ e.preventDefault();
+ this.recordBullet();
+ }
+ }
+
+ recordBullet = async () => {
+ let completedCue = "end session";
+ let results = await DictationManager.Controls.listen({
+ interimHandler: this.setCurrentBulletContent,
+ continuous: { indefinite: false },
+ terminators: [completedCue, "bullet", "next"]
+ });
+ if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) {
+ DictationManager.Controls.stop();
+ return;
+ }
+ this.nextBullet(this._editorView!.state.selection.to);
+ setTimeout(this.recordBullet, 2000);
+ }
+
+ setCurrentBulletContent = (value: string) => {
+ if (this._editorView) {
+ let state = this._editorView.state;
+ let from = state.selection.from;
+ let to = state.selection.to;
+ this._editorView.dispatch(state.tr.insertText(value, from, to));
+ state = this._editorView.state;
+ let updated = TextSelection.create(state.doc, from, from + value.length);
+ this._editorView.dispatch(state.tr.setSelection(updated));
+ }
+ }
+
+ nextBullet = (pos: number) => {
+ if (this._editorView) {
+ let frag = Fragment.fromArray(this.newListItems(2));
+ let slice = new Slice(frag, 2, 2);
+ let state = this._editorView.state;
+ this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice)));
+ pos += 4;
+ state = this._editorView.state;
+ this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos)));
+ }
+ }
+
+ private newListItems = (count: number) => {
+ let listItems: any[] = [];
+ for (let i = 0; i < count; i++) {
+ listItems.push(schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create()));
+ }
+ return listItems;
+ }
+
componentDidMount() {
const config = {
schema,
@@ -298,7 +358,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
this.pullFromGoogleDoc(this.checkState);
- runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true);
+ this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true);
this._reactionDisposer = reaction(
() => {
@@ -309,13 +369,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
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.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();
}
}
@@ -378,22 +431,20 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
pushToGoogleDoc = async () => {
- this.pullFromGoogleDoc(async (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => {
- let modes = GoogleApiClientUtils.Docs.WriteMode;
+ this.pullFromGoogleDoc(async (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => {
+ let modes = GoogleApiClientUtils.WriteMode;
let mode = modes.Replace;
- let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string");
+ let reference: Opt<GoogleApiClientUtils.Reference> = Cast(this.dataDoc[GoogleRef], "string");
if (!reference) {
mode = modes.Insert;
- reference = {
- title: StrCast(this.dataDoc.title),
- handler: id => this.dataDoc[GoogleRef] = id
- };
+ reference = { service: GoogleApiClientUtils.Service.Documents, title: StrCast(this.dataDoc.title) };
}
let redo = async () => {
let data = Cast(this.dataDoc.data, RichTextField);
if (this._editorView && reference && data) {
let content = data[ToPlainText]();
let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
+ response && (this.dataDoc[GoogleRef] = response.documentId);
let pushSuccess = response !== undefined && !("errors" in response);
dataDoc.unchanged = pushSuccess;
DocumentDecorations.Instance.startPushOutcome(pushSuccess);
@@ -413,32 +464,38 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
pullFromGoogleDoc = async (handler: PullHandler) => {
let dataDoc = this.dataDoc;
let documentId = StrCast(dataDoc[GoogleRef]);
- let exportState: GoogleApiClientUtils.Docs.ReadResult = {};
+ let exportState: GoogleApiClientUtils.ReadResult = {};
if (documentId) {
- exportState = await GoogleApiClientUtils.Docs.read({ documentId });
+ exportState = await GoogleApiClientUtils.Docs.read({ identifier: documentId });
}
UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
}
- updateState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => {
+ updateState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => {
let pullSuccess = false;
if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) {
- let data = Cast(dataDoc.data, RichTextField);
- if (data) {
+ const data = Cast(dataDoc.data, RichTextField);
+ if (data instanceof RichTextField) {
pullSuccess = true;
- this.isGoogleDocsUpdate = true;
dataDoc.data = new RichTextField(data[FromPlainText](exportState.body));
+ setTimeout(() => {
+ if (this._editorView) {
+ let state = this._editorView.state;
+ let end = state.doc.content.size - 1;
+ this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end)));
+ }
+ }, 0);
dataDoc.title = exportState.title;
+ this.Document.customTitle = true;
dataDoc.unchanged = true;
}
} else {
delete dataDoc[GoogleRef];
}
DocumentDecorations.Instance.startPullOutcome(pullSuccess);
- this.tryUpdateHeight();
}
- checkState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => {
+ checkState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => {
if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) {
let data = Cast(dataDoc.data, RichTextField);
if (data) {
@@ -573,7 +630,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (!this.props.isOverlay) this.props.select(false);
else this._editorView!.focus();
}
- this.tryUpdateHeight();
}
componentWillUnmount() {
@@ -659,6 +715,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
@action
onFocused = (e: React.FocusEvent): void => {
+ document.removeEventListener("keypress", this.recordKeyHandler);
+ document.addEventListener("keypress", this.recordKeyHandler);
+ this.tryUpdateHeight();
if (!this.props.isOverlay) {
FormattedTextBox.InputBoxOverlay = this;
} else {
@@ -708,6 +767,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
});
}
onBlur = (e: any) => {
+ document.removeEventListener("keypress", this.recordKeyHandler);
if (this._undoTyping) {
this._undoTyping.end();
this._undoTyping = undefined;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 704030d85..3f4ee8960 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -34,7 +34,7 @@ library.add(faVideo);
export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) {
private _reactionDisposer?: IReactionDisposer;
private _youtubeReactionDisposer?: IReactionDisposer;
- private _youtubePlayer: any = undefined;
+ private _youtubePlayer: YT.Player | undefined = undefined;
private _videoRef: HTMLVideoElement | null = null;
private _youtubeIframeId: number = -1;
private _youtubeContentCreated = false;
@@ -78,7 +78,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
@action public Pause = (update: boolean = true) => {
this.Playing = false;
update && this.player && this.player.pause();
- update && this._youtubePlayer && this._youtubePlayer.pauseVideo();
+ update && this._youtubePlayer && this._youtubePlayer.pauseVideo && this._youtubePlayer.pauseVideo();
this._youtubePlayer && this._playTimer && clearInterval(this._playTimer);
this._playTimer = undefined;
this.updateTimecode();
@@ -244,7 +244,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
let onYoutubePlayerStateChange = (event: any) => runInAction(() => {
if (started && event.data === YT.PlayerState.PLAYING) {
started = false;
- this._youtubePlayer.unMute();
+ this._youtubePlayer && this._youtubePlayer.unMute();
this.Pause();
return;
}