aboutsummaryrefslogtreecommitdiff
path: root/src/fields/RichTextUtils.ts
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-05-14 23:15:24 -0400
committerbobzel <zzzman@gmail.com>2024-05-14 23:15:24 -0400
commit3534aaf88a3c30a474b3b5a5b7f04adfe6f15fac (patch)
tree47fb7a8671b209bd4d76e0f755a5b035c6936607 /src/fields/RichTextUtils.ts
parent87bca251d87b5a95da06b2212400ce9427152193 (diff)
parent5cb7ad90e120123ca572e8ef5b1aa6ca41581134 (diff)
Merge branch 'restoringEslint' into sarah-ai-visualization
Diffstat (limited to 'src/fields/RichTextUtils.ts')
-rw-r--r--src/fields/RichTextUtils.ts212
1 files changed, 113 insertions, 99 deletions
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index b84a91709..3763dcd2c 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -1,21 +1,25 @@
+/* eslint-disable no-await-in-loop */
+/* eslint-disable no-use-before-define */
import { AssertionError } from 'assert';
-import { docs_v1 } from 'googleapis';
+import * as Color from 'color';
+import { docs_v1 as docsV1 } from 'googleapis';
import { Fragment, Mark, Node } from 'prosemirror-model';
import { sinkListItem } from 'prosemirror-schema-list';
import { EditorState, TextSelection, Transaction } from 'prosemirror-state';
-import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils';
-import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils';
+import { ClientUtils, DashColor } from '../ClientUtils';
+import { Utils } from '../Utils';
import { DocServer } from '../client/DocServer';
-import { Docs, DocUtils } from '../client/documents/Documents';
import { Networking } from '../client/Network';
+import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils';
+import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils';
+import { Docs } from '../client/documents/Documents';
+import { DocUtils } from '../client/documents/DocUtils';
import { FormattedTextBox } from '../client/views/nodes/formattedText/FormattedTextBox';
import { schema } from '../client/views/nodes/formattedText/schema_rts';
-import { DashColor, Utils } from '../Utils';
import { Doc, Opt } from './Doc';
import { Id } from './FieldSymbols';
import { RichTextField } from './RichTextField';
import { Cast, StrCast } from './Types';
-import * as Color from 'color';
export namespace RichTextUtils {
const delimiter = '\n';
@@ -47,13 +51,11 @@ export namespace RichTextUtils {
return JSON.stringify(state);
};
- export const Synthesize = (plainText: string, oldState?: RichTextField) => {
- return new RichTextField(ToProsemirrorState(plainText, oldState), plainText);
- };
+ export const Synthesize = (plainText: string, oldState?: RichTextField) => new RichTextField(ToProsemirrorState(plainText, oldState), plainText);
export const ToPlainText = (state: EditorState) => {
// Because we're working with plain text, just concatenate all paragraphs
- const content = state.doc.content;
+ const { content } = state.doc;
const paragraphs: Node[] = [];
content.forEach(node => node.type.name === 'paragraph' && paragraphs.push(node));
@@ -112,9 +114,9 @@ export namespace RichTextUtils {
agnostic: string;
}
- const parseInlineObjects = async (document: docs_v1.Schema$Document): Promise<Map<string, ImageTemplate>> => {
+ const parseInlineObjects = async (document: docsV1.Schema$Document): Promise<Map<string, ImageTemplate>> => {
const inlineObjectMap = new Map<string, ImageTemplate>();
- const inlineObjects = document.inlineObjects;
+ const { inlineObjects } = document;
if (inlineObjects) {
const objects = Object.keys(inlineObjects).map(objectId => inlineObjects[objectId]);
@@ -140,8 +142,8 @@ export namespace RichTextUtils {
inlineObjectMap.set(object.objectId!, {
title: embeddedObject.title || `Imported Image from ${document.title}`,
width,
- url: Utils.prepend(_m.client),
- agnostic: Utils.prepend(agnostic.client),
+ url: ClientUtils.prepend(_m.client),
+ agnostic: ClientUtils.prepend(agnostic.client),
});
}
}
@@ -162,7 +164,7 @@ export namespace RichTextUtils {
const inlineObjectMap = await parseInlineObjects(document);
const title = document.title!;
const { text, paragraphs } = GoogleApiClientUtils.Docs.Utils.extractText(document);
- let state = FormattedTextBox.blankState();
+ let state = EditorState.create(new FormattedTextBox({} as any).config);
const structured = parseLists(paragraphs);
let position = 3;
@@ -170,10 +172,12 @@ export namespace RichTextUtils {
const indentMap = new Map<ListGroup, BulletPosition[]>();
let globalOffset = 0;
const nodes: Node[] = [];
+ // eslint-disable-next-line no-restricted-syntax
for (const element of structured) {
if (Array.isArray(element)) {
lists.push(element);
const positions: BulletPosition[] = [];
+ // eslint-disable-next-line no-loop-func
const items = element.map(paragraph => {
const item = listItem(state.schema, paragraph.contents);
const sinks = paragraph.bullet!;
@@ -187,41 +191,42 @@ export namespace RichTextUtils {
});
indentMap.set(element, positions);
nodes.push(list(state.schema, items));
+ } else if (element.contents.some(child => 'inlineObjectId' in child)) {
+ const group = element.contents;
+ // eslint-disable-next-line no-loop-func
+ group.forEach((child, i) => {
+ let node: Opt<Node>;
+ if ('inlineObjectId' in child) {
+ node = imageNode(state.schema, inlineObjectMap.get(child.inlineObjectId!)!, textNote);
+ } else if ('content' in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) {
+ node = paragraphNode(state.schema, [child]);
+ }
+ if (node) {
+ position += node.nodeSize;
+ nodes.push(node);
+ }
+ });
} else {
- if (element.contents.some(child => 'inlineObjectId' in child)) {
- const group = element.contents;
- group.forEach((child, i) => {
- let node: Opt<Node>;
- if ('inlineObjectId' in child) {
- node = imageNode(state.schema, inlineObjectMap.get(child.inlineObjectId!)!, textNote);
- } else if ('content' in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) {
- node = paragraphNode(state.schema, [child]);
- }
- if (node) {
- position += node.nodeSize;
- nodes.push(node);
- }
- });
- } else {
- const paragraph = paragraphNode(state.schema, element.contents);
- nodes.push(paragraph);
- position += paragraph.nodeSize;
- }
+ const paragraph = paragraphNode(state.schema, element.contents);
+ nodes.push(paragraph);
+ position += paragraph.nodeSize;
}
}
state = state.apply(state.tr.replaceWith(0, 2, nodes));
const sink = sinkListItem(state.schema.nodes.list_item);
- const dispatcher = (tr: Transaction) => (state = state.apply(tr));
- for (const list of lists) {
- for (const pos of indentMap.get(list)!) {
+ const dispatcher = (tr: Transaction) => {
+ state = state.apply(tr);
+ };
+ lists.forEach(list => {
+ indentMap.get(list)?.forEach(pos => {
const resolved = state.doc.resolve(pos.value);
state = state.apply(state.tr.setSelection(new TextSelection(resolved)));
for (let i = 0; i < pos.sinks; i++) {
sink(state, dispatcher);
}
- }
- }
+ });
+ });
return { title, text, state };
};
@@ -233,7 +238,7 @@ export namespace RichTextUtils {
const parseLists = (paragraphs: ListGroup) => {
const groups: PreparedParagraphs = [];
let group: ListGroup = [];
- for (const paragraph of paragraphs) {
+ paragraphs.forEach(paragraph => {
if (paragraph.bullet !== undefined) {
group.push(paragraph);
} else {
@@ -243,26 +248,22 @@ export namespace RichTextUtils {
}
groups.push(paragraph);
}
- }
+ });
group.length && groups.push(group);
return groups;
};
- const listItem = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => {
- return schema.node('list_item', null, paragraphNode(schema, runs));
- };
+ const listItem = (lschema: any, runs: docsV1.Schema$TextRun[]): Node => lschema.node('list_item', null, paragraphNode(lschema, runs));
- const list = (schema: any, items: Node[]): Node => {
- return schema.node('ordered_list', { mapStyle: 'bullet' }, items);
- };
+ const list = (lschema: any, items: Node[]): Node => lschema.node('ordered_list', { mapStyle: 'bullet' }, items);
- const paragraphNode = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => {
- const children = runs.map(run => textNode(schema, run)).filter(child => child !== undefined);
+ const paragraphNode = (lschema: any, runs: docsV1.Schema$TextRun[]): Node => {
+ const children = runs.map(run => textNode(lschema, run)).filter(child => child !== undefined);
const fragment = children.length ? Fragment.from(children) : undefined;
- return schema.node('paragraph', null, fragment);
+ return lschema.node('paragraph', null, fragment);
};
- const imageNode = (schema: any, image: ImageTemplate, textNote: Doc) => {
+ const imageNode = (lschema: any, image: ImageTemplate, textNote: Doc) => {
const { url: src, width, agnostic } = image;
let docId: string;
const guid = Utils.GenerateDeterministicGuid(agnostic);
@@ -275,30 +276,30 @@ export namespace RichTextUtils {
} else {
docId = backingDocId;
}
- return schema.node('image', { src, agnostic, width, docId, float: null });
+ return lschema.node('image', { src, agnostic, width, docId, float: null });
};
- const textNode = (schema: any, run: docs_v1.Schema$TextRun) => {
+ const textNode = (lschema: any, run: docsV1.Schema$TextRun) => {
const text = run.content!.removeTrailingNewlines();
- return text.length ? schema.text(text, styleToMarks(schema, run.textStyle)) : undefined;
+ return text.length ? lschema.text(text, styleToMarks(lschema, run.textStyle)) : undefined;
};
- const StyleToMark = new Map<keyof docs_v1.Schema$TextStyle, keyof typeof schema.marks>([
+ const StyleToMark = new Map<keyof docsV1.Schema$TextStyle, keyof typeof schema.marks>([
['bold', 'strong'],
['italic', 'em'],
['foregroundColor', 'pFontColor'],
['fontSize', 'pFontSize'],
]);
- const styleToMarks = (schema: any, textStyle?: docs_v1.Schema$TextStyle) => {
+ const styleToMarks = (lschema: any, textStyle?: docsV1.Schema$TextStyle) => {
if (!textStyle) {
return undefined;
}
const marks: Mark[] = [];
Object.keys(textStyle).forEach(key => {
- let value: any;
- const targeted = key as keyof docs_v1.Schema$TextStyle;
- if ((value = textStyle[targeted])) {
+ const targeted = key as keyof docsV1.Schema$TextStyle;
+ const value = textStyle[targeted] as any;
+ if (value) {
const attributes: any = {};
let converted = StyleToMark.get(targeted) || targeted;
@@ -315,20 +316,20 @@ export namespace RichTextUtils {
converted = ImportFontFamilyMapping.get(value.fontFamily) || 'timesNewRoman';
}
- const mapped = schema.marks[converted];
+ const mapped = lschema.marks[converted];
if (!mapped) {
alert(`No mapping found for ${converted}!`);
return;
}
- const mark = schema.mark(mapped, attributes);
+ const mark = lschema.mark(mapped, attributes);
mark && marks.push(mark);
}
});
return marks;
};
- const MarkToStyle = new Map<keyof typeof schema.marks, keyof docs_v1.Schema$TextStyle>([
+ const MarkToStyle = new Map<keyof typeof schema.marks, keyof docsV1.Schema$TextStyle>([
['strong', 'bold'],
['em', 'italic'],
['pFontColor', 'foregroundColor'],
@@ -360,16 +361,18 @@ export namespace RichTextUtils {
const ignored = ['user_mark'];
- const marksToStyle = async (nodes: (Node | null)[]): Promise<docs_v1.Schema$Request[]> => {
- const requests: docs_v1.Schema$Request[] = [];
+ const marksToStyle = async (nodes: (Node | null)[]): Promise<docsV1.Schema$Request[]> => {
+ const requests: docsV1.Schema$Request[] = [];
let position = 1;
+ // eslint-disable-next-line no-restricted-syntax
for (const node of nodes) {
if (node === null) {
position += 2;
+ // eslint-disable-next-line no-continue
continue;
}
const { marks, attrs, nodeSize } = node;
- const textStyle: docs_v1.Schema$TextStyle = {};
+ const textStyle: docsV1.Schema$TextStyle = {};
const information: LinkInformation = {
startIndex: position,
endIndex: position + nodeSize,
@@ -377,36 +380,45 @@ export namespace RichTextUtils {
};
let mark: Mark;
const markMap = BuildMarkMap(marks);
+ // eslint-disable-next-line no-restricted-syntax
for (const markName of Object.keys(schema.marks)) {
+ // eslint-disable-next-line no-cond-assign
if (ignored.includes(markName) || !(mark = markMap[markName])) {
+ // eslint-disable-next-line no-continue
continue;
}
- let converted = MarkToStyle.get(markName) || (markName as keyof docs_v1.Schema$TextStyle);
+ let converted = MarkToStyle.get(markName) || (markName as keyof docsV1.Schema$TextStyle);
let value: any = true;
if (!converted) {
+ // eslint-disable-next-line no-continue
continue;
}
+ // eslint-disable-next-line @typescript-eslint/no-shadow
const { attrs } = mark;
switch (converted) {
case 'link':
- let url = attrs.allLinks.length ? attrs.allLinks[0].href : '';
- const delimiter = '/doc/';
- const alreadyShared = '?sharing=true';
- if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) {
- const linkDoc = await DocServer.GetRefField(url.split(delimiter)[1]);
- if (linkDoc instanceof Doc) {
- let exported = (await Cast(linkDoc.link_anchor_2, Doc))!;
- if (!exported.customLayout) {
- exported = Doc.MakeEmbedding(exported);
- DocUtils.makeCustomViewClicked(exported, Docs.Create.FreeformDocument);
- linkDoc.link_anchor_2 = exported;
+ {
+ let url = attrs.allLinks.length ? attrs.allLinks[0].href : '';
+ const docDelimeter = '/doc/';
+ const alreadyShared = '?sharing=true';
+ if (new RegExp(window.location.origin + docDelimeter).test(url) && !url.endsWith(alreadyShared)) {
+ // eslint-disable-next-line no-await-in-loop
+ const linkDoc = await DocServer.GetRefField(url.split(docDelimeter)[1]);
+ if (linkDoc instanceof Doc) {
+ // eslint-disable-next-line no-await-in-loop
+ let exported = (await Cast(linkDoc.link_anchor_2, Doc))!;
+ if (!exported.customLayout) {
+ exported = Doc.MakeEmbedding(exported);
+ DocUtils.makeCustomViewClicked(exported, Docs.Create.FreeformDocument);
+ linkDoc.link_anchor_2 = exported;
+ }
+ url = ClientUtils.shareUrl(exported[Id]);
}
- url = Utils.shareUrl(exported[Id]);
}
+ value = { url };
+ textStyle.foregroundColor = fromRgb.blue;
+ textStyle.bold = true;
}
- value = { url };
- textStyle.foregroundColor = fromRgb.blue;
- textStyle.bold = true;
break;
case 'fontSize':
value = { magnitude: attrs.fontSize, unit: 'PT' };
@@ -416,9 +428,11 @@ export namespace RichTextUtils {
break;
case 'weightedFontFamily':
value = { fontFamily: ExportFontFamilyMapping.get(markName) };
+ break;
+ default:
}
- let matches: RegExpExecArray | null;
- if ((matches = /p(\d+)/g.exec(markName)) !== null) {
+ const matches = /p(\d+)/g.exec(markName);
+ if (matches !== null) {
converted = 'fontSize';
value = { magnitude: parseInt(matches[1].replace('px', '')), unit: 'PT' };
}
@@ -428,7 +442,7 @@ export namespace RichTextUtils {
requests.push(EncodeStyleUpdate(information));
}
if (node.type.name === 'image') {
- const width = attrs.width;
+ const { width } = attrs;
requests.push(
await EncodeImage({
startIndex: position + nodeSize - 1,
@@ -444,14 +458,16 @@ export namespace RichTextUtils {
const BuildMarkMap = (marks: readonly Mark[]) => {
const markMap: { [type: string]: Mark } = {};
- marks.forEach(mark => (markMap[mark.type.name] = mark));
+ marks.forEach(mark => {
+ markMap[mark.type.name] = mark;
+ });
return markMap;
};
interface LinkInformation {
startIndex: number;
endIndex: number;
- textStyle: docs_v1.Schema$TextStyle;
+ textStyle: docsV1.Schema$TextStyle;
}
interface ImageInformation {
@@ -461,36 +477,34 @@ export namespace RichTextUtils {
}
namespace fromRgb {
- export const convert = (red: number, green: number, blue: number): docs_v1.Schema$OptionalColor => {
- return {
- color: {
- rgbColor: {
- red: red / 255,
- green: green / 255,
- blue: blue / 255,
- },
+ export const convert = (red: number, green: number, blue: number): docsV1.Schema$OptionalColor => ({
+ color: {
+ rgbColor: {
+ red: red / 255,
+ green: green / 255,
+ blue: blue / 255,
},
- };
- };
+ },
+ });
export const red = convert(255, 0, 0);
export const green = convert(0, 255, 0);
export const blue = convert(0, 0, 255);
}
- const fromHex = (color: string): docs_v1.Schema$OptionalColor => {
+ const fromHex = (color: string): docsV1.Schema$OptionalColor => {
const c = DashColor(color);
return fromRgb.convert(c.red(), c.green(), c.blue());
};
- const EncodeStyleUpdate = (information: LinkInformation): docs_v1.Schema$Request => {
+ const EncodeStyleUpdate = (information: LinkInformation): docsV1.Schema$Request => {
const { startIndex, endIndex, textStyle } = information;
return {
updateTextStyle: {
fields: '*',
range: { startIndex, endIndex },
textStyle,
- } as docs_v1.Schema$UpdateTextStyleRequest,
+ } as docsV1.Schema$UpdateTextStyleRequest,
};
};