jonschlinkert / remarkable

Markdown parser, done right. Commonmark support, extensions, syntax plugins, high speed - all in one. Gulp and metalsmith plugins available. Used by Facebook, Docusaurus and many others! Use https://github.com/breakdance/breakdance for HTML-to-markdown conversion. Use https://github.com/jonschlinkert/markdown-toc to generate a table of contents.
https://jonschlinkert.github.io/remarkable/demo/
MIT License
5.75k stars 373 forks source link

Extensible way to extend terminal characters in text parser #408

Open knubie opened 3 years ago

knubie commented 3 years ago

To implement some inline plugins we need to be able to add characters to the isTerminatorChar function in /lib/rules_inline/text.js. If your plugin's parser relies on a character that's not in that list (e.g. #), then the parser will never run.

One solution is to override the function with a different function, but that would conflict with other plugins that need to add their own character(s) to the list.

jpribyl commented 3 years ago

I also just ran into this issue. Use case here is if you're trying to do something like adding a hashtag mention plugin. Plenty of workarounds, but feels weird to require a break on @ when the real character you want is #

TimLaue91 commented 3 years ago

I ran into this problem trying to implement iaWriter-style image links (just beginning with a slash, like '/myImage.jpeg'). I went with the workaround suggested by @knubie, which works, but feels bad:

const md = new Remarkable();

const parse = function(state: Remarkable.StateInline) {
  // Parse...
};

// Part 1 of the hack (taken from text.js and adjusted)
function isTerminatorChar(ch: number) {
  switch (ch) {
    case 0x0A/* \n */:
    case 0x5C/* \ */:
    case 0x2F/* / */:  // Added this line to make '/' a terminator character
    case 0x60/* ` */:
    case 0x2A/* * */:
    case 0x5F/* _ */:
    case 0x5E/* ^ */:
    case 0x5B/* [ */:
    case 0x5D/* ] */:
    case 0x21/* ! */:
    case 0x26/* & */:
    case 0x3C/* < */:
    case 0x3E/* > */:
    case 0x7B/* { */:
    case 0x7D/* } */:
    case 0x24/* $ */:
    case 0x25/* % */:
    case 0x40/* @ */:
    case 0x7E/* ~ */:
    case 0x2B/* + */:
    case 0x3D/* = */:
    case 0x3A/* : */:
      return true;
    default:
      return false;
  }
}

// Part 2 of the hack (taken from text.js, unchanged)
function text(state: Remarkable.StateInline, silent: boolean) {
  var pos = state.pos;
  while (pos < state.posMax && !isTerminatorChar(state.src.charCodeAt(pos))) {
    pos++;
  }
  if (pos === state.pos) { return false; }
  if (!silent) { state.pending += state.src.slice(state.pos, pos); }
  state.pos = pos;
  return true;
};

const myPlugin = function(md: Remarkable) {
  const options = {};
  md.inline.ruler.at('text', text, options)  // Replace default text parser with adjusted one
  md.inline.ruler.push('myPlugin', parse, options);
};

md.use(myPlugin);

Edit: Added correct type annotations.

DiscoNova commented 3 years ago

function text(state: {[key: string]: any}, silent: boolean) {

Holeymoley... that looks like TypeScript - can we do that these days? Have I missed something?

TimLaue91 commented 3 years ago

I'm afraid I don't understand your question.

If you mean the incrdibly general type annotation: I didn't find a matching type definition in the Remarkable package and I was too lazy to spell out the properties of the state object. I'm also very new to Typescript.

Edit: Added correct type annotation.