-
-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Assign more fields from definition
node into imageReference
node
#37
Comments
It’s an understandable hope! But, given that you make your own transform, where you tried to set things on definitions and then found it didn’t work, is it then not rather simple to change that transform? To work on link references? This to me feels a bit 🤷♂️ I guess But perhaps even more sensical would be, to treat it like other nodes: if you define a Any other unknown node with What about footnote definitions? What if theoretical other syntax extensions define similar things? 🤔 Say, abbreviations? |
I believe it would be possible, but it would require additional expensive tree scanning. When I visit each definition node, I'd then have to search for every corresponding reference node. The You bring up some fair questions. I do have a solution for now which is to overwrite the
The downside for me is that now I have to keep track of any changes you make to Thanks for the quick reply as always. |
I like the idea of supporting video URLs like that in markdown. This needs to support link references, and at first this request made sense. However #37 (comment) convinced me it’s probably not a good idea. Then I realized, this is an HTML transform, not a markdown transform. The functionality you want is probably best implemented as a rehype plugin that transforms: <img
alt="Video alt"
src="./video.mp4"
title="Video title"
other-attribute="for-example class or id"
> into: <video title="Video title" other-attribute="for-example class or id">
<source src="./video.mp4" type="video/mp4"></source>
<p>Video alt</p>
</video> In that case markdown link references become an implementation detail you no longer have to deal with. |
@ben519 Does a rehype plugin work for you? Remco’s post seems convincing to me. Then we can close this issue for now—we can always revisit when there’s a more practical example of data on definitions when staying in markdown? |
Not really because the HTML |
Hmm, can you elaborate? Those weren’t in your OP here. Data should be carried over from mdast through to hast (around here: https://github.com/syntax-tree/mdast-util-to-hast/blob/f511a93817b131fb73419bf7d24d73a5b8b0f0c2/lib/handlers/image.js#L35) |
If I recall correctly, the situation goes like this.. There are two MDAST nodes (a link and a link-reference) that essentially get merged into a single HAST node (the Given that premise, how should the nodes' properties be merged? For example, what if the link has class My understanding is that the current merge behavior is to ONLY use the Does that make more sense? |
References only have url and title properties, because that is all that can exist in markdown. In my mind there is no merging, and probably shouldn't be. |
Yes and in my case I am adding properties via a plugin. I'm reluctant transfer the properties between nodes inside that plugin because doing so requires an expensive lookup operating (i.e. finding all the matching link references.) This lookup is happens inside the |
It's not expensive, it can be done in a single pass of the AST and a very efficient import { Plugin } from 'unified';
import { Node } from 'unist';
import { visit } from 'unist-util-visit';
import { Parent, LinkReference, Definition } from 'mdast';
// Define an interface for link references
interface LinkReferenceWithParent {
node: LinkReference;
index: number;
parent: Parent;
definition?: Definition;
}
const remarkExampleForBen: Plugin = () => {
return (tree: Node) => {
const definitions = new Map<string, Definition>();
const linkReferences: LinkReferenceWithParent[] = [];
// Single pass: collect all definitions and link references
visit(tree, (node, index, parent) => {
if (node.type === 'definition') {
const definition = node as Definition;
definitions.set(definition.identifier.toLowerCase(), definition);
} else if (node.type === 'linkReference') {
const linkReference = node as LinkReference;
if (parent && typeof index === 'number') {
linkReferences.push({ node: linkReference, index, parent });
}
}
});
// Fill in the definition from the map
const mappedLinkReferences = linkReferences.map(reference => {
const definition = definitions.get(reference.node.identifier.toLowerCase());
if (definition) {
reference.definition = definition;
}
return reference;
});
mappedLinkReferences.forEach(({ node, index, parent, definition }) => {
// do any processing you need
});
};
};
export default remarkExampleForBen; |
@ChristianMurphy I understand what your saying and really appreciate the implementation example. I think our disagreement lies here
I thought that scanning the entire AST, even just once, is expensive. And if it can be avoided, it should be avoided. To be fair, you might be right and maybe it's not as expensive as I'm guessing. I think we'd need to run some timings on example data to know for sure. I don't have the time or motivation to do it right now; maybe another day. Anyways, as long as I can override the |
Premature optimization is the root of all evil 😅 Visiting the AST is fast on a reasonably sized document. |
Basically, a bit nit picky but I’d say: the first sentence is correct but does not conclude to the second sentence. There are 2 mdast nodes. 1 mdast node is indeed mapped there. The other mdast node is mapped somewhere else.
I don’t think this is relevant: you are talking about custom If you can set
There are arguments to be made for several potential changes: your proposed change, or turning a Hence my question: how are you getting I have a feeling you want as few plugins as possible. |
To provide a bit more context, I wanted to support user-defined properties for links and images like this ![](myimg.png){ height=100 width=100 .border .border-white} and this ![][dog]{ height=100 width=100 .border .border-white}
<!-- References -->
[dog]: myimg.png (My users are already familiar with this curly-brace syntax because that's how you pass properties into directives, and I use a lot of them.) I made the following remark plugin to handle these types of attributes: import { SKIP, visit } from "unist-util-visit"
const nodeTest = (node) => {
return (
node.type === "text" &&
node.value.startsWith("{") &&
node.value.includes("}")
)
}
export default function remarkAttributes() {
// This plugin handles non-standard markdown attributes
// The best example is when we want to support CSS classes on images
// The input markdown looks like this
//
// ![](myimg.png){.w-96}
//
// This input is parsed into mdast nodes like this
// {
// type: 'paragraph',
// children: [
// {
// type: 'image',
// url: 'myimg.png',
// position: {...}
// },
// {
// type: 'text',
// value: '{.w-96}',
// position: {...}
// }
// ],
// position: {...}
// },
//
// This plugin parses the attributes in the image's sibling node and assigns them as
// hProperties of the image node, like this
//
// {
// type: 'image',
// url: 'myimg.png',
// data: {hProperties: {className: ['.w96']}}
// position: {...}
// }
//
// remarkRehype will then apply the classNames to the html element
return (tree) => {
visit(tree, nodeTest, (node, index, parent) => {
// Exit early if this node is not the next sibling of an image node
if (index === 0) return
// Get the previous sibling
const prevSibling = parent.children[index - 1]
// Exit early if the "{...}" isn't on the same line and immediately after the image/link/etc.
if (
prevSibling.position.end.line !== node.position.start.line ||
prevSibling.position.end.column !== node.position.start.column
) {
return
}
// Exit early if the previous sibling isn't an image or a link
if (
!["image", "imageReference", "link", "linkReference"].includes(
prevSibling.type
)
)
return
// Get everything between the first set of braces {...}
const value = node.value
const innerValue = value.match(/^{(.*?)}/)[1]
// Parse the properties
const tokens = innerValue.split(" ").filter((x) => x !== "")
if (tokens.length === 0) return
// Apply them to the image node as hProperties
const data = prevSibling.data || (prevSibling.data = {})
const hProperties = data.hProperties || (data.hProperties = {})
const className = hProperties.className || (hProperties.className = [])
tokens.forEach((x) => {
// We expect three forms of tokens:
// .class (e.g. .text-red-500)
// key=value (e.g. target="_blank")
// key (e.g. autoplay)
if (/^\.[\w\.\/-]+/.test(x)) {
className.push(x.substring(1))
} else if (/^[^=]+=[^=]+$/.test(x)) {
const { key, val } =
/^(?<key>[^=]+)=(?:['"]+)?(?<val>[^=]+?)(?:['"]+)?$/.exec(x).groups
hProperties[key] = val
} else {
hProperties[x] = true
}
})
if (hProperties.className.length === 0) delete hProperties["className"]
// Update this node's value by chopping off the "{...}" bit
node.value = node.value.slice(innerValue.length + 2)
// If the remaining string is empty, delete this node
if (node.value === "") {
parent.children.splice(index, 1)
return [SKIP, index]
}
})
}
} I think it's clear at this point that this is some custom functionality that only I need. So, feel free to close the issue and stop wasting brain cells on it 😂 |
Your code looks fine, what I don’t understand is how it leads to this issue? |
Are you suggesting that instead of this... ![][dog]{ height=100 width=100 .border .border-white }
<!-- References -->
[dog]: myimg.png I do this ![][dog]
<!-- References -->
[dog]: myimg.png { height=100 width=100 .border .border-white } ? I think I may have tried something like this a while back and this format prevents the parser from recognizing the definition. (I could be wrong). It's also a weird UI design, in my opinion, to tack the attributes onto the definition in this way. In some cases, it could be hard to tell where the URL ends and the properties begin. |
No, I am saying that I don‘t understand why there is any issue. Everything already works? Your markdown: ![][dog]{ height=100 width=100 .border .border-white }
[dog]: myimg.png Plus your plugin. Without any changes to this project. Is fine? |
Yes, my code works thanks to my override of the |
I don’t see how. Your plugin does not touch Your plugin touches imageReference. It sets data on them. That data is already handled by the default handler: https://github.com/syntax-tree/mdast-util-to-hast/blob/main/lib/handlers/image-reference.js |
Could you perhaps please make a minimum viable reproduction of the exact dependencies and code you have? |
Alrighty, I posted a reprex here. During the process, I remembered some of the reasoning behind why I did what I did, and I also understand your confusion better.. In my example, the input markdown looks like this ![][dog]{ height=100 }
[dog]: foo.mp4 My desire is to have the output look like this <p><video src="http://127.0.0.1:9199/foo.mp4" alt="" height="100" controls></video></p> However, there are a few things that I also need...
# My football picks!
:::paywall
1. Manchester
2. Real Madrid
3. Richmond
::: So, I think I decided to place my remarkFiles plugin before my remarkPaywall plugin, since I need to take inventory of all the referenced images and some of them might get chopped off by the paywall. But I may have a different solution here.. This is all very complex and specific to my needs, haha. You sure you want to go down this rabbit hole? Anyways, let me know if you need help reading my code. Cheers Oh, and just as an aside, if you want to see what my platform looks like and how it works, you can sign up for free or check out these youtube videos I made on it. |
Thanks for explaining more of the macro problem you’re facing, and providing all the code!
I recommend:
Not that complex! ;) I’ll close this for now. I think your root problems are better solved differently (moving to rehype). There are also viable ways to work around this (a custom handler). There could be a change, but there are 3 different changes we could make. To choose one, I’d want a good concrete problem that would be solved well by it. If one such problem surfaces, we can reopen! |
Initial checklist
Problem
I have some markdown like this 👇
I turn it into mdast like this 👇
Note the
data
property of the definition node, particularly the line"hName": "video"
. My hope was that this would render a<video>
tag in the HTML...remarkRehype
turns this mdast into the following hast 👇The resulting HTML element is a
<img>
, NOT a<video>
. I understand why this happens, but I think the attributes in thedefinition
node should (mostly) overwrite the attributes in theimageReference
node.Solution
The relevant code is in /lib/handlers/image-reference.js
Need to think about the appropriate solution here. Would be curious to hear your thoughts.?
Alternatives
I guess I could make my own handler, but I think this behavior should be the default.
The text was updated successfully, but these errors were encountered: