Skip to content

Current inline directive syntax should not be enabled by default #33

@DavidMulder0

Description

@DavidMulder0

Initial checklist

Affected package

latest

Steps to reproduce

Although there are MANY people reporting how completely broken this extension is, somehow all relevant issues link to other closed issues and closed pull requests just saying that it should be discussed elsewhere.

By far the best work on this is done by @GauBen in remarkjs/remark-directive#19 (comment)

Hi there! Sorry for unearthing this issue but it seems to be the most appropriate place to post my two cents. Please let me cook.

When this many people have been bit by a feature, we might start considering it's a bug.

The most interesting thing to note is that everyone complains about text directives (:directive), and for one very legitimate reason: :\w+ may appear naturally in markdown prose (10:30 AM, localhost:8080, Mitglieder:innen, John 15:13...). ::leaf directives and :::container directives have no such issue because of intentionality: starting a new line with at least two colons is never accidental.

I see two different approaches to address the way this feature behaves.

Syntactical Solutions

We may first approach this problem with a syntactical solution: since the underlying discussion is not settled we are free to implement what we want until a standard is created.

  1. Don't allow "bare" text directives. We'll call a bare directive a directive without label and attributes. As currently implemented, :directive, :directive[], :directive{} and :directive[]{} behave the same. This proposal suggests that :directive is no longer parsed as a directive.
    This solves the intentionality principle: you don't see :\w+[] or :\w+{} naturally appear in prose.
    Also, as a side-note, I don't see the use for text directives without parameters. It probably means you want to insert something in your text, and for that, you can use the :emoji: syntax, which removes all ambiguity. All examples in the original post have parameters. Maybe some people abuse the syntax by using non-static directive names (:123 instead of :pr[123], :boolean instead of :badge[boolean]), but that goes against the concept of markdown directives.
  2. Use another starting character. The proposal mentions !, @ or ::, but many keyboard-accessible characters could do the trick (maybe %?). The constraint being it should not appear in the middle of words.
    I find this solution less convincing than the first one, mostly because it does not solve the intentionality principle as well, and many platforms already use @ or ! (mention a user, link to GitLab issue...). Coupled with 1 is perfectly fine though.
  3. Ask users to escape :. Just kidding, this is a terrible idea. The point of markdown extensions is to add features to markdown, and that might happen after you have written a significant amount of markdown. If it breaks existing markdown, it's a design flaw: it means that the syntax is not specific enough, and we're back to the intentionality principle.
    \: being the normal way to insert colons is user-hostile.

Technical Solutions

If we want to keep the syntax untouched, the other leverage we have is the implementation one: the way micromark-extension-directive behaves when parsing markdown.

Since directives are extension points for markdown (one syntax for arbitrary extensions in markdown), users would always read the documentation (or other markdown files) to know what's available for them to use. You cannot expect all platforms to implement the same directives.

What should a user expect when using an unknown directive?

Typing ::youtub[dQw4w9WgXcQ] instead ::youtube[dQw4w9WgXcQ] should result in <p>::youtub[dQw4w9WgXcQ]</p>, I'd argue that the outcome that makes the most sense.

To implement that, we could add some kind of filter to micromark-extension-directive so that it's not :\w+ that gets parsed into a textDirective node, but only the specific directives supported by the platform in question. Other directive-like symbols remain as-is in the output document.

directive({
  /**
   * @type {(type: 'text' | 'leaf' | 'container', name: string) => boolean}
   * @default () => true
   */
  filter: (type, name) => {
    if (type == 'text' && name == 'year') return true;
    if (type == 'leaf' && name == 'youtube') return true;
    if (type == 'container') return true;
    return false; // All other directives are not parsed
  }
})

Closing Words

Thanks for taking the time to read my proposal. I'm okay with any solution that would not require updating my markdown files while still being able to add features to them over time.

My current workaround is patching micromark-extension-directive to only enable container directives, which is as bad and unsafe as it looks:

import { directiveFromMarkdown } from 'mdast-util-directive';
import { directive } from 'micromark-extension-directive';

unified().use(function remarkDirective() {
  const data = this.data();
  const { flow } = directive();
  // Remove `directiveText` and `directiveLeaf` manually
  (data.micromarkExtensions ??= []).push({ flow: { [58]: flow[58][0] } });
  (data.fromMarkdownExtensions ??= []).push(directiveFromMarkdown());
});

It works ok for now, but an official (and safe) solution would be more than welcome.

And then there is @wooorm who somehow believes this is only about numbers (common counter examples: spreadsheet formulas (A1:A10), company names and other names (re:zero, NYSE:SHEL)).

Actual behavior

Depending on the exact setup the result might differ, but fundamentally the golden rule of markdown is broken: The source syntax reads VERY differently from the rendered output including completely changing the meaning (e.g. "common counter examples: spreadsheet formulas (A1), company names and other names (re, NYSE)").

Expected behavior

Normal human text should not be affected by a generic extension.

 - To sum all values use sum(A1:A10)
 - The stock of NYSE:SHEL went up despite the news
 - I watched re:zero all night
 - At 3:15PM we left to the hotel
 - See DOI:10.1000/xyz123 for more information
 - The 3:4 TV looked good on their wall

Runtime

No response

Package manager

No response

Operating system

No response

Build and bundle tools

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    🤞 phase/openPost is being triaged manually

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions