markedjs / marked

A markdown parser and compiler. Built for speed.
https://marked.js.org
Other
32.85k stars 3.38k forks source link

Labels #1486

Open anoblet opened 5 years ago

anoblet commented 5 years ago

Describe the feature Task lists should generate a label for their input element

<label>
  <input type="checkbox">Receive promotional offers?</input>
</label>

currently it only generates:

  <input type="checkbox"/> Receive promotional offers?

Why is this feature necessary? Accessibility is a a rule on performance tests. Checkboxes without labels bring down the score.

Describe alternatives you've considered NA

Is this already available, or a known issue?

UziTech commented 5 years ago

I agree, accessibility should be important.

Unfortunately that would go against the gfm spec. Since we are focused on following the spec, this might be a better argument for changing the spec.

I'm not even sure where you would go to request such a change.

anoblet commented 5 years ago

I hear you. I'm going to try and see if I can't find someone from Github to comment on the issue.

UziTech commented 5 years ago

The easiest way to do that currently would be to extend the renderer and add the <label>

var marked = require('marked');

var renderer = new marked.Renderer();
var renderListitem = renderer.listitem.bind(renderer);

renderer.listitem = function(text, task) {
  var html = renderListitem(text, task);

  if (task) {
    html = '<li><label>' + html.replace(/^<li>([\s\S]*)<\/li>\n$/, '$1') + '</label></li>\n';
  }

  return html;
};

console.log(marked('- [ ] task', { renderer: renderer }));
// <ul>
// <li><label><input disabled="" type="checkbox"> task</label></li>
// </ul>

I could see there being an issue with this format when you have nested lists. You could have labels inside labels.

The correct way to do this would require parsing the list item and only wrapping the inline text after the checkbox with a <label>

anoblet commented 5 years ago

I don't see that being an issue just yet, but thank you for the snippet. I'm going to try it out now. I also reached out to Github on Twitter and on their forum to see if they have an opinion on whether or not a renderer would be going against spec by implementing this without it being defined. Thanks for all of the help!

kungpaogao commented 1 year ago

Is there a convenient way to just get the inline text after the checkbox? I'm trying to make checkboxes more accessible with labels, and the solution @UziTech mentioned is pretty good unless there are nested items.

UziTech commented 1 year ago

@kungpaogao you can change the regular expression however you want to wrap the label. The best way to do it will depend on your use case. If you want to allow any markdown you could use another tool to create the dom after marked converts it to html and add the label that way.

kungpaogao commented 1 year ago

@UziTech sorry for the late reply - I'm trying to do something like the following:

- [x] todo item 1 with `inline code` block
- [ ] todo item 2
   - [ ] nested item
       some text underneath that's technically another paragraph

should become something like

<ul>
  <li>
    <label><input type="checkbox" ...>todo item 1 with <code>inline code</code> block</label>
  </li>
  <li>
    <label><input type="checkbox" ...>todo item 2</label>
    <ul>
       <!-- following line could also be wrapped in paragraph tabs (i think that's current behavior) -->
       <label><input type="checkbox" ...>nested item</label>
       <p>some text underneath that's technically another paragraph</p>
    </ul>
  </li>
</ul>

I tried for a while, but I'm stumped on how to only have the <label> wrap the text afterwards + any inline blocks, but not any children/nested list items or block items. Do you have any pointers, or is this use case out of the scope of just extending marked?

UziTech commented 1 year ago

Your best bet is to use a different tool. You can still extend marked the way I show above but using regex to add the label in every situation would be difficult.

Ideally you would do something like:

import { marked } from 'marked';

const renderer = new marked.Renderer();
const renderListitem = renderer.listitem.bind(renderer);

renderer.listitem = function(text, task) {
  let html = renderListitem(text, task);

  if (task) {
    // use different tool to parse html and add label
  }

  return html;
};

marked.use({ renderer });

console.log(marked('- [ ] task'));
// <ul>
// <li><label><input disabled="" type="checkbox"> task</label></li>
// </ul>
kungpaogao commented 1 year ago

thanks for the suggestion! I was able to solve this for my use case by first parsing the list item to

- [x] <label>some todo item</label>

and then using simple string manipulation to move the label tag around

    if (task && text.includes("<label>")) {
      return (
        "<li class='todo-item'><label>" + text.replace("<label>", "") + "</li>"
      );
    }