Closed KaptianCore closed 4 years ago
Hello,
your issue lacks a lot of information... What plugin are you referring to? Why would you need inline CSS? Where do you think you can "set classes"? Could you please edit to add some minimal information; an example of the syntax you'd like or what you're trying to acheive would also be great.
I want to do it as I want to make a block that allows me to do text colouring with inline css would look like this. [color='worded colours, hex colour or rgb colour']Red Text[/color]
spoiler: {
classes: 'spoiler-block',
style: 'color: red'
title: 'optional',
details: true
},
this is for the custom blocks plugin, I want to actually use it on the remark.js parser.
I'm not against this feature, but likely won't do it myself anytime soon. Maybe you'd be interested in working on it? Should be fairly easy to do.
Idk I'm not very confident with Remarks plugin system, I'll give it a crack maybe.
Well, that would require modifying one of our plugin, so should be easier than creating your own. I'm able to help you if you have any problem.
I just wanna know how to start lol
I mean I could take a guess and just like basically mimic what is done to add classes instead to add styles. this is what I have so far
const spaceSeparated = require('space-separated-tokens')
function escapeRegExp (str) {
return str.replace(new RegExp(`[-[\\]{}()*+?.\\\\^$|/]`, 'g'), '\\$&')
}
const C_NEWLINE = '\n'
const C_FENCE = '|'
function compilerFactory (nodeType) {
let text
let title
return {
blockHeading (node) {
title = this.all(node).join('')
return ''
},
blockBody (node) {
text = this.all(node).map(s => s.replace(/\n/g, '\n| ')).join('\n|\n| ')
return text
},
block (node) {
text = ''
title = ''
this.all(node)
if (title) {
return `[[${nodeType} | ${title}]]\n| ${text}`
} else {
return `[[${nodeType}]]\n| ${text}`
}
},
}
}
module.exports = function blockPlugin (availableBlocks = {}) {
const pattern = Object
.keys(availableBlocks)
.map(escapeRegExp)
.join('|')
if (!pattern) {
throw new Error('remark-custom-blocks needs to be passed a configuration object as option')
}
const regex = new RegExp(`\\[\\[(${pattern})(?: *\\| *(.*))?\\]\\]\n`)
function blockTokenizer (eat, value, silent) {
const now = eat.now()
const keep = regex.exec(value)
if (!keep) return
if (keep.index !== 0) return
const [eaten, blockType, blockTitle] = keep
/* istanbul ignore if - never used (yet) */
if (silent) return true
const linesToEat = []
const content = []
let idx = 0
while ((idx = value.indexOf(C_NEWLINE)) !== -1) {
const next = value.indexOf(C_NEWLINE, idx + 1)
// either slice until next NEWLINE or slice until end of string
const lineToEat = next !== -1 ? value.slice(idx + 1, next) : value.slice(idx + 1)
if (lineToEat[0] !== C_FENCE) break
// remove leading `FENCE ` or leading `FENCE`
const line = lineToEat.slice(lineToEat.startsWith(`${C_FENCE} `) ? 2 : 1)
linesToEat.push(lineToEat)
content.push(line)
value = value.slice(idx + 1)
}
const contentString = content.join(C_NEWLINE)
const stringToEat = eaten + linesToEat.join(C_NEWLINE)
const potentialBlock = availableBlocks[blockType]
const titleAllowed = potentialBlock.title &&
['optional', 'required'].includes(potentialBlock.title)
const titleRequired = potentialBlock.title && potentialBlock.title === 'required'
if (titleRequired && !blockTitle) return
if (!titleAllowed && blockTitle) return
const add = eat(stringToEat)
if (potentialBlock.details) {
potentialBlock.containerElement = 'details'
potentialBlock.titleElement = 'summary'
}
const exit = this.enterBlock()
const contents = {
type: `${blockType}CustomBlockBody`,
data: {
hName: potentialBlock.contentsElement ? potentialBlock.contentsElement : 'div',
hProperties: {
className: 'custom-block-body',
},
},
children: this.tokenizeBlock(contentString, now),
}
exit()
const blockChildren = [contents]
if (titleAllowed && blockTitle) {
const titleElement = potentialBlock.titleElement ? potentialBlock.titleElement : 'div'
const titleNode = {
type: `${blockType}CustomBlockHeading`,
data: {
hName: titleElement,
hProperties: {
className: 'custom-block-heading',
},
},
children: this.tokenizeInline(blockTitle, now),
}
blockChildren.unshift(titleNode)
}
const styleList = spaceSeparated.parse(potentialBlock.styles || '')
const classList = spaceSeparated.parse(potentialBlock.classes || '')
return add({
type: `${blockType}CustomBlock`,
children: blockChildren,
data: {
hName: potentialBlock.containerElement ? potentialBlock.containerElement : 'div',
hProperties: {
className: ['custom-block', ...classList],
styleName: ['custom-style', ...styleList],
},
},
})
}
const Parser = this.Parser
// Inject blockTokenizer
const blockTokenizers = Parser.prototype.blockTokenizers
const blockMethods = Parser.prototype.blockMethods
blockTokenizers.customBlocks = blockTokenizer
blockMethods.splice(blockMethods.indexOf('fencedCode') + 1, 0, 'customBlocks')
const Compiler = this.Compiler
if (Compiler) {
const visitors = Compiler.prototype.visitors
if (!visitors) return
Object.keys(availableBlocks).forEach(key => {
const compiler = compilerFactory(key)
visitors[`${key}CustomBlock`] = compiler.block
visitors[`${key}CustomBlockHeading`] = compiler.blockHeading
visitors[`${key}CustomBlockBody`] = compiler.blockBody
})
}
// Inject into interrupt rules
const interruptParagraph = Parser.prototype.interruptParagraph
const interruptList = Parser.prototype.interruptList
const interruptBlockquote = Parser.prototype.interruptBlockquote
interruptParagraph.splice(interruptParagraph.indexOf('fencedCode') + 1, 0, ['customBlocks'])
interruptList.splice(interruptList.indexOf('fencedCode') + 1, 0, ['customBlocks'])
interruptBlockquote.splice(interruptBlockquote.indexOf('fencedCode') + 1, 0, ['customBlocks'])
}
Ok, I'll start from the beginning:
remark
plugin, hence it parses Markdown and must return valid MDAST, which is a syntax tree for Markdown;remark
plugin, to transform custom blocks into a specific MDAST syntax, and a second plugin, but this time for remark-rehype
, transforming our custom MDAST into HAST, an syntax tree for HTML;remark
feature, that simply allows to write a single remark
plugin with HAST embedded inside;data
property of an MDAST node, it consist mainly of:
hName
, transformed into the tagName
property of the HAST element being created;hProperties
, containing the HAST properties; if you know client-side Javascript, the accessible properties are the same one that make the DOM.In our code, three elements MDAST nodes are generated (hence three HAST elements also):
If you want to customize style on the block, you'll need to allow customization of the three elements separately. What we are doing for classes is not enough, because the given class is only applied to the parent element; for style, you'll need to take a configuration object like:
{
heading: "heading-style",
content: "content-style",
parent: "parent-style"
}
And then applying the corresponding style to each element. As I'm writing this, I'm realizing this is likely not such a good thing to have; could you please explain a bit more why you couldn't use classes with CSS styling instead for your usage?
About your code, styleName
is not a valid DOM property, but you could definitely use style
. If you're using the plugin with a sanitizer, make sure the style is not filtered; next, you will be able to transform your single-string style
parameter into an object, as mentioned above, and apply to each of the three blocks, not only the parent one.
is this good?
const spaceSeparated = require('space-separated-tokens')
function escapeRegExp (str) {
return str.replace(new RegExp(`[-[\\]{}()*+?.\\\\^$|/]`, 'g'), '\\$&')
}
const C_NEWLINE = '\n'
const C_FENCE = '|'
function compilerFactory (nodeType) {
let text
let title
return {
blockHeading (node) {
title = this.all(node).join('')
return ''
},
blockBody (node) {
text = this.all(node).map(s => s.replace(/\n/g, '\n| ')).join('\n|\n| ')
return text
},
block (node) {
text = ''
title = ''
this.all(node)
if (title) {
return `[[${nodeType} | ${title}]]\n| ${text}`
} else {
return `[[${nodeType}]]\n| ${text}`
}
},
}
}
module.exports = function blockPlugin (availableBlocks = {}) {
const pattern = Object
.keys(availableBlocks)
.map(escapeRegExp)
.join('|')
if (!pattern) {
throw new Error('remark-custom-blocks needs to be passed a configuration object as option')
}
const regex = new RegExp(`\\[\\[(${pattern})(?: *\\| *(.*))?\\]\\]\n`)
function blockTokenizer (eat, value, silent) {
const now = eat.now()
const keep = regex.exec(value)
if (!keep) return
if (keep.index !== 0) return
const [eaten, blockType, blockTitle] = keep
/* istanbul ignore if - never used (yet) */
if (silent) return true
const linesToEat = []
const content = []
let idx = 0
while ((idx = value.indexOf(C_NEWLINE)) !== -1) {
const next = value.indexOf(C_NEWLINE, idx + 1)
// either slice until next NEWLINE or slice until end of string
const lineToEat = next !== -1 ? value.slice(idx + 1, next) : value.slice(idx + 1)
if (lineToEat[0] !== C_FENCE) break
// remove leading `FENCE ` or leading `FENCE`
const line = lineToEat.slice(lineToEat.startsWith(`${C_FENCE} `) ? 2 : 1)
linesToEat.push(lineToEat)
content.push(line)
value = value.slice(idx + 1)
}
const contentString = content.join(C_NEWLINE)
const stringToEat = eaten + linesToEat.join(C_NEWLINE)
const potentialBlock = availableBlocks[blockType]
const titleAllowed = potentialBlock.title &&
['optional', 'required'].includes(potentialBlock.title)
const titleRequired = potentialBlock.title && potentialBlock.title === 'required'
if (titleRequired && !blockTitle) return
if (!titleAllowed && blockTitle) return
const add = eat(stringToEat)
if (potentialBlock.details) {
potentialBlock.containerElement = 'details'
potentialBlock.titleElement = 'summary'
}
const exit = this.enterBlock()
const contents = {
type: `${blockType}CustomBlockBody`,
data: {
hName: potentialBlock.contentsElement ? potentialBlock.contentsElement : 'div',
hProperties: {
className: 'custom-block-body',
style: ['body-style', ...styleList],
},
},
children: this.tokenizeBlock(contentString, now),
}
exit()
const blockChildren = [contents]
if (titleAllowed && blockTitle) {
const titleElement = potentialBlock.titleElement ? potentialBlock.titleElement : 'div'
const titleNode = {
type: `${blockType}CustomBlockHeading`,
data: {
hName: titleElement,
hProperties: {
className: 'custom-block-heading',
style: ['style-title', ...styleList],
},
},
children: this.tokenizeInline(blockTitle, now),
}
blockChildren.unshift(titleNode)
}
const styleList = spaceSeparated.parse(potentialBlock.styles || '')
const classList = spaceSeparated.parse(potentialBlock.classes || '')
return add({
type: `${blockType}CustomBlock`,
children: blockChildren,
data: {
hName: potentialBlock.containerElement ? potentialBlock.containerElement : 'div',
hProperties: {
className: ['custom-block', ...classList],
style: ['style-block', ...styleList],
},
},
})
}
const Parser = this.Parser
// Inject blockTokenizer
const blockTokenizers = Parser.prototype.blockTokenizers
const blockMethods = Parser.prototype.blockMethods
blockTokenizers.customBlocks = blockTokenizer
blockMethods.splice(blockMethods.indexOf('fencedCode') + 1, 0, 'customBlocks')
const Compiler = this.Compiler
if (Compiler) {
const visitors = Compiler.prototype.visitors
if (!visitors) return
Object.keys(availableBlocks).forEach(key => {
const compiler = compilerFactory(key)
visitors[`${key}CustomBlock`] = compiler.block
visitors[`${key}CustomBlockHeading`] = compiler.blockHeading
visitors[`${key}CustomBlockBody`] = compiler.blockBody
})
}
// Inject into interrupt rules
const interruptParagraph = Parser.prototype.interruptParagraph
const interruptList = Parser.prototype.interruptList
const interruptBlockquote = Parser.prototype.interruptBlockquote
interruptParagraph.splice(interruptParagraph.indexOf('fencedCode') + 1, 0, ['customBlocks'])
interruptList.splice(interruptList.indexOf('fencedCode') + 1, 0, ['customBlocks'])
interruptBlockquote.splice(interruptBlockquote.indexOf('fencedCode') + 1, 0, ['customBlocks'])
}
Do you know what ['body-style', ...styleList]
does?
EDIT: also, I'll reask this: could you please explain a bit more why you couldn't use classes with CSS styling instead for your usage?
I'm closing this issue as I no longer require it, but it was for coloured text.
Like we can set classes would be good if we could do inline css