diff options
Diffstat (limited to 'src/client/apis/google_docs')
-rw-r--r-- | src/client/apis/google_docs/GoogleApiClientUtils.ts | 164 | ||||
-rw-r--r-- | src/client/apis/google_docs/GooglePhotosClientUtils.ts | 91 |
2 files changed, 131 insertions, 124 deletions
diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index c8f381cc0..0b303eacf 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -1,45 +1,46 @@ -import { docs_v1 } from "googleapis"; -import { Opt } from "../../../fields/Doc"; -import { isArray } from "util"; -import { EditorState } from "prosemirror-state"; -import { Networking } from "../../Network"; +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-use-before-define */ +import { docs_v1 as docsV1 } from 'googleapis'; +// eslint-disable-next-line node/no-deprecated-api +import { isArray } from 'util'; +import { EditorState } from 'prosemirror-state'; +import { Opt } from '../../../fields/Doc'; +import { Networking } from '../../Network'; -export const Pulls = "googleDocsPullCount"; -export const Pushes = "googleDocsPushCount"; +export const Pulls = 'googleDocsPullCount'; +export const Pushes = 'googleDocsPushCount'; export namespace GoogleApiClientUtils { - export enum Actions { - Create = "create", - Retrieve = "retrieve", - Update = "update" + Create = 'create', + Retrieve = 'retrieve', + Update = 'update', } export namespace Docs { - - export type RetrievalResult = Opt<docs_v1.Schema$Document>; - export type UpdateResult = Opt<docs_v1.Schema$BatchUpdateDocumentResponse>; + export type RetrievalResult = Opt<docsV1.Schema$Document>; + export type UpdateResult = Opt<docsV1.Schema$BatchUpdateDocumentResponse>; export interface UpdateOptions { documentId: DocumentId; - requests: docs_v1.Schema$Request[]; + requests: docsV1.Schema$Request[]; } export enum WriteMode { Insert, - Replace + Replace, } export type DocumentId = string; export type Reference = DocumentId | CreateOptions; export interface Content { text: string | string[]; - requests: docs_v1.Schema$Request[]; + requests: docsV1.Schema$Request[]; } export type IdHandler = (id: DocumentId) => any; export type CreationResult = Opt<DocumentId>; - export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>; - export type ReadResult = { title: string, body: string }; + export type ReadLinesResult = Opt<{ title?: string; bodyLines?: string[] }>; + export type ReadResult = { title: string; body: string }; export interface ImportResult { title: string; text: string; @@ -67,23 +68,23 @@ 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. - */ + * 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 = `/googleDocs/Documents/${Actions.Create}`; const parameters = { requestBody: { - title: options.title || `Dash Export (${new Date().toDateString()})` - } + title: options.title || `Dash Export (${new Date().toDateString()})`, + }, }; try { - const schema: docs_v1.Schema$Document = await Networking.PostToServer(path, parameters); + const schema: docsV1.Schema$Document = await Networking.PostToServer(path, parameters); return schema.documentId === null ? undefined : schema.documentId; } catch { return undefined; @@ -91,19 +92,25 @@ export namespace GoogleApiClientUtils { }; export namespace Utils { - - export type ExtractResult = { text: string, paragraphs: DeconstructedParagraph[] }; - export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false): ExtractResult => { + export type ExtractResult = { text: string; paragraphs: DeconstructedParagraph[] }; + export const extractText = (document: docsV1.Schema$Document, removeNewlines = false): ExtractResult => { const paragraphs = extractParagraphs(document); - let text = paragraphs.map(paragraph => paragraph.contents.filter(content => !("inlineObjectId" in content)).map(run => (run as docs_v1.Schema$TextRun).content).join("")).join(""); + let text = paragraphs + .map(paragraph => + paragraph.contents + .filter(content => !('inlineObjectId' in content)) + .map(run => (run as docsV1.Schema$TextRun).content) + .join('') + ) + .join(''); text = text.substring(0, text.length - 1); - removeNewlines && text.replace(/\n/g, ""); + removeNewlines && text.replace(/\n/g, ''); return { text, paragraphs }; }; - export type ContentArray = (docs_v1.Schema$TextRun | docs_v1.Schema$InlineObjectElement)[]; - export type DeconstructedParagraph = { contents: ContentArray, bullet: Opt<number> }; - const extractParagraphs = (document: docs_v1.Schema$Document, filterEmpty = true): DeconstructedParagraph[] => { + export type ContentArray = (docsV1.Schema$TextRun | docsV1.Schema$InlineObjectElement)[]; + export type DeconstructedParagraph = { contents: ContentArray; bullet: Opt<number> }; + const extractParagraphs = (document: docsV1.Schema$Document, filterEmpty = true): DeconstructedParagraph[] => { const fragments: DeconstructedParagraph[] = []; if (document.body && document.body.content) { for (const element of document.body.content) { @@ -132,7 +139,7 @@ export namespace GoogleApiClientUtils { return fragments; }; - export const endOf = (schema: docs_v1.Schema$Document): number | undefined => { + export const endOf = (schema: docsV1.Schema$Document): number | undefined => { if (schema.body && schema.body.content) { const paragraphs = schema.body.content.filter(el => el.paragraph); if (paragraphs.length) { @@ -146,10 +153,10 @@ export namespace GoogleApiClientUtils { } } } + return undefined; }; - export const initialize = async (reference: Reference) => typeof reference === "string" ? reference : create(reference); - + export const initialize = async (reference: Reference) => (typeof reference === 'string' ? reference : create(reference)); } export const retrieve = async (options: RetrieveOptions): Promise<RetrievalResult> => { @@ -168,8 +175,8 @@ export namespace GoogleApiClientUtils { const parameters = { documentId: options.documentId, requestBody: { - requests: options.requests - } + requests: options.requests, + }, }; try { const replies: UpdateResult = await Networking.PostToServer(path, parameters); @@ -179,83 +186,84 @@ export namespace GoogleApiClientUtils { } }; - export const read = async (options: ReadOptions): Promise<Opt<ReadResult>> => { - return retrieve({ documentId: options.documentId }).then(document => { + export const read = async (options: ReadOptions): Promise<Opt<ReadResult>> => + retrieve({ documentId: options.documentId }).then(document => { if (document) { const title = document.title!; const body = Utils.extractText(document, options.removeNewlines).text; return { title, body }; } + return undefined; }); - }; - export const readLines = async (options: ReadOptions): Promise<Opt<ReadLinesResult>> => { - return retrieve({ documentId: options.documentId }).then(document => { + export const readLines = async (options: ReadOptions): Promise<Opt<ReadLinesResult>> => + retrieve({ documentId: options.documentId }).then(document => { if (document) { - const title = document.title; - let bodyLines = Utils.extractText(document).text.split("\n"); + const { title } = document; + let bodyLines = Utils.extractText(document).text.split('\n'); options.removeNewlines && (bodyLines = bodyLines.filter(line => line.length)); - return { title: title ?? "", bodyLines }; + return { title: title ?? '', bodyLines }; } + return undefined; }); - }; export const setStyle = async (options: UpdateOptions) => { const replies: any = await update({ documentId: options.documentId, - requests: options.requests + requests: options.requests, }); - if ("errors" in replies) { - console.log("Write operation failed:"); + if ('errors' in replies) { + console.log('Write operation failed:'); console.log(replies.errors.map((error: any) => error.message)); } return replies; }; export const write = async (options: WriteOptions): Promise<UpdateResult> => { - const requests: docs_v1.Schema$Request[] = []; + const requests: docsV1.Schema$Request[] = []; const documentId = await Utils.initialize(options.reference); if (!documentId) { return undefined; } - let index = options.index; - const mode = options.mode; + let { index } = options; + const { mode } = options; if (!(index && mode === WriteMode.Insert)) { const schema = await retrieve({ documentId }); + // eslint-disable-next-line no-cond-assign if (!schema || !(index = Utils.endOf(schema))) { return undefined; } } if (mode === WriteMode.Replace) { - index > 1 && requests.push({ - deleteContentRange: { - range: { - startIndex: 1, - endIndex: index - } - } - }); + index > 1 && + requests.push({ + deleteContentRange: { + range: { + startIndex: 1, + endIndex: index, + }, + }, + }); index = 1; } - const text = options.content.text; - text.length && requests.push({ - insertText: { - text: isArray(text) ? text.join("\n") : text, - location: { index } - } - }); + const { text } = options.content; + text.length && + requests.push({ + insertText: { + text: isArray(text) ? text.join('\n') : text, + location: { index }, + }, + }); if (!requests.length) { return undefined; } requests.push(...options.content.requests); const replies: any = await update({ documentId, requests }); - if ("errors" in replies) { - console.log("Write operation failed:"); + if ('errors' in replies) { + console.log('Write operation failed:'); console.log(replies.errors.map((error: any) => error.message)); } return replies; }; - } - -}
\ No newline at end of file +} diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index e8fd8fb8a..fdc185a8e 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -1,18 +1,19 @@ +/* eslint-disable no-use-before-define */ +import Photos = require('googlephotos'); import { AssertionError } from 'assert'; import { EditorState } from 'prosemirror-state'; +import { ClientUtils } from '../../../ClientUtils'; import { Doc, DocListCastAsync, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { RichTextField } from '../../../fields/RichTextField'; import { RichTextUtils } from '../../../fields/RichTextUtils'; -import { Cast, StrCast } from '../../../fields/Types'; -import { ImageField } from '../../../fields/URLField'; +import { Cast, ImageCast, StrCast } from '../../../fields/Types'; import { MediaItem, NewMediaItemResult } from '../../../server/apis/google/SharedTypes'; -import { Utils } from '../../../Utils'; -import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents'; import { Networking } from '../../Network'; +import { Docs, DocumentOptions } from '../../documents/Documents'; +import { DocUtils } from '../../documents/DocUtils'; import { FormattedTextBox } from '../../views/nodes/formattedText/FormattedTextBox'; import { GoogleAuthenticationManager } from '../GoogleAuthenticationManager'; -import Photos = require('googlephotos'); export namespace GooglePhotos { const endpoint = async () => new Photos(await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken()); @@ -76,17 +77,16 @@ export namespace GooglePhotos { export const CollectionToAlbum = async (options: AlbumCreationOptions): Promise<Opt<AlbumCreationResult>> => { const { collection, title, descriptionKey, tag } = options; const dataDocument = Doc.GetProto(collection); - const images = ((await DocListCastAsync(dataDocument.data)) || []).filter(doc => Cast(doc.data, ImageField)); + const images = ((await DocListCastAsync(dataDocument.data)) || []).filter(doc => ImageCast(doc.data)); if (!images || !images.length) { return undefined; } - const resolved = title ? title : StrCast(collection.title) || `Dash Collection (${collection[Id]}`; + const resolved = title || StrCast(collection.title) || `Dash Collection (${collection[Id]}`; const { id, productUrl } = await Create.Album(resolved); const response = await Transactions.UploadImages(images, { id }, descriptionKey); if (response) { const { results, failed } = response; - let index: Opt<number>; - while ((index = failed.pop()) !== undefined) { + for (let index = failed.pop(); index !== undefined; index = failed.pop()) { Doc.RemoveDocFromList(dataDocument, 'data', images.splice(index, 1)[0]); } const mediaItems: MediaItem[] = results.map(item => item.mediaItem); @@ -97,13 +97,12 @@ export namespace GooglePhotos { for (let i = 0; i < images.length; i++) { const image = Doc.GetProto(images[i]); const mediaItem = mediaItems[i]; - if (!mediaItem) { - continue; + if (mediaItem) { + image.googlePhotosId = mediaItem.id; + image.googlePhotosAlbumUrl = productUrl; + image.googlePhotosUrl = mediaItem.productUrl || mediaItem.baseUrl; + idMapping[mediaItem.id] = image; } - image.googlePhotosId = mediaItem.id; - image.googlePhotosAlbumUrl = productUrl; - image.googlePhotosUrl = mediaItem.productUrl || mediaItem.baseUrl; - idMapping[mediaItem.id] = image; } collection.googlePhotosAlbumUrl = productUrl; collection.googlePhotosIdMapping = idMapping; @@ -111,9 +110,10 @@ export namespace GooglePhotos { await Query.TagChildImages(collection); } collection.albumId = id; - Transactions.AddTextEnrichment(collection, `Find me at ${Utils.prepend(`/doc/${collection[Id]}?sharing=true`)}`); + Transactions.AddTextEnrichment(collection, `Find me at ${ClientUtils.prepend(`/doc/${collection[Id]}?sharing=true`)}`); return { albumId: id, mediaItems }; } + return undefined; }; } @@ -124,7 +124,7 @@ export namespace GooglePhotos { await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(); const response = await Query.ContentSearch(requested); const uploads = await Transactions.WriteMediaItemsToServer(response); - const children = uploads.map((upload: Transactions.UploadInformation) => Docs.Create.ImageDocument(Utils.fileUrl(upload.fileNames.clean) /*, {"data_contentSize":upload.contentSize}*/)); + const children = uploads.map((upload: Transactions.UploadInformation) => Docs.Create.ImageDocument(ClientUtils.fileUrl(upload.fileNames.clean) /* , {"data_contentSize":upload.contentSize} */)); const options = { _width: 500, _height: 500 }; return constructor(children, options); }; @@ -144,7 +144,7 @@ export namespace GooglePhotos { const images = (await DocListCastAsync(collection.data))!.map(Doc.GetProto); images?.forEach(image => tagMapping.set(image[Id], ContentCategories.NONE)); const values = Object.values(ContentCategories).filter(value => value !== ContentCategories.NONE); - for (const value of values) { + values.forEach(async value => { const searched = (await ContentSearch({ included: [value] }))?.mediaItems?.map(({ id }) => id); searched?.forEach(async id => { const image = await Cast(idMapping[id], Doc); @@ -154,7 +154,7 @@ export namespace GooglePhotos { !tags?.includes(value) && tagMapping.set(key, tags + delimiter + value); } }); - } + }); images?.forEach(image => { const concatenated = tagMapping.get(image[Id])!; const tags = concatenated.split(delimiter); @@ -200,9 +200,10 @@ export namespace GooglePhotos { export const AlbumSearch = async (albumId: string, pageSize = 100): Promise<MediaItem[]> => { const photos = await endpoint(); const mediaItems: MediaItem[] = []; - let nextPageTokenStored: Opt<string> = undefined; + let nextPageTokenStored: Opt<string>; const found = 0; do { + // eslint-disable-next-line no-await-in-loop const response: any = await photos.mediaItems.search(albumId, pageSize, nextPageTokenStored); mediaItems.push(...response.mediaItems); nextPageTokenStored = response.nextPageToken; @@ -222,7 +223,7 @@ export namespace GooglePhotos { excluded.length && excluded.forEach(category => contentFilter.addExcludedContentCategories(category)); filters.setContentFilter(contentFilter); - const date = options.date; + const { date } = options; if (date) { const dateFilter = new photos.DateFilter(); if (date instanceof Date) { @@ -240,15 +241,11 @@ export namespace GooglePhotos { }); }; - export const GetImage = async (mediaItemId: string): Promise<Transactions.MediaItem> => { - return (await endpoint()).mediaItems.get(mediaItemId); - }; + export const GetImage = async (mediaItemId: string): Promise<Transactions.MediaItem> => (await endpoint()).mediaItems.get(mediaItemId); } namespace Create { - export const Album = async (title: string) => { - return (await endpoint()).albums.create(title); - }; + export const Album = async (title: string) => (await endpoint()).albums.create(title); } export namespace Transactions { @@ -278,6 +275,7 @@ export namespace GooglePhotos { return enrichmentItem.id; } } + return undefined; }; export const WriteMediaItemsToServer = async (body: { mediaItems: any[] }): Promise<UploadInformation[]> => { @@ -291,9 +289,12 @@ export namespace GooglePhotos { return undefined; } const baseUrls: string[] = await Promise.all( - response.results.map(item => { - return new Promise<string>(resolve => Query.GetImage(item.mediaItem.id).then(item => resolve(item.baseUrl))); - }) + response.results.map( + item => + new Promise<string>(resolve => { + Query.GetImage(item.mediaItem.id).then(itm => resolve(itm.baseUrl)); + }) + ) ); return baseUrls; }; @@ -303,31 +304,29 @@ export namespace GooglePhotos { failed: number[]; } - export const UploadImages = async (sources: Doc[], album?: AlbumReference, descriptionKey = 'caption'): Promise<Opt<ImageUploadResults>> => { + export const UploadImages = async (sources: Doc[], albumIn?: AlbumReference, descriptionKey = 'caption'): Promise<Opt<ImageUploadResults>> => { await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(); - if (album && 'title' in album) { - album = await Create.Album(album.title); - } + const album = albumIn && 'title' in albumIn ? await Create.Album(albumIn.title) : albumIn; const media: MediaInput[] = []; - for (const source of sources) { - const data = Cast(Doc.GetProto(source).data, ImageField); - if (!data) { - return; - } - const url = data.url.href; - const target = Doc.MakeEmbedding(source); - const description = parseDescription(target, descriptionKey); - await DocUtils.makeCustomViewClicked(target, Docs.Create.FreeformDocument); - media.push({ url, description }); - } + sources + .filter(source => ImageCast(Doc.GetProto(source).data)) + .forEach(async source => { + const data = ImageCast(Doc.GetProto(source).data); + const url = data.url.href; + const target = Doc.MakeEmbedding(source); + const description = parseDescription(target, descriptionKey); + await DocUtils.makeCustomViewClicked(target, Docs.Create.FreeformDocument); + media.push({ url, description }); + }); if (media.length) { const results = await Networking.PostToServer('/googlePhotosMediaPost', { media, album }); return results; } + return undefined; }; const parseDescription = (document: Doc, descriptionKey: string) => { - let description: string = Utils.prepend(`/doc/${document[Id]}?sharing=true`); + let description: string = ClientUtils.prepend(`/doc/${document[Id]}?sharing=true`); const target = document[descriptionKey]; if (typeof target === 'string') { description = target; |