Skip to content

Implement Heading Styles (H1-H6) #589

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

Open
wants to merge 33 commits into
base: main
Choose a base branch
from

Conversation

DevinDuricka
Copy link

@DevinDuricka DevinDuricka commented Apr 24, 2025

Implement Heading Styles (H1-H6)

This pull request introduces support for applying and managing heading styles (H1 through H6) as paragraph-level formatting within the Rich Editor. This is a common feature in rich text editors (like Google Docs) and improves the semantic representation of content when converting to formats like HTML and Markdown.

Key Features Implemented:

  • Paragraph-Level Headings: Heading styles are applied to entire paragraphs, consistent with standard rich text editor behavior.
  • API for Setting Headings: A public function RichTextState.setHeadingStyle(style: HeadingStyle) is added to allow users to apply or remove heading styles based on the current selection.
  • Internal Representation: A new HeadingStyle enum is introduced to represent the different heading levels (Normal, H1-H6) and their associated Markdown elements and HTML tags.
  • Style Management: Logic is implemented within RichParagraph to handle applying and removing heading-specific SpanStyle and ParagraphStyle.
  • Handling User-Applied Styles: Special care is taken to ensure that user-applied inline styles (like Bold or Italic) persist correctly when toggling between normal paragraphs and heading styles. This is achieved by refining the diff and unmerge logic for SpanStyle and adjusting how base heading SpanStyle is retrieved (HeadingStyle.getSpanStyle now returns fontWeight = null).
  • HTML Conversion:
    • Encoding (RichTextStateHtmlParser.encode): HTML heading tags (<h1>-<h6>) are correctly parsed into paragraphs with the corresponding HeadingStyle and base SpanStyle/ParagraphStyle. Inline styles within heading tags are merged with the base styles.
    • Decoding (RichTextStateHtmlParser.decode): RichTextState paragraphs with HeadingStyle are correctly converted back to HTML using the appropriate <h1>-<h6> tags. User-applied additional inline styles (those not part of the base heading style) are included in the style attribute of an inner <span> tag using the diff logic.
  • Markdown Conversion:
    • Encoding (RichTextStateMarkdownParser.encode): Markdown heading syntax (#, ##, etc.) is correctly parsed into paragraphs with the corresponding HeadingStyle.
    • Decoding (RichTextStateMarkdownParser.decode): RichTextState paragraphs with HeadingStyle are correctly converted back to Markdown using the appropriate # syntax.
  • Unit Tests: Comprehensive unit tests have been added to verify the correct behavior of HeadingStyle functions and the encoding/decoding processes for both HTML and Markdown, including scenarios with additional inline styles.

Closes #578

…sion between the M3 textstyles to their Heading equivalent. Also added some rudimentary initial toggle functionality.
…aph or selection paragraph(s) all either set the HeadingParagraphStyle or remove the HeadingParagraphStyle. Still todo: change some naming to heading from header, rename the primary function, alter the HTML conversion to use the HeadingParagraphStyle instead of the custom spanstyle, add unit tests.
…d also added the markdown values as a property for the HeadingParagraphStyle enum
…lookup the markdown # signs using the HeadingParagraphStyle function.
… encode the markdown since the tests were failing without it
…l need to add the paragraphstyle to the RichText encoded because the linespacing is different than default for typography
…with the HeadingParagraphStyle helper functions
…sParagraphStyle.kt to hold a reference to the ParagraphStyle (if applicable)
…sts, not just for the headings. I added logic to merge in the ParagraphStyle if it is found in the encoding map for either the Markdown or HTML. I also removed the sets of headings, since they ended up being unnecessary.
…tyle] applied to the paragraph. In Rich Text editors like Google Docs, heading styles (H1-H6) are applied to the entire paragraph. This function reflects that behavior by checking all child [RichSpan]s for a non-default [HeadingParagraphStyle]. If any child [RichSpan] has a heading style (other than [HeadingParagraphStyle.Normal]), this function returns that heading style, indicating that the entire paragraph is styled as a heading.
…was defaulting to <p> tags. Switching the ParagraphType to the RichParagraph allows us to also get the HeadingParagraphStyle.html, and still get the type to determine if it is <ul> or <ol>
…is was defaulting to <p> anytime the paragraphGroupTagName was not "ul" or "ol".
…raphy class. This will allow us to remove any inherited ParagraphStyle properties, but keep the user added ones. <h1> to <h6> tags will allow the browser to apply the default heading styles. If the paragraphTagName isn't a h1-h6 tag, it will revert to the old behavior of applying whatever paragraphstyle is present.
…ng the SpanStyle to the Css span style. If it is a heading paragraph style, remove the Heading-specific [SpanStyle] features via [unmerge] but retain the non-heading associated [SpanStyle] properties.
…ng the SpanStyle to the Css span style. If it is a heading paragraph style, remove the Heading-specific [SpanStyle] features via [unmerge] but retain the non-heading associated [SpanStyle] properties.
…to subsequent paragraphs. TODO - Verify that this isn't crucial (all the tests still pass)
… with the private add and remove heading paragraph style functions.
…Span or Paragraph style from the extended one. This is useful for removing the Heading's Span or Paragraph style when converting to HTML. Otherwise, it would encode the Heading Paragraph or Span style properties into the css of the HTML.
…the RichParagraph as the HeadingStyle gets set on the whole paragraph, not just a selection. The RichTextState now calls the setHeadingStyle on all the pertinent paragraphs.
…the RichParagraph as the HeadingStyle gets set on the whole paragraph, not just a selection. The RichTextState now calls the setHeadingStyle on all the pertinent paragraphs.
…eadingParagraphStyle and converting to/from html and Markdown
…ust the normal font weight and it conflicts if the user has a bold fontweight.
…kes more sense and avoid confusions with the ParagraphStyle
…ociated TextStyle for non-compose-richeditor composables, you need to be able to access it i.e. set a standard Text(style = getTextStyle())
@MohamedRejeb
Copy link
Owner

Thanks for your contribution! I'm checking your PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Question about H1-H6 Heading Tags
2 participants