Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Challenge] Create a rich text paragraph component #440

Open
brauliodiez opened this issue Oct 15, 2024 · 0 comments
Open

[Challenge] Create a rich text paragraph component #440

brauliodiez opened this issue Oct 15, 2024 · 0 comments

Comments

@brauliodiez
Copy link
Member

brauliodiez commented Oct 15, 2024

Create a rich text paragraph component.

It should allow the user:

  • enter a mutline text (text should be truncated into new lines if in the text there's a \n like character, or if the text doesn't fit in the line.
  • Some sections can be wrapped with two asterisks (markdown like) to get bold text or italic indicator underscore or one asterisk to get italic text

A sample of the text:

This is a rich multline paragraph example, using markdown like, you can add in some sections of the content *bold text* _italic text_ as well, we could start with that.
 
There are some markdonw libraries that parses this

The expected result

Captura de pantalla 2024-10-15 a las 9 48 17

We can use @deletidev consts to make this more accurate.

Some tips to get started reasearching this (chat GPT magic :P):

## Objective:

You want to render multiline text with bold and italic markdown formatting using React Konva. Additionally, if a line of text exceeds the width of a shape, it should automatically wrap onto the next line.

Steps:

Determine the maximum allowed width: Specify the maximum width where the text will be rendered.

Wrap lines if they exceed the width: Implement a function that splits the text into lines based on the allowed width.

Example Implementation with Word Wrapping:

import React, { useState } from "react";
import { Stage, Layer, Text } from "react-konva";
import ReactMarkdown from "react-markdown";

// Custom component to render formatted text with word wrapping
const FormattedText = ({ x, y, markdownText, maxWidth }) => {
  const [lines, setLines] = useState([]);

  // Function to split the text into lines based on max width
  const wrapText = (text, context, maxWidth) => {
    const words = text.split(" ");
    let line = "";
    const lines = [];

    words.forEach((word) => {
      const testLine = line + word + " ";
      const metrics = context.measureText(testLine);
      const testWidth = metrics.width;

      if (testWidth > maxWidth && line.length > 0) {
        lines.push(line);
        line = word + " ";
      } else {
        line = testLine;
      }
    });
    lines.push(line);
    return lines;
  };

  // Function to process the markdown and generate wrapped lines
  const processMarkdown = (text, context, maxWidth) => {
    const elements = [];
    ReactMarkdown({
      children: text,
      components: {
        strong: ({ children }) => elements.push({ text: children.join(''), bold: true }),
        em: ({ children }) => elements.push({ text: children.join(''), italic: true }),
        p: ({ children }) => elements.push({ text: children.join(''), bold: false, italic: false }),
      },
    });

    // Generate the wrapped lines based on max width
    const wrappedLines = elements.flatMap((element) =>
      wrapText(element.text, context, maxWidth).map((line) => ({
        text: line,
        bold: element.bold,
        italic: element.italic,
      }))
    );
    return wrappedLines;
  };

  // Create a 2D context to measure text width
  const canvas = document.createElement("canvas");
  const context = canvas.getContext("2d");
  context.font = "16px Arial"; // Same font that will be used for rendering

  // Process the markdown and wrap the lines to the max width
  const wrappedElements = processMarkdown(markdownText, context, maxWidth);

  return (
    <>
      {wrappedElements.map((element, index) => (
        <Text
          key={index}
          x={x}
          y={y + index * 20} // Vertical adjustment for each line
          text={element.text}
          fontStyle={
            element.bold && element.italic
              ? "bold italic"
              : element.bold
              ? "bold"
              : element.italic
              ? "italic"
              : "normal"
          }
          fontSize={16}
          width={maxWidth}
        />
      ))}
    </>
  );
};

// Main component
const MarkdownKonva = () => {
  const markdownText = `**This is a bold text** that is quite long to demonstrate word wrapping.\n_And this is italic text that is also quite long to demonstrate wrapping across multiple lines._\nNormal text can also be multiline.`;

  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        <FormattedText x={50} y={50} markdownText={markdownText} maxWidth={300} />
      </Layer>
    </Stage>
  );
};

export default MarkdownKonva;

## Explanation:

wrapText function: This function handles splitting text into multiple lines when it exceeds the maximum allowed width (maxWidth). It uses the measureText method from a canvas context to measure the width of the text and decides when to wrap to a new line.

processMarkdown function: This function still processes the markdown and detects whether the text is bold or italic, but it now also calls wrapText to ensure the text fits within the given width.

Canvas context for measuring text: A 2D canvas context is created to measure the text width with the same font that will be used in the final rendering (16px Arial).

Rendering: The text is split into lines and rendered using multiple Text components from Konva. Each line is vertically adjusted with a 20-pixel margin between lines.

maxWidth as a parameter: The maximum width available for the text is passed as a prop (maxWidth), and the text is wrapped accordingly.

Result:

With this approach, the text will automatically wrap both when it contains newline characters (\n) and when it is too long to fit within the allowed width. You can adjust the maxWidth value to change the wrapping limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Backlog
Development

No branches or pull requests

1 participant