import { serializable } from 'serializr'; import { scriptingGlobal } from '../client/util/ScriptingGlobals'; import { Deserializable } from '../client/util/SerializationHelper'; import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols'; import { ObjectField } from './ObjectField'; @scriptingGlobal @Deserializable('RichTextField') export class RichTextField extends ObjectField { @serializable(true) readonly Data: string; @serializable(true) readonly Text: string; /** * NOTE: if 'text' doesn't match the plain text of 'data', this can cause infinite loop problems or other artifacts when rendered. * @param data this is the formatted text representation of the RTF * @param text this is the plain text of whatever text is in the 'data' */ constructor(data: string, text: string) { super(); this.Data = data; this.Text = text; // ideally, we'd compute 'text' from 'data' by doing what Prosemirror does at run-time ... just need to figure out how to write that function accurately } Empty() { return !(this.Text || this.Data.toString().includes('dashField') || this.Data.toString().includes('dashDoc') || this.Data.toString().includes('align')); } [Copy]() { return new RichTextField(this.Data, this.Text); } [ToJavascriptString]() { return '`' + this.Text + '`'; } [ToScriptString]() { return `new RichTextField(\`${this.Data?.replace(/"/g, '\\"')}\`, \`${this.Text}\`)`; } [ToString]() { return this.Text; } // AARAV ADD static ToProsemirrorDoc = (content: Record[], selection: Record) => ({ doc: { type: 'doc', content, }, selection, }); private static ToProsemirrorTextContent = (text: string, styles?: { bold?: boolean; italic?: boolean; fontSize?: number; color?: string }) => [ { type: 'text', marks: [ ...(styles?.bold ? [{ type: 'strong' }] : []), ...(styles?.italic ? [{ type: 'em' }] : []), ...(styles?.fontSize ? [{ type: 'pFontSize', attrs: { fontSize: `${styles.fontSize}px` } }] : []), ...(styles?.color ? [{ type: 'pFontColor', attrs: { fontColor: styles.color } }] : []), ], text, }, ]; private static ToProsemirrorDashDocContent = (docId: string) => [ { type: 'dashDoc', attrs: { width: '200px', height: '200px', title: 'dashDoc', float: 'unset', hidden: false, docId }, }, ]; private static ToProsemirror = (plaintext: string, imgDocId?: string, styles?: { bold?: boolean; italic?: boolean; fontSize?: number; color?: string }, selectBack?: number) => RichTextField.ToProsemirrorDoc( plaintext .split('\n') .filter(text => (imgDocId ? text : true)) // if there's an image doc, we don't want it repeat for each paragraph -- assume there's only one paragraph with text in it .map(text => ({ type: 'paragraph', content: [ ...(text.length ? RichTextField.ToProsemirrorTextContent(text, styles) : []), // An empty paragraph gets treated as a line break ...(imgDocId ? RichTextField.ToProsemirrorDashDocContent(imgDocId) : []), ], })), { type: 'text', anchor: 2 + plaintext.length - (selectBack ?? 0), head: 2 + plaintext.length } ); // AARAV ADD // takes in text segments instead of single text field private static ToProsemirrorSegmented = (textSegments: { text: string; styles?: { bold?: boolean; italic?: boolean; fontSize?: number; color?: string } }[], imgDocId?: string, selectBack?: number) => RichTextField.ToProsemirrorDoc( textSegments.map(seg => ({ type: 'paragraph', // Each segment becomes its own paragraph content: [...RichTextField.ToProsemirrorTextContent(seg.text, seg.styles), ...(imgDocId ? RichTextField.ToProsemirrorDashDocContent(imgDocId) : [])], })), (textLen => ({ type: 'text', anchor: textLen - (selectBack ?? 0), head: textLen, }))(2 * textSegments.length + textSegments.map(seg => seg.text).join('').length - 1) // selection/doc end = text length + 2 for each paragraph. subtract 1 to set selection inside of end of last paragraph ); // AARAV ADD || public static textToRtf(text: string, imgDocId?: string, styles?: { bold?: boolean; italic?: boolean; fontSize?: number; color?: string }, selectBack?: number) { return new RichTextField(JSON.stringify(RichTextField.ToProsemirror(text, imgDocId, styles, selectBack)), text); } // AARAV ADD public static textToRtfFormat(textSegments: { text: string; styles?: { bold?: boolean; italic?: boolean; fontSize?: number; color?: string } }[], imgDocId?: string, selectBack?: number) { return new RichTextField(JSON.stringify(RichTextField.ToProsemirrorSegmented(textSegments, imgDocId, selectBack)), textSegments.map(seg => seg.text).join('')); } // AARAV ADD }