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] Always display markdown syntax of block at cursor position #645

Closed
pm64 opened this issue Jul 30, 2022 · 24 comments
Closed

[Feature] Always display markdown syntax of block at cursor position #645

pm64 opened this issue Jul 30, 2022 · 24 comments
Assignees
Labels
enhancement New feature or request planned This issue is being planned in iteration

Comments

@pm64
Copy link

pm64 commented Jul 30, 2022

Provide way to always show the markdown syntax for the block at the cursor position, similar to Typora/Obsidian/Vditor/etc.

Obsidian_ZGBGNh7sGr

@pm64 pm64 added the enhancement New feature or request label Jul 30, 2022
@Saul-Mirone
Copy link
Member

It will be added in the future, in a progressive way.

@Saul-Mirone Saul-Mirone added the planned This issue is being planned in iteration label Aug 4, 2022
@Saul-Mirone Saul-Mirone changed the title Always display markdown syntax of block at cursor position [Feature] Always display markdown syntax of block at cursor position Aug 8, 2022
@pm64
Copy link
Author

pm64 commented Aug 30, 2022

@Saul-Mirone, may I ask where this enhancement sits on the roadmap?

@4-1-1
Copy link

4-1-1 commented Sep 14, 2022

It will be added in the future, in a progressive way.

Maybe make it as an option?

@Saul-Mirone
Copy link
Member

Closing this since we'll track the plan at #819.

@s524797336
Copy link

@Saul-Mirone Sorry to bother you, i see this has been done, but can't find a way to use it

@darkuranium
Copy link

@Saul-Mirone Bump. I have the same problem as @s524797336.

@Saul-Mirone
Copy link
Member

@s524797336 @darkuranium
Similar to this https://stackblitz.com/github/Milkdown/examples/tree/main/react-custom-component?file=README.md,
the general idea is to show some prosemirror decorators when selection changes. For example, if selection is in a heading node, you'll display the #s as prosemirror decorator.

@s524797336
Copy link

@Saul-Mirone @darkuranium After few month, i made this happend.
20230722-153844
It is similar to the inline-sync plugin, but displays syntax dynamically and changes node styles.
Also like the inline-sync-plugin, it's buggy in some ways.

@darkuranium
Copy link

@s524797336 I had my own work on this, it's mostly non-buggy (sans a few unimplemented features), but:
http://ygg-md-editor-pm.surge.sh/

I'm strongly considering making a "proper" editor out of this (or at least a ProseMirror component). Note that it focuses on inline only, as I intend to use BlockNote or similar for the block components. It also uses a special dialect of Markdown, with some features disabled, and others (e.g. [[...]] for WikiLinks) added — to suit my own purposes.

The fact that I'm doing inline-only does simplify a few things, though — for example, I'm free to simply re-parse the whole text on every change (because inline parts, e.g. paragraphs, tend to be relatively short). Then I use ProseMirror's decorations to change how things are displayed. Indeed, this means that internally, inline text is simple plaintext/Markdown (which is what makes it more robust!).

I also recently found out about Rino, but haven't had a chance to test it properly. At least on paper, it seems like a legitimately great candidate for a "pure" markdown editor (as opposed to the hybrid "high-level blocks + Markdown inline" that I'm pursuing).

@s524797336
Copy link

s524797336 commented Jul 26, 2023

@darkuranium I tried the example link, it's buggy for me.
Just input **123** and input a at this **123*a* position, and then remove a, see the output.
Rino is buggy in some edge case too.

@darkuranium
Copy link

@s524797336
To be honest, I'm not 100% sure of the correct Markdown behaviour in that case. So it's less of a bug, and more of a misinterpretation of MD. Should be easily fixable, at any rate.

@s524797336
Copy link

@darkuranium You can compare to the Typora to see the differences, which is perfect markdown editor to me.
Try the mixed markdown syntax and different order, some time use escape and such.
I have hard time to explain the difficulty in English.
I'm not saying you can't do it, I'm saying it is difficult to perfect, specially in some edge cases.

@Saul-Mirone
Copy link
Member

@darkuranium You can compare to the Typora to see the differences, which is perfect markdown editor to me. Try the mixed markdown syntax and different order, some time use escape and such. I have hard time to explain the difficulty in English. I'm not saying you can't do it, I'm saying it is difficult to perfect, specially in some edge cases.

Yeah, I totally agree. The secret behind Typora is that it use markdown string as single source of truth and use it to decide a lot of things such as selection, that's how it solves a lot of edge cases. But there're always trade offs, for example, in Typora you cannot mix html with markdown. I really respect Typora because of that If you want to build something just like typora in web, it's nearly impossible to use any rich text frameworks. In rich text based editors, something like inlineSyncPlugin is the best approach we can do but it's still not perfect.

@darkuranium
Copy link

darkuranium commented Jul 27, 2023

@darkuranium You can compare to the Typora to see the differences, which is perfect markdown editor to me.
Try the mixed markdown syntax and different order, some time use escape and such.
I have hard time to explain the difficulty in English.
I'm not saying you can't do it, I'm saying it is difficult to perfect, specially in some edge cases.

I've just tried it, and Typora behaves exactly the same on the **123*a* example as my editor. There are some issues with escapes (e.g. \*), but that's mostly because I haven't fully implemented those yet.

Note that because of my approach, any MD-related bugs you find are bugs in the Markdown parser itself, not the editor per se. I'm very confident on this, because every single change throws away all the decoration and does a full re-parse. There might be bugs in some unrelated components; this is just a proof-of-concept, at the end of the day.
Also, there is nothing to perfect, other than the Markdown parser itself and some UX, because it's a full Markdown reparse on every single change. No state is maintained, other than the raw Markdown text.

And the parser is an ad-hoc PEG one, of basically haphazardly translating the (relevant for me) inline rules from peg-markdown to PEG.js syntax. Furthermore, I (purposefully, for my use-case) don't support all of Markdown. That's more a design choice for my own uses, than a limitation of this approach.
I also didn't bother resolving all the bugs with the parser (e.g. escapes), because this has accomplished its goal of a proof-of-concept.

What I'm trying to say is: a Typora experience is 100% possible already, at least at the inline level (block level should be possible, but it'll take more work). It's only a matter of using the same MD parser as Typora, plus a few quality-of-life features (e.g. the way images are edited, and you may have noticed Typora has a ~0.5s delay between a cursor stopping on an element, and it displaying markup).

Yeah, I totally agree. The secret behind Typora is that it use markdown string as single source of truth and use it to decide a lot of things such as selection, that's how it solves a lot of edge cases. But there're always trade offs, for example, in Typora you cannot mix html with markdown. I really respect Typora because of that If you want to build something just like typora in web, it's nearly impossible to use any rich text frameworks. In rich text based editors, something like inlineSyncPlugin is the best approach we can do but it's still not perfect.

That's exactly the approach in my editor/prototype, too. It simply re-parses all the contents as Markdown on every change, using ProseMirror decorators to highlight the code. The plugin itself simply looks for an Inline block, and then parses it as Markdown.
I figured by using BlockNote (or some other component) for blocks, and "plain text mode with full reparsing" for inline, I can get the best of both worlds:

  • Extensible, rich blocks (consider also diagrams, video embeds, etc --- not just lists and such). Which is something that modern rich text editors are good at, but Markdown is (kind of) bad at (yes, it can be done [with extensions], but it tends to get unreadable and/or syntactically hard to remember).
  • ... but without having to fight the editor on text syntax. E.g. an editor thinking that some text should be bold just because it's started right after some other bold text. Which is something that Markdown is good at, but rich text editors are bad at.
    It's also very easy to type in links without touching the mouse, which is a feature I absolutely needed (since I'm making something Wiki-like, which means a lot of linking).

Ultimately, I'd like to see an editor component that does exactly that. Basically, high-level blocks, but Markdown-level text editing; kind of like what Typora does, though not necessarily with a 1:1 mapping to Markdown (I have no need for such on the block level, but I 100% agree that there are good reasons to have that, too).

It's what this issue was about, more or less.
I take it Milkdown cannot do something along those lines?

@Saul-Mirone
Copy link
Member

Saul-Mirone commented Jul 27, 2023

@darkuranium You can compare to the Typora to see the differences, which is perfect markdown editor to me.
Try the mixed markdown syntax and different order, some time use escape and such.
I have hard time to explain the difficulty in English.
I'm not saying you can't do it, I'm saying it is difficult to perfect, specially in some edge cases.

I've just tried it, and Typora behaves exactly the same on the **123*a* example as my editor. There are some issues with escapes (e.g. \*), but that's mostly because I haven't fully implemented those yet.

Note that because of my approach, any MD-related bugs you find are bugs in the Markdown parser itself, not the editor per se. I'm very confident on this, because every single change throws away all the decoration and does a full re-parse. There might be bugs in some unrelated components; this is just a proof-of-concept, at the end of the day. Also, there is nothing to perfect, other than the Markdown parser itself and some UX, because it's a full Markdown reparse on every single change. No state is maintained, other than the raw Markdown text.

And the parser is an ad-hoc PEG one, of basically haphazardly translating the (relevant for me) inline rules from peg-markdown to PEG.js syntax. Furthermore, I (purposefully, for my use-case) don't support all of Markdown. That's more a design choice for my own uses, than a limitation of this approach. I also didn't bother resolving all the bugs with the parser (e.g. escapes), because this has accomplished its goal of a proof-of-concept.

What I'm trying to say is: a Typora experience is 100% possible already, at least at the inline level (block level should be possible, but it'll take more work). It's only a matter of using the same MD parser as Typora, plus a few quality-of-life features (e.g. the way images are edited, and you may have noticed Typora has a ~0.5s delay between a cursor stopping on an element, and it displaying markup).

Yeah, I totally agree. The secret behind Typora is that it use markdown string as single source of truth and use it to decide a lot of things such as selection, that's how it solves a lot of edge cases. But there're always trade offs, for example, in Typora you cannot mix html with markdown. I really respect Typora because of that If you want to build something just like typora in web, it's nearly impossible to use any rich text frameworks. In rich text based editors, something like inlineSyncPlugin is the best approach we can do but it's still not perfect.

That's exactly the approach in my editor/prototype, too. It simply re-parses all the contents as Markdown on every change, using ProseMirror decorators to highlight the code. The plugin itself simply looks for an Inline block, and then parses it as Markdown. I figured by using BlockNote (or some other component) for blocks, and "plain text mode with full reparsing" for inline, I can get the best of both worlds:

  • Extensible, rich blocks (consider also diagrams, video embeds, etc --- not just lists and such). Which is something that modern rich text editors are good at, but Markdown is (kind of) bad at (yes, it can be done [with extensions], but it tends to get unreadable and/or syntactically hard to remember).
  • ... but without having to fight the editor on text syntax. E.g. an editor thinking that some text should be bold just because it's started right after some other bold text. Which is something that Markdown is good at, but rich text editors are bad at.
    It's also very easy to type in links without touching the mouse, which is a feature I absolutely needed (since I'm making something Wiki-like, which means a lot of linking).

Ultimately, I'd like to see an editor component that does exactly that. Basically, high-level blocks, but Markdown-level text editing; kind of like what Typora does, though not necessarily with a 1:1 mapping to Markdown (I have no need for such on the block level, but I 100% agree that there are good reasons to have that, too).

It's what this issue was about, more or less. I take it Milkdown cannot do something along those lines?

Sounds that milkdown's inline sync plugin has a similar approach with your editor. It also passes user input into markdown parser and render the ast with prosemirror. The hard part is that you'll lose user's original input as markdown because of the parse and serialize transform steps. But typora won't.

@s524797336
Copy link

s524797336 commented Jul 28, 2023

@darkuranium Maybe i misleading you with my poor English.
20230728-102255
You can see, when i delete a in **123*a*, the * is also deleted. Below is Typora behave
20230728-102628
And i also re-parses all the contents as Markdown on every change, just like inline-sync-plugin dose.

@darkuranium
Copy link

Sounds that milkdown's inline sync plugin has a similar approach with your editor. It also passes user input into markdown parser and render the ast with prosemirror. The hard part is that you'll lose user's original input as markdown because of the parse and serialize transform steps. But typora won't.

I think you're misunderstanding me. There is no step, ever, that converts the AST back into Markdown. ProseMirror decorations are 100% view-only (they do not exist in the actual document model), as opposed to e.g. marks (which do).

Basically, the editing itself is 100% plaintext-based. All changes are based on the base (Markdown) plaintext. Said plaintext is then decorated (only!) in the view (exclusively!). It's basically a really fancy syntax highlighter.
In fact, an earlier prototype of mine was CodeMirror-based (as is Typora, I believe). I only moved to ProseMirror because of easier integration into existing rich editors such as BlockNote, but it uses the same approach.

You can see, when i delete a in **123a, the * is also deleted. Below is Typora behave

Okay, I see the confusion: your browser behaves differently from mine1!. That's really strange. In my case, only the a is deleted when I try to delete that.
Whether this is a ProseMirror bug or my own, it shouldn't be too hard to fix; because as stated above, it's ultimately pure plaintext editing, which is why I'm so confident in it being robust. It's just a ProseMirror-based syntax highlighter, really.

Footnotes

  1. For the record, my primary browser is WaterFox, a Firefox fork. This might be a Gecko vs WebKit-related bug.

@s524797336
Copy link

@darkuranium How do you use ProseMirror without transform ProseMirror ast to markdown?
User input text and control selection on top of ProseMirror, i'm very interesting.

@darkuranium
Copy link

@s524797336 Look into ProseMirror's decorators, that I've mentioned a few times. They're work very similar to marks, but they're 100% in the view.

Here's a (truncated/simplified) snippet of what I'm doing; I'm happy to release this project (once I clean it up), but I'll need to figure out what to do for block-level. I'm thinking I'll look into maybe using some ProseMirror defaults, but replace inline only?

import { Node } from 'prosemirror-model';
import { EditorState, Plugin, PluginView, Transaction } from 'prosemirror-state';
import { Decoration, DecorationSet, EditorView } from 'prosemirror-view';

import * as parser from './grammar.pegjs';

export default function markdownInline(): Plugin
{
    const decorateText = (view: EditorView, state: EditorState, node: Node, npos: number): Decoration[] => {
        const decorations: Decoration[] = [];
        const p = parser.parse(node.text!) as D.MDNode;
        p.walk((node, pos) => {
            ...
            if(node instanceof D.Markup) // Markup is what's ultimately going to be shown or hidden depending on cursor position
            {
                decorations.push(Decoration.inline(pos, end, {class: 'md-mark'}));
                return false;
            }
            if(node instanceof D.Emph)
            {
                decorations.push(Decoration.inline(pos, end, {class: 'md-emph'}));
                return true;
            }
            if(node instanceof D.Strong)
            {
                decorations.push(Decoration.inline(pos, end, {class: 'md-strong'}));
                return true;
            }
            if(node instanceof D.Strike)
            {
                decorations.push(Decoration.inline(pos, end, {class: 'md-strike'}));
                return true;
            }
            ...
        });
        ...
        decorations.push(...handleNodesWithinCursor(state)); // basically just adds the 'active' class all relevant nodes within the cursor
        ...
        return decorations;
    };

    return new Plugin({
        ...
    });
}

@Saul-Mirone
Copy link
Member

http://ygg-md-editor-pm.surge.sh/

Brilliant idea, I get it. You're not using prosemirror marks. The input will always be a pure text and you'll use decorators to render them into different elements. I think maybe I can give it a try to implement something similar in milkdown.

@s524797336
Copy link

@darkuranium Hi, did you solved the bug we discussed earlier, the chrome one? I'm currently continuing with milkdown and implementing my own version of this. I had the same problem, i solved it by not set md-mark display: none to hide marks, but use width:0;height:0;display:inline-block;overflow:hidden to hide marks. and it's working.

@Saul-Mirone
Copy link
Member

The trade off of this solution is that it cannot leverage the ability of prosemirror's mark feature. It makes it hard to reuse some abilities in prosemirror's eco system. But it's still a good idea. I'll try to find out that if it's possible to use it with prosemirror mark.

@s524797336
Copy link

The trade off of this solution is that it cannot leverage the ability of prosemirror's mark feature. It makes it hard to reuse some abilities in prosemirror's eco system. But it's still a good idea. I'll try to find out that if it's possible to use it with prosemirror mark.

If we can somehow attach the user origin input on every inline container, like paragraph and heading, we can go back use our old solution.

  1. User selecting a container with prosemirror node only eg. [em]123[em], [em] is prosemirror's mark pair
  2. Add markdown syntax into container via origin input *123* eg. *[em]123[em]*
  3. User change something eg. *[em]1234[em]*
  4. Transform user changed prosemirror node to markdown with custom parser, that only get node.textContent and something nested in special cases, so we can get *1234* as origin text
  5. Transform *1234* to prosemirror's node via milkdown parser and replace current container eg. [em]1234[em]
  6. Redo step 2 to add md syntax eg *[em]1234[em]*

What do you think?

I'll try this with my old solution

@Ainias
Copy link

Ainias commented Nov 18, 2023

What is the current situation here? Is this possible with markdown? I saw #819 is marked as complete but there seems to be no documentation to enable this feature

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request planned This issue is being planned in iteration
Projects
None yet
Development

No branches or pull requests

6 participants