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

Feature Request: Inner Content Editing via Raw Markdown #505

Open
gurkerl83 opened this issue Mar 27, 2025 · 4 comments
Open

Feature Request: Inner Content Editing via Raw Markdown #505

gurkerl83 opened this issue Mar 27, 2025 · 4 comments

Comments

@gurkerl83
Copy link

gurkerl83 commented Mar 27, 2025

Feature Request: Inner Content Editing via Raw Markdown

Context

In CodeHike’s current setup, each block (or “section”) renders Markdown content directly through CodeHike’s internal transformations. While this works well for static display, there’s no straightforward way to preserve the original Markdown for subsequent edits. As a result, user modifications often require extracting HTML from a rendered React node—assigning the content to the DOM and then reading it back—just to re-generate the original Markdown. This process is tedious and error-prone.

To streamline round-trip editing (Markdown → Editor → Markdown), we need to keep the original Markdown intact. This ensures that specialized MDX syntax and custom transformations don’t get lost or mangled in the process.


Goal

Store and expose the raw Markdown for each block so that:

  1. We can render it through CodeHike’s existing pipelines.
  2. We can edit the content in a rich-text editor or other tooling that supports Markdown.
  3. We preserve all MDX or custom syntax details between edits.

Proposed Change

We added a dedicated body property in the _data object of each HikeSection. This field captures the raw Markdown for that section, simplifying future transformations and allowing for easy re-injection into editors.

const root: HikeSection = {
  type: 'section',
  _data: {
    header: '',
    body: '' // ← new field for the raw Markdown
  },
  name: '',
  depth: 0,
  title: '',
  parent: null,
  children: [],
  multi: false
};

Implementation in listToSection.ts
Below is a brief look at the strategy we used in listToSection.ts to populate the _data.body field:

function pushToSection(parent: HikeSection, node: any) {
  if (!parent._dataNodes) {
    parent._dataNodes = [];
  }
  parent._dataNodes.push(node);

  parent.children.push({
    type: 'content',
    value: node
  });
}

function finalizeSections(section: HikeSection) {
  if (section._dataNodes && section._dataNodes.length > 0) {
    const miniRoot = {
      type: 'root' as const,
      children: section._dataNodes
    };
    const markdown = toMarkdown(miniRoot).replace(/\n(?!\n)/g, '');
    section._data.body = markdown;
  }
  for (const child of section.children) {
    if (child.type === 'section') {
      finalizeSections(child);
    }
  }
}

Parse & Collect
As we traverse the AST, we store each node in an intermediate array (_dataNodes) instead of immediately transforming it to Markdown.

Finalize
After building the entire hierarchy, we call a finalizeSections function on each HikeSection. This function wraps _dataNodes in a mini “root” node, converts them to a Markdown string via mdast-util-to-markdown, and stores the result in _data.body. By isolating the raw content capture from the parsing logic, we avoid conflicts with specialized nodes and maintain the section’s original text.


Observed Complexities
In standard Markdown scenarios, storing the raw content via _data.body works smoothly. However, in more advanced CodeHike demos (e.g., BlocksDemo), we’ve seen issues arising from special or custom MDX node types such as mdxFlowExpression. Because these nodes aren’t handled by default Markdown utilities, they may introduce parse errors or unexpected behavior. A separate, MDX-aware plugin or additional parsing configuration could be required to handle these cases gracefully.


Example Use Case
Below is an example of a more complex MDX snippet, which we’d like to make editable without losing any of its structure or syntax:

<TimelineInjector>

# !!posts Post 1

Nestled **between** turquoise waters and majestic peaks, this enchanting haven offers breathtaking landscapes and endless adventure. Every moment spent here feels like a vibrant dream come true.

Wander through charming streets lined with colorful murals and delightful cafés, where every corner tells a story. The warm, welcoming community makes you feel like a part of the magic that surrounds this place.

# !!posts Post 2

Paradise.

Sunsets paint the sky in hues of wonder.

Enchanted.

</TimelineInjector>

By populating _data.body with this exact Markdown (including any custom CodeHike directives or MDX features), we can:

  1. Render the snippet in CodeHike.
  2. Open it in a rich-text or Markdown-focused editor without losing the specialized sections and flow elements.
  3. Preserve and re-export all syntax details as new edits are made.

Conclusion
Storing the original Markdown in _data.body provides a clean “exit strategy” for editing workflows. Rather than reconstructing Markdown from the rendered DOM, we maintain the source text directly, ensuring round-trip integrity—even for advanced MDX content. This approach simplifies edits, retains specialized syntax, and avoids parse errors or data loss. It works well in most standard scenarios and, with some additional handling for more complex MDX nodes, can support a full range of CodeHike use cases.

Thx!

@pomber
Copy link
Contributor

pomber commented Mar 28, 2025

can you tell me more about your use case? seems interesting

we may be able to add an option (I don't want to always expose the raw markdown because in some cases can be too large)

@gurkerl83
Copy link
Author

gurkerl83 commented Mar 31, 2025

@pomber, you raise a valid point. I've created a pull request that addresses this issue from the ground up. The idea is to allow users to instruct their components to adopt this behavior. By setting the markdownEnabled property on a component, the corresponding content or paragraph will be automatically transformed into Markdown. (See the changes made in remark-section-to-attribute for details.) If markdownEnabled isn’t specified, the component retains its current behavior—no Markdown transformation or export occurs.

Below is an example:

<Component markdownEnabled>

# !!posts Post 1

## !text Post 1

Nestled **between** turquoise waters and majestic peaks, this enchanting haven offers breathtaking landscapes and endless adventure. Every moment spent here feels like a vibrant dream come true.

Wander through charming streets lined with colorful murals and delightful cafés, where every corner tells a story. The warm, welcoming community makes you feel like a part of the magic that surrounds this place.

# !!posts Post 2

## !text Post 2

🤔

# !!posts Post 3

## !text Post 3

Paradise.

Sunsets paint the sky in hues of wonder.

Enchanted.

</Component>

This solution effectively resolves the problems discussed earlier by operating on a different level—leveraging already sourced content rather than creating it in a separate process as previously proposed.

I have two more questions.

  1. Extending the Block Definition
    When the markdownEnabled prop is used, we could extend the Block definition by adding an optional markdown field. Otherwise, users will need to extend Block manually. For example:
markdown: z.string().optional()
  1. Snapshot Test Line Endings
    The snapshot tests aren’t running due to differences in line endings. My machine uses Unix-style (\n), whereas the snapshots appear to use Windows-style (\r\n). Also, the tests use absolute file paths. Given that the changes are very minor and don’t affect other code, perhaps we can adjust the tests to account for this discrepancy.

Here is the PR #506

Thx!

@gurkerl83
Copy link
Author

@pomber What do you think how the markdown export is implemented, can I do more so this can land anytime soon?

Thx!

@Aline-king
Copy link

I want to experience it. Has the code been merged into the main branch?

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

No branches or pull requests

3 participants