Open scwambach opened 6 years ago
This is happening because the markdown widget parses content before shortcodes, and shortcode patterns are checked against individual blocks. When the content of a shortcode spans multiple blocks, it can never match the pattern.
We should address this by parsing shortcodes before parsing the Markdown string into an AST. This carries performance concerns that we'll need to be mindful of.
This fix is also a prerequisite for related issue #1001.
I've also run into this while trying to use a custom editor component to recognize "islands" of yaml, eg.:
Here is some text
::: MyComponent
foo: bar
quux: baz
:::
Some more text.
I've specified my component's pattern
as /::: MyComponent([\s\S]*)/
, and the above case works fine. However, if I have leading whitespace or an empty line in the yaml block, it breaks:
::: MyComponent
foo: bar
quux: baz
array:
- an item
- another item
:::
in the above case it gets handed this:
::: MyComponent
foo: bar
quux: baz
array:
Similar to #1001 it also breaks if I have something resembling markdown styling inside the yaml.
I think I can work around this by bastardizing the yaml inside the component, but it would be immensely powerful if the components could have the ability to match the undigested markdown. Then I'd be able to set pattern
to something like /^::: MyComponent\n([\s\S]+?\n)?:::\n/m
.
I realize that there might be challenges associated with this as you'd probably have to hook in at multiple stages to hydrate all the components.
I also tried to use the ```
delimiter for my custom component, but it looks like that's already special cased, so I wasn't able to match that at all.
So I remembered why editor components can't access raw markdown - it's for performance reasons. That's why we have a Remark plugin that transforms images back into markdown strings for parsing by the image editor component. Applying multiple RegExp patterns to a large markdown string can be a showstopper. By parsing the markdown before applying shortcode patterns, we:
^
, which is highly performant, and still have it apply to all potential shortcode nodes.Ideally inline shortcodes would work and raw markdown would be accessible for editor components. Just need to innovate through that performance hurdle. Suggestions and PR's more than welcome here.
@erquhart, for now I've resorted to forking netlify-cms and regexping the verbose format to something shortcode-like with a base64 blob: https://github.com/netlify/netlify-cms/compare/master...papandreou:netlify-cms-papandreou
And then have my editor widget recognize and serialize to that format:
pattern: /^::: MyComponent (\S*)/,
// Function to extract data elements from the regexp match
fromBlock(match) {
return jsyaml.load(base64Decode(match[1]));
},
// Function to create a text block from an instance of this component
toBlock(obj) {
return '::: MyComponent ' + base64Encode(jsyaml.safeDump(obj));
},
It's a bit naive and annoying (and doesn't avoid the double regexping), but it seems to work.
Gotcha, glad you were able to at least work out a stop gap.
Is there any work planned for this issue. This is something that prevents us from trying out Netlify CMS for a serious website – we need components with multi line markdown content!
I don't really see the big performance issue either, since it would still be possible to take advantage of matching with /^…$/
– it's just that the string that's matched against is incomplete, so we would need to combine multiple lines of (processed) markdown into one string so our regex can match.
@bjoernbg agreed - it's certainly possible, just takes thought and effort. Definitely open to contributions for this.
Anyone has an idea how to create an inline component based on selection (like Bold, Italic, Link, h1, etc.) with registerEditorComponent? It seems it's only made to add a new block Component into the markdown, no? I would like to create a custom modal component from a text selection (working very similar as a link). I didn't find any doc about selecting inline text with registerEditorComponent. Is it possible? Thanks!
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
We're now allowing shortcode patterns to match against raw markdown, although I believe multiline is still disabled. We're closer, though. #2828 should help as well.
@eur2 inline isn't on the docket atm. Eventually, but not yet. All block level for now.
We're now allowing shortcode patterns to match against raw markdown
@erquhart, that's amazing! Has that capability already been shipped?
@erquhart Yes, is this in the production code now?
found a solution to that. I create my editor component in React and stringify it. the stringified component also has to match the 'pattern' regex. here's how:
import React from 'react';
import { renderToString } from 'react-dom/server';
import CMS from 'netlify-cms-app';
const TravelQuote = (props) => {
const authorIsAnonymous = authorName && authorName.toLowerCase() === 'anonymous';
const authorImg = props.authorPic ? props.authorPic : ""; // src needs to exist *
// * the React components automatically removes the src attr if
// its attribute is undefined when it creates it.
// With no src though, the component structur won't match the regex.
return (
`<div class="travel-quote">
<p class="travel-quote__copy">{props.quote}</p>
<span class="travel-quote__author">{props.authorName}</span>
<img class="travel-quote__author-pic" src={authorImg} alt={props.authorName} />
</div>`
)
}
// create string from component to create the regex
// for CMS.registerEditorComponent 'pattern'
const patternString = renderToString(
`
<TravelQuote
quote='(.*)'
authorName='(.*)'
authorPic='(.*)'/>
`
);
// create the regex
const travelQuoteRegex = new RegExp(patternString.replace(/\//g, '\\/'));
CMS.registerEditorComponent({
id: "travelQuote",
label: "Travel quote",
fields: [
{ name: 'quote', label: 'Quote', widget: 'string' },
{ name: 'authorName', label: 'Author name', widget: 'string' },
{ name: 'authorPic', label: 'Author picture', widget: 'image' },
],
pattern: travelQuoteRegex,
fromBlock: function(match) {
return {
quote: match[1],
authorName: match[2],
authorPic: match[3],
};
},
toBlock: function(obj) {
return renderToString(
`<TravelQuote
quote={obj.quote}
authorName={obj.authorName}
authorPic={obj.authorPic} />`
)
},
toPreview: function(obj) {
return renderToString(
`<TravelQuote
quote={obj.quote}
authorName={obj.authorName}
authorPic={obj.authorPic} />`
)
},
});
// Note: renderToString() works only if the project is runned with the Production
// environment, with gatsby build and gatsby serve.
Thanks for sharing your solution @davideforestali!
Anyone has an idea how to create an inline component based on selection (like Bold, Italic, Link, h1, etc.) with registerEditorComponent? It seems it's only made to add a new block Component into the markdown, no? I would like to create a custom modal component from a text selection (working very similar as a link). I didn't find any doc about selecting inline text with registerEditorComponent. Is it possible? Thanks!
Any ideas if this is possible yet or is still in the works?
Hi everyone
I found a regex pattern to match multiline markdown, maybe it helps someone.
From
/^{{< text-image src="(.*)" alt="(.*)" >}}(.*){{< \/text-image >}}/
I changed it to
/^{{< text-image src="(.*)" alt="(.*)" >}}(.*){{< \/text-image >}}/s
.
The catch is in the last "s".
The entire component:
CMS.registerEditorComponent({
id: 'text-image',
label: 'Text & image',
fields: [
{ name: 'src', label: 'Image', widget: 'image' },
{ name: 'alt', label: 'Image alt', widget: 'string', default: '', required: false },
{ name: 'inner', label: 'Content', widget: 'markdown' },
],
pattern: /^{{< text-image src="(.*)" alt="(.*)" >}}(.*){{< \/text-image >}}/s,
fromBlock (match) {
return {
src: match[1],
alt: match[2],
inner: match[3],
}
},
toBlock (obj) {
return `{{< text-image src="${obj.src}" alt="${obj.alt}" >}}${obj.inner}{{< /text-image >}}`
},
})
That's because the .
character class doesn't match line terminators when the s
flag isn't set: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes#Types
In situations where you don't control the flags, you can use eg. [\s\S]*
instead if .*
to achieve the same thing (the "space or not space" hack).
- Do you want to request a feature or report a bug? bug
- What is the current behavior? When rendering a registerEditorComponent, using the Markdown field, that has multiple lines the preview breaks.
- If the current behavior is a bug, please provide the steps to reproduce. Create an Editor Component that has a Markdown field and make sure the field has content with multiple lines.
Example:
- What is the expected behavior? To render markdown correctly.
- Please mention your CMS, node.js, and operating system version. Netlify CMS version 1.0.4 Node.js 5.5.1 HUGO 0.30.2 macOS Sierra 10.12.6
- Please link or paste your
config.yml
below if applicable.