finos / FDC3

An open standard for the financial desktop.
https://fdc3.finos.org
Other
196 stars 116 forks source link

RichTextBlock data type #575

Closed bertrand-s closed 1 year ago

bertrand-s commented 2 years ago

Enhancement Request

Use Case:

At first sight we could think of Markdown as a good candidate for formatted text. However Markdown is not easily extensible and the custom tags, action buttons or attachment use cases would be hard to support. Besides markdown comes in different flavours and all libraries are not always compatible.

As such, the proposal is to use a simple JSON format that can be easily transformed into Markdow (or into HTML) and that can be then displayed in many different environments.

Other constraints:

Proposal

The proposal is to define a JSON-based structure composed of 2 element types

To keep things simple, block containers should either contain children of type block or children of type inline elements

As an example:

The goal of the proposal below is to introduce the basic structural elements. The attributes associated to each elements are deliberately kept simple to focus on the core structure. Further improvements (e.g. new attributes or new elements) could be then added through future proposals.


/**
 * Rich Text Block type
 */
export interface RichTextBlock {
    kind: "richtextblock";
    content: RtBlockElt[];

    // the following props could be added to support multiple languages:
    // lang: string; // language used by the content value (default lang)
    // translatedContent: {lang:string; content:RtBlockElt[];}[]
}

// --------------------------------- Blocks  
type RtBlockElt = RtParagraph | RtHeading | RtBulletList | RtOrderedList | RtBlockQuote | RtTable | RtAttachments;
// could be be extended with 
// - RtImageBlock : to support images
// - RtCodeBlock : to highlight some content as code (cf. HTML)

interface RtParagraph {
    kind: "p";
    content: RtInlineElt[];
}

interface RtHeading {
    kind: "h";
    level: 1 | 2 | 3;
    content: RtInlineElt[];
}

interface RtOrderedList {
    kind: "ol";
    content: RtListItem[];
}

interface RtBulletList {
    kind: "ul";
    content: RtListItem[];
}

interface RtListItem {
    kind: "li";
    content: RtBlockElt[]; // first element must be a paragraph
}

interface RtBlockQuote {
    kind: "blockquote";
    readlony: boolean; // whether the content should be editable in the RTE
    content: RtBlockElt[];
}

interface RtTable {
    kind: "table";
    content: RtTableRow[];
}

interface RtTableRow {
    kind: "tr";
    content: RtTableDiv[];
}

interface RtTableDiv {
    kind: "td";
    content: RtBlockElt[];
}

interface RtAttachments {
    kind: "attachments";
    content: RtData[];
}

interface RtDataTag {
    kind: "data";
    dataType: string; // e.g. "finos.rfq"  - custom type to know which renderer should be used
    displayType?: string; // e.g. "finos.instrument" or "finos.contact" or custom value supported by the chat system
    data: any; // the data content
    name: string; // string to display
    title?: string; // tooltip
}

// --------------------------------- Inline content
type RtInlineElt = string | RtStrongText | RtEmText | RtTextLink | RtDataTag;
// could be completed with
// - RtCode: to support inline 'code' style (i.e. monospace font + grayed background - cf. github markdown)

interface RtStrongText {
    kind: "strong"; // aka. bold
    content: RtInlineElt[];
}

interface RtEmText {
    kind: "em"; // aka. italic
    content: RtInlineElt[];
}

interface RtTextLink {
    kind: "link"; // aka. HTML anchor (a)
    href: string;
    content: RtInlineElt[];
}

// Custom tag examples:
interface RtInstrumentTag extends RtDataTag {
    tagType: "fdc3.instrument";
    data: Instrument; // cf. 'fdc3.instrument'
}

interface RtChatUserMention extends RtDataTag {
    tagType: "fdc3.chat.mention";
    data: { [applicationName: string]: { userId: string; userName: string; } }
}

This structure is very similar to HTML and can be easily transformed into documents loaded into rich text editors like proseMirror for instance

Apart from the usual elements, it contains 2 "special" content nodes:

Note: The previous type structure may seem quite verbose, but this can be easily hidden to the developer through helper functions (that could be provided by the fdc3 lib):

// Rich text block with table and paragraphs
let rtb1 = rtb([
    p("table example:"),
    table([
        tr([
            td("1.1"),
            td("1.2")
        ]),
        tr([
            td([p("2.1")]),
            td([
                p("2.2"),
                p("++")
            ]),
        ]),
    ])
]);

// example with strong and em elements
let rtb2 = rtb([
    p(["hello", strong(["brave", em(" new "), "world!"])])
]);
bertrand-s commented 2 years ago

Hi @rikoe - another proposal (in case you are looking for topics for next Thursday meeting 😁 )

kriswest commented 2 years ago

A few notes from meeting:

Notes:

openfin-johans commented 2 years ago

Hi all,

more of a technical issue perhaps - but is FDC3 really equipped to create and maintain a new "rich text" standard? Is there no existing standard that - even if not perfect - would be more practical to adopt?

That said, I think the key thing now, is to formulate how you should send “body” or “message” type data (ie unstructured) in fdc3 context objects. Suggesting to standardize on eg “plain text”, “xml”, “adaptive card”, “symphony message ml”, etc - similar to the recent efforts for eg dates and currency? That way applications will know what the type is and can decide if they can handle it or not. This would allow for a new rich text format to be tested and put into use by interested parties as a custom type before debating a new standard.

Best regards,

Johan

nemery-flextrade commented 2 years ago

That is a valid concern. I think we might get away with it as the proposal seems like it covers a lot of formatting/content types. I remember we discussed the concern that Markdown, while common, is difficult to extend, but what of the extensibility of adaptive card (I'm afraid I'm not overly familiar with it)?

pauldyson commented 2 years ago

I have very mixed feelings on this.

I can absolutely see where the advantages of a custom approach may come in as the use case of passing rich text with embedded context from one system to another is very powerful.

On the other I'm dead against our devs spending time writing custom generators and/or parsers when we have already have a lot of stuff that generates HTML/XML for use in emails, websites, internal portals, file-based and API-based integrations.

ISTM that HTML could do the job even if it is a bit of overkill. Use of data attributes could meet the need to embed contexts and use of inputs or smart parsing of links could be used for 'action buttons'. Regardless, most of our use cases involve at least the potential to pass links that it would be nice not to render in full https://blah.blah/blah horror.

I think if you're going to encourage adoption of a JSON-based standard you'll need not just to define the standard but also supply a fast and robust Javascript 'reference implementation' of both a generator and a parser/convertor.

Otherwise I think you need to consider using what's already there, even if you define an acceptable subset and defined outcomes for what happens should people stray outside of that defined subset.

nemery-flextrade commented 2 years ago

Use of data attributes could meet the need to embed contexts and use of inputs or smart parsing of links could be used for 'action buttons'

I can see data attributes meeting the need for context but I think we need to be careful about the usage of links as targets for action buttons - I think most if not all of the major 'desktop' providers offer adaptors for non-web applications (.NET/Java usually).

On the other I'm dead against our devs spending time writing custom generators and/or parsers when we have already have a lot of stuff that generates HTML/XML for use in emails, websites, internal portals, file-based and API-based integrations.

Thinking about it now, I'm not so worried about the generation of the JSON but more the rendering on the other side. Initially I thought this wouldn't be such a big deal but now I'm wondering how much overhead would have to go in to writing/maintaining 'native' renderers for those who don't use a cross-platform framework - it could quite easily snowball.

That said, I think we do need to decide on a small selection of standards for this formatting - too many and we risk fragmenting the user/application base. While we could implement some sort of plain text fall-back, that still (in my mind) runs the risk of some applications being relegated to second class citizen status because they use format 'X', which, while permitted under the standard is vastly less popular than 'Y' leading to 'X' having to fall-back to plain text more often.

bertrand-s commented 2 years ago

Thanks for your feedback. I must say I agree with most of what you said - i.e. I would rather use an existing standard instead of proposing something new. As a recap, here are the different options today:

This is why I think the RichTextBlock approach is still the best option, even though it seems like re-inventing the wheel. That said I agree that we probably need to complement the proposal with

@kriswest to come back on your comment, here is how RTB would be generated (i.e. through helper functions and not 'by hand' - btw. this approach holds true if we were to generate HTML instead of RTB)

// RTB generation with TypeScript helper functions 
// -> type validation and auto-completion
let tableSample = rtb([
    p("table example:"),
    table([
        tr([
            td("1.1"),
            td("1.2")
        ]),
        tr([
            td([p("2.1")]),
            td([
                p("2.2"),
                p("++")
            ]),
        ]),
     ])
])

// HTML equivalent
let tableHTML = "<p>table example:</p><table class=\"rtb\"><tr><td><p>1.1</p></td><td><p>1.2</p></td></tr><tr><td><p>2.1</p></td><td><p>2.2</p><p>++</p></td></tr></table>";

// RTB - produced by the helper function
let tableRTB = {"kind":"richtextblock","content":[
    {"kind":"p","content":[
         "table example:"]
    },{"kind":"table","content":[
        {"kind":"tr","content":[
            {"kind":"td","content":[{"kind":"p","content":["1.1"]}]},
            {"kind":"td","content":[{"kind":"p","content":["1.2"]}]}
        ]},{"kind":"tr","content":[
            {"kind":"td","content":[{"kind":"p","content":["2.1"]}]},
            {"kind":"td","content":[{"kind":"p","content":["2.2"]},{"kind":"p","content":["++"]}]}
        ]}
    ]}
]}
nemery-flextrade commented 2 years ago

@bertrand-s w/r/t WYSIWYG editor for adaptive card: https://adaptivecards.io/designer/

If we intend to provide a utility library for handling translation to HTML / Markdown I'm much happier - though I feel obliged to mention that these should probably be available for at least C# and Java as well as JS/TS as again, many of the desktops offer adaptors for those languages. If you are able to provide a JS/TS implementation I would be happy to port.

A WYSIWYG would be nice if you do decide to open source it - while it's not high priority for developers it would be great to have something that more business oriented folks could use to design content.

bertrand-s commented 2 years ago

Well, the link you shared is not really a WYSIWYG editor, but a specialised JSON editor (they call it a card payload editor). What we need is an editor that end users can use to edit actual content, this is why I consider that there is no real WYSIWYG for adaptive cards (my feeling is that their syntax is actually too complex for that as it wasn't design with this concern in mind). The reason behind the editor requirement is that we want to give end-users the possibility to adapt/complement a message when we receive a chat intent

kriswest commented 1 year ago

We heard at:

kriswest commented 1 year ago

@bertrand-s @pierreneu @Yannick-Malins will this issue be revived one day, or have you gone a different direction? (If so please close it)

Yannick-Malins commented 1 year ago

hi @kriswest could you close this? i don't have the rights

cheers