aboutsummaryrefslogtreecommitdiff
path: root/src/fields/RichTextUtils.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/fields/RichTextUtils.ts')
-rw-r--r--src/fields/RichTextUtils.ts230
1 files changed, 113 insertions, 117 deletions
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index a19be5df9..bf055cd8b 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -1,48 +1,46 @@
-import { AssertionError } from "assert";
-import { docs_v1 } from "googleapis";
-import { Fragment, Mark, Node } from "prosemirror-model";
-import { sinkListItem } from "prosemirror-schema-list";
-import { Utils, DashColor } from "../Utils";
-import { Docs, DocUtils } from "../client/documents/Documents";
-import { schema } from "../client/views/nodes/formattedText/schema_rts";
-import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils";
-import { DocServer } from "../client/DocServer";
-import { Networking } from "../client/Network";
-import { FormattedTextBox } from "../client/views/nodes/formattedText/FormattedTextBox";
-import { Doc, Opt } from "./Doc";
-import { Id } from "./FieldSymbols";
-import { RichTextField } from "./RichTextField";
-import { Cast, StrCast } from "./Types";
+import { AssertionError } from 'assert';
+import { docs_v1 } from 'googleapis';
+import { Fragment, Mark, Node } from 'prosemirror-model';
+import { sinkListItem } from 'prosemirror-schema-list';
+import { Utils, DashColor } from '../Utils';
+import { Docs, DocUtils } from '../client/documents/Documents';
+import { schema } from '../client/views/nodes/formattedText/schema_rts';
+import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils';
+import { DocServer } from '../client/DocServer';
+import { Networking } from '../client/Network';
+import { FormattedTextBox } from '../client/views/nodes/formattedText/FormattedTextBox';
+import { Doc, Opt } from './Doc';
+import { Id } from './FieldSymbols';
+import { RichTextField } from './RichTextField';
+import { Cast, StrCast } from './Types';
import Color = require('color');
-import { EditorState, TextSelection, Transaction } from "prosemirror-state";
-import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils";
+import { EditorState, TextSelection, Transaction } from 'prosemirror-state';
+import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils';
export namespace RichTextUtils {
-
- const delimiter = "\n";
- const joiner = "";
-
+ const delimiter = '\n';
+ const joiner = '';
export const Initialize = (initial?: string) => {
const content: any[] = [];
const state = {
doc: {
- type: "doc",
+ type: 'doc',
content,
},
selection: {
- type: "text",
+ type: 'text',
anchor: 0,
- head: 0
- }
+ head: 0,
+ },
};
if (initial && initial.length) {
content.push({
- type: "paragraph",
+ type: 'paragraph',
content: {
- type: "text",
- text: initial
- }
+ type: 'text',
+ text: initial,
+ },
});
state.selection.anchor = state.selection.head = initial.length + 1;
}
@@ -56,8 +54,8 @@ export namespace RichTextUtils {
export const ToPlainText = (state: EditorState) => {
// Because we're working with plain text, just concatenate all paragraphs
const content = state.doc.content;
- const paragraphs: Node<any>[] = [];
- content.forEach(node => node.type.name === "paragraph" && paragraphs.push(node));
+ const paragraphs: Node[] = [];
+ content.forEach(node => node.type.name === 'paragraph' && paragraphs.push(node));
// Functions to flatten ProseMirror paragraph objects (and their components) to plain text
// Concatentate paragraphs and string the result together
@@ -80,22 +78,21 @@ export namespace RichTextUtils {
// Preserve the current state, but re-write the content to be the blocks
const parsed = JSON.parse(oldState ? oldState.Data : Initialize());
parsed.doc.content = elements.map(text => {
- const paragraph: any = { type: "paragraph" };
- text.length && (paragraph.content = [{ type: "text", marks: [], text }]); // An empty paragraph gets treated as a line break
+ const paragraph: any = { type: 'paragraph' };
+ text.length && (paragraph.content = [{ type: 'text', marks: [], text }]); // An empty paragraph gets treated as a line break
return paragraph;
});
// 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 };
+ parsed.selection = { type: 'text', anchor: 1, head: 1 };
// Export the ProseMirror-compatible state object we've just built
return JSON.stringify(parsed);
};
export namespace GoogleDocs {
-
export const Export = async (state: EditorState): Promise<GoogleApiClientUtils.Docs.Content> => {
- const nodes: (Node<any> | null)[] = [];
+ const nodes: (Node | null)[] = [];
const text = ToPlainText(state);
state.doc.content.forEach(node => {
if (!node.childCount) {
@@ -126,10 +123,10 @@ export namespace RichTextUtils {
return { baseUrl: embeddedObject.imageProperties!.contentUri! };
});
- const uploads = await Networking.PostToServer("/googlePhotosMediaGet", { mediaItems });
+ const uploads = await Networking.PostToServer('/googlePhotosMediaGet', { mediaItems });
if (uploads.length !== mediaItems.length) {
- throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: "Error with internally uploading inlineObjects!" });
+ throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: 'Error with internally uploading inlineObjects!' });
}
for (let i = 0; i < objects.length; i++) {
@@ -144,14 +141,14 @@ export namespace RichTextUtils {
title: embeddedObject.title || `Imported Image from ${document.title}`,
width,
url: Utils.prepend(_m.client),
- agnostic: Utils.prepend(agnostic.client)
+ agnostic: Utils.prepend(agnostic.client),
});
}
}
return inlineObjectMap;
};
- type BulletPosition = { value: number, sinks: number };
+ type BulletPosition = { value: number; sinks: number };
interface MediaItem {
baseUrl: string;
@@ -172,7 +169,7 @@ export namespace RichTextUtils {
const lists: ListGroup[] = [];
const indentMap = new Map<ListGroup, BulletPosition[]>();
let globalOffset = 0;
- const nodes: Node<any>[] = [];
+ const nodes: Node[] = [];
for (const element of structured) {
if (Array.isArray(element)) {
lists.push(element);
@@ -182,7 +179,7 @@ export namespace RichTextUtils {
const sinks = paragraph.bullet!;
positions.push({
value: position + globalOffset,
- sinks
+ sinks,
});
position += item.nodeSize;
globalOffset += 2 * sinks;
@@ -191,13 +188,13 @@ export namespace RichTextUtils {
indentMap.set(element, positions);
nodes.push(list(state.schema, items));
} else {
- if (element.contents.some(child => "inlineObjectId" in child)) {
+ if (element.contents.some(child => 'inlineObjectId' in child)) {
const group = element.contents;
group.forEach((child, i) => {
- let node: Opt<Node<any>>;
- if ("inlineObjectId" in child) {
+ 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)) {
+ } else if ('content' in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) {
node = paragraphNode(state.schema, [child]);
}
if (node) {
@@ -215,7 +212,7 @@ export namespace RichTextUtils {
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);
+ const dispatcher = (tr: Transaction) => (state = state.apply(tr));
for (const list of lists) {
for (const pos of indentMap.get(list)!) {
const resolved = state.doc.resolve(pos.value);
@@ -252,17 +249,17 @@ export namespace RichTextUtils {
};
const listItem = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => {
- return schema.node("list_item", null, paragraphNode(schema, runs));
+ return schema.node('list_item', null, paragraphNode(schema, runs));
};
const list = (schema: any, items: Node[]): Node => {
- return schema.node("ordered_list", { mapStyle: "bullet" }, items);
+ return schema.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 fragment = children.length ? Fragment.from(children) : undefined;
- return schema.node("paragraph", null, fragment);
+ return schema.node('paragraph', null, fragment);
};
const imageNode = (schema: any, image: ImageTemplate, textNote: Doc) => {
@@ -278,7 +275,7 @@ export namespace RichTextUtils {
} else {
docid = backingDocId;
}
- return schema.node("image", { src, agnostic, width, docid, float: null, location: "add:right" });
+ return schema.node('image', { src, agnostic, width, docid, float: null, location: 'add:right' });
};
const textNode = (schema: any, run: docs_v1.Schema$TextRun) => {
@@ -287,10 +284,10 @@ export namespace RichTextUtils {
};
const StyleToMark = new Map<keyof docs_v1.Schema$TextStyle, keyof typeof schema.marks>([
- ["bold", "strong"],
- ["italic", "em"],
- ["foregroundColor", "pFontColor"],
- ["fontSize", "pFontSize"]
+ ['bold', 'strong'],
+ ['italic', 'em'],
+ ['foregroundColor', 'pFontColor'],
+ ['fontSize', 'pFontSize'],
]);
const styleToMarks = (schema: any, textStyle?: docs_v1.Schema$TextStyle) => {
@@ -301,21 +298,21 @@ export namespace RichTextUtils {
Object.keys(textStyle).forEach(key => {
let value: any;
const targeted = key as keyof docs_v1.Schema$TextStyle;
- if (value = textStyle[targeted]) {
+ if ((value = textStyle[targeted])) {
const attributes: any = {};
let converted = StyleToMark.get(targeted) || targeted;
value.url && (attributes.href = value.url);
if (value.color) {
const object = value.color.rgbColor;
- attributes.color = Color.rgb(["red", "green", "blue"].map(color => object[color] * 255 || 0)).hex();
+ attributes.color = Color.rgb(['red', 'green', 'blue'].map(color => object[color] * 255 || 0)).hex();
}
if (value.magnitude) {
attributes.fontSize = value.magnitude;
}
- if (converted === "weightedFontFamily") {
- converted = ImportFontFamilyMapping.get(value.fontFamily) || "timesNewRoman";
+ if (converted === 'weightedFontFamily') {
+ converted = ImportFontFamilyMapping.get(value.fontFamily) || 'timesNewRoman';
}
const mapped = schema.marks[converted];
@@ -332,38 +329,38 @@ export namespace RichTextUtils {
};
const MarkToStyle = new Map<keyof typeof schema.marks, keyof docs_v1.Schema$TextStyle>([
- ["strong", "bold"],
- ["em", "italic"],
- ["pFontColor", "foregroundColor"],
- ["pFontSize", "fontSize"],
- ["timesNewRoman", "weightedFontFamily"],
- ["georgia", "weightedFontFamily"],
- ["comicSans", "weightedFontFamily"],
- ["tahoma", "weightedFontFamily"],
- ["impact", "weightedFontFamily"]
+ ['strong', 'bold'],
+ ['em', 'italic'],
+ ['pFontColor', 'foregroundColor'],
+ ['pFontSize', 'fontSize'],
+ ['timesNewRoman', 'weightedFontFamily'],
+ ['georgia', 'weightedFontFamily'],
+ ['comicSans', 'weightedFontFamily'],
+ ['tahoma', 'weightedFontFamily'],
+ ['impact', 'weightedFontFamily'],
]);
const ExportFontFamilyMapping = new Map<string, string>([
- ["timesNewRoman", "Times New Roman"],
- ["arial", "Arial"],
- ["georgia", "Georgia"],
- ["comicSans", "Comic Sans MS"],
- ["tahoma", "Tahoma"],
- ["impact", "Impact"]
+ ['timesNewRoman', 'Times New Roman'],
+ ['arial', 'Arial'],
+ ['georgia', 'Georgia'],
+ ['comicSans', 'Comic Sans MS'],
+ ['tahoma', 'Tahoma'],
+ ['impact', 'Impact'],
]);
const ImportFontFamilyMapping = new Map<string, string>([
- ["Times New Roman", "timesNewRoman"],
- ["Arial", "arial"],
- ["Georgia", "georgia"],
- ["Comic Sans MS", "comicSans"],
- ["Tahoma", "tahoma"],
- ["Impact", "impact"]
+ ['Times New Roman', 'timesNewRoman'],
+ ['Arial', 'arial'],
+ ['Georgia', 'georgia'],
+ ['Comic Sans MS', 'comicSans'],
+ ['Tahoma', 'tahoma'],
+ ['Impact', 'impact'],
]);
- const ignored = ["user_mark"];
+ const ignored = ['user_mark'];
- const marksToStyle = async (nodes: (Node<any> | null)[]): Promise<docs_v1.Schema$Request[]> => {
+ const marksToStyle = async (nodes: (Node | null)[]): Promise<docs_v1.Schema$Request[]> => {
const requests: docs_v1.Schema$Request[] = [];
let position = 1;
for (const node of nodes) {
@@ -376,25 +373,25 @@ export namespace RichTextUtils {
const information: LinkInformation = {
startIndex: position,
endIndex: position + nodeSize,
- textStyle
+ textStyle,
};
- let mark: Mark<any>;
+ let mark: Mark;
const markMap = BuildMarkMap(marks);
for (const markName of Object.keys(schema.marks)) {
if (ignored.includes(markName) || !(mark = markMap[markName])) {
continue;
}
- let converted = MarkToStyle.get(markName) || markName as keyof docs_v1.Schema$TextStyle;
+ let converted = MarkToStyle.get(markName) || (markName as keyof docs_v1.Schema$TextStyle);
let value: any = true;
if (!converted) {
continue;
}
const { attrs } = mark;
switch (converted) {
- case "link":
- let url = attrs.allLinks.length ? attrs.allLinks[0].href : "";
- const delimiter = "/doc/";
- const alreadyShared = "?sharing=true";
+ 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) {
@@ -411,41 +408,43 @@ export namespace RichTextUtils {
textStyle.foregroundColor = fromRgb.blue;
textStyle.bold = true;
break;
- case "fontSize":
- value = { magnitude: attrs.fontSize, unit: "PT" };
+ case 'fontSize':
+ value = { magnitude: attrs.fontSize, unit: 'PT' };
break;
- case "foregroundColor":
+ case 'foregroundColor':
value = fromHex(attrs.color);
break;
- case "weightedFontFamily":
+ case 'weightedFontFamily':
value = { fontFamily: ExportFontFamilyMapping.get(markName) };
}
let matches: RegExpExecArray | null;
if ((matches = /p(\d+)/g.exec(markName)) !== null) {
- converted = "fontSize";
- value = { magnitude: parseInt(matches[1].replace("px", "")), unit: "PT" };
+ converted = 'fontSize';
+ value = { magnitude: parseInt(matches[1].replace('px', '')), unit: 'PT' };
}
textStyle[converted] = value;
}
if (Object.keys(textStyle).length) {
requests.push(EncodeStyleUpdate(information));
}
- if (node.type.name === "image") {
+ if (node.type.name === 'image') {
const width = attrs.width;
- requests.push(await EncodeImage({
- startIndex: position + nodeSize - 1,
- uri: attrs.agnostic,
- width: Number(typeof width === "string" ? width.replace("px", "") : width)
- }));
+ requests.push(
+ await EncodeImage({
+ startIndex: position + nodeSize - 1,
+ uri: attrs.agnostic,
+ width: Number(typeof width === 'string' ? width.replace('px', '') : width),
+ })
+ );
}
position += nodeSize;
}
return requests;
};
- const BuildMarkMap = (marks: Mark<any>[]) => {
- const markMap: { [type: string]: Mark<any> } = {};
- marks.forEach(mark => markMap[mark.type.name] = mark);
+ const BuildMarkMap = (marks: readonly Mark[]) => {
+ const markMap: { [type: string]: Mark } = {};
+ marks.forEach(mark => (markMap[mark.type.name] = mark));
return markMap;
};
@@ -462,23 +461,21 @@ 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
- }
- }
+ 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 => {
@@ -490,10 +487,10 @@ export namespace RichTextUtils {
const { startIndex, endIndex, textStyle } = information;
return {
updateTextStyle: {
- fields: "*",
+ fields: '*',
range: { startIndex, endIndex },
- textStyle
- } as docs_v1.Schema$UpdateTextStyleRequest
+ textStyle,
+ } as docs_v1.Schema$UpdateTextStyleRequest,
};
};
@@ -507,13 +504,12 @@ export namespace RichTextUtils {
return {
insertInlineImage: {
uri: baseUrls[0],
- objectSize: { width: { magnitude: width, unit: "PT" } },
- location: { index: startIndex }
- }
+ objectSize: { width: { magnitude: width, unit: 'PT' } },
+ location: { index: startIndex },
+ },
};
}
return {};
};
}
-
-} \ No newline at end of file
+}