Hugo data template telephone number gets rendered as a hash - hugo

I have a weird problem with Hugo and data templates.
I have link content in a json data template that i try to render inside an a tag like so:
data/foobar.json:
{ [ { link: 'tel:+123123', text: 'a' }, { link: 'mailto:mail#example.com', text: 'b' ] }
partial.html:
{{ range .Site.Data.foobar }}
{{ .text }}
{{ end }}
Which Produces:
a
b
For some reason the first anchor target renders out as a random hash, but the second one correctly. This seems to happen only when i start the link with tel:, and i can't understand why?

Probably syntax should be: {{ .link | safeURL }}
https://gohugo.io/functions/safehtml/
https://gohugo.io/functions/safehtmlattr/#readout
https://gohugo.io/functions/safeurl/
https://gohugo.io/functions/urlize/#readout
This is intentional as the link is not being sanitized.
See docs on Go and the way elements are rendered.
Off the cuff and not near my station - one of the above will point you in the right direction.
Check it and let me know.

Related

Get a list of components and their props from MDX string - by regex?

My problem - Get components & props from MDX/JSX string
I have an MDX string with Front-matter meta data in YAML, some regular text, some markdown and some React components.
I need to get the list of all React (non-HTML) components from it with their parameters.
So given this example:
---
title: Title of documents
tags: one, two, three
---
# My heading H1
Some text in paragraph. Than the list:
- First item
- Second item
More text with [a link](https://example.com).
<Articles category="theatre" count={3} />
Further text with more information.
<Newsletter categories={['theatre', 'design']} />
<MultilineComponent
paramA="A"
paramB="B"
/>
<ComponentWithChildren param="value">
Some children
</ComponentWithChildren>
... I would need this output:
[
{
component: 'Articles',
props: {
category: 'theatre',
count: 3,
},
},
{
component: 'Newsletter',
props: {
categories: ['theatre', 'design'],
}
},
{
component: 'MultilineComponent',
props: {
paramA: 'A',
paramB: 'B',
}
},
{
component: 'ComponentWithChildren',
props: {
param: 'value',
}
}
]
Also I need to do this on the server, so I don't have access to browser functionality (window, document, etc.).
What I've tried
Some basic Regex, but as I'm far from being professional in regexing, now I have two problems. :)
Is there some built in way how to parse JSX string to get a list of components & props in the way that I've described above? Or is there some maybe some other parser that I can use to solve this? If not, is there some Regex pattern I can use to get this?
Quick summary on "Why"
During the build of my Next.js project I need to determine which data is actually needed for each MDX page in the bundle. So if I see this in the Mdx file:
...other text
<Articles category="theatre" count={3} />
...other text
... which I'm somehow able to parse to this:
component: "Articles"
category: "theatre"
count: 3
that's enough info for me to know that I need to send those data to the page:
[
{
title: 'Romeo and Juliet',
category: 'theatre',
},
{
title: 'The Cherry Orchard',
category: 'theatre',
},
{
title: 'Death of a Salesman',
category: 'theatre',
}
]
Would you be able to help me with this? Thank you in advance! 💪
Edit
#Rango's answer pointed me to the right direction! One caveat: jsx-parser can not handle multiline components to which Rango's proposed the following solution:
if (rsp.test(c)) continue; // add before /index.js:374
This however removes all whitespace from string attributes. So I've replaced it with this:
if (/[\n\r]/.test(c)) continue; // this should remove lines only
So far this solution works. I would be more comfortable to use more stable libraries, but unfortunately none of the proposed solution worked for me (acorn-jsx, react-jsx-parser, babel/parser).
Not sure that parsing JSX with regular expressions is efficient because curly brackets {...} can contain any JS expression, so if you choose this way then prepare to parse Javascript as well.
Fortunately, there are a bunch of JSX parsers that can do it for you. E.g. the first one I picked was jsx-parser and this small beast can parse your example (with a simple trick). The shape of the result is quite different but you can transform it to match your needs.
var test = `
---
title: Title of documents
tags: one, two, three
---
# My heading H1
Some text in paragraph. Than the list:
- First item
- Second item
More text with [a link](https://example.com).
<Articles category="theatre" count={3} />
Further text with more information.
<Newsletter categories={['theatre', 'design']} />
<MultilineComponent
paramA="A"
paramB="B"
/>
<ComponentWithChildren param="value">
Some children
</ComponentWithChildren>
`
const components = [...test.matchAll(/<[A-Z]/g)]
.map(match => JSXParser(test.slice(match.index)))
document.getElementById('result').textContent = JSON.stringify(components, null, 2)
<script src="https://unpkg.com/jsx-parser#1.0.8/index.umd.js"></script>
<pre id="result">Hello</pre>
In my snippet I used UMD version of the package, but for node.js consider choosing ES module ofc.

Render last element of nested array of Object in Angular HTML template

I have an Object named comment, which has description property and editedDescription Array. Whenever user edits description, new description will be pushed into editedDescription array.This array is used so as to maintain history of edited descriptions.
Following is my comment Object :
export interface Comment {
userName: String,
userImage: String,
description: String,
upvote : Number,
downvote : Number,
createdDate: Date,
replies: [
{
userName: String,
userImage: String,
description: String,
upvote : Number,
downvote : Number,
createdDate: Date
}
],
editedDescription: [
{
Description: String,
editedDate: Date
}
]
}
In order to output last element of editedDescription array, I have tried :
{{ comment?.editedDescription[editedDescription?.length-1]?.Description}}
I am getting following error - 'Cannot read property '-1' of undefined '
I have made use of safe navigation operator to check if array exists but im getting cannot read -1 of undefined
this is my code where in part is failing
<div *ngIf="comment?.editedDescription?.length == 0 ; else edited_comment">
<p> {{ comment?.description }}</p>
</div>
<ng-template #edited_comment>
<p> {{ comment?.editedDescription[editedDescription?.length-1]?.Description}}</p>
</ng-template>
*ngIf conditions check if there is no data in editedDescription array, use description property else use last element of editedDescription array.
I think it needs to be the following, as at the moment you are not trying to access the array through comment where it sounds like it is stored.
{{ comment?.editedDescription[comment.editedDescription.length -1 ]?.Description}}
You still need to access comment when looking for the length. Put the safety operators back in when you are happy it is working. Below shows a simplified example of what you have tried.
const obj = { hello: [1, 2, 3] };
console.log(obj.hello[obj.hello.length - 1]); // last element, expected 3
// console.log(obj.hello[hello.length - 1]) // This is like what you have done.

VueJS Array update bug

I have a JSFiddle below to explain my problem but basically I have an array called tiles which has a title. When the instance is created() I add a field to this array called active
I then output this array in an <li> element and loop through it outputting the title and active objects. My problem is as you can see in the fiddle when I run v-on:click="tile.active = true" nothing happens to the active state written in the <li> element
but if I run v-on:click="tile.title = 'test'" it seems to update the active object and the title object.
Its strange behaviour I can't seem to work out why. Does anyone have any ideas?
https://jsfiddle.net/jgb34dqo/
Thanks
It's to do with Vue not knowing about your properties, change your array to this:
tiles: [
{
title: 'tile one',
active: false
},
{
title: 'tile two',
active: false
},
{
title: 'tile three',
active: false
}
]
This allows Vue to know about the active property and in turn it knows to monitor that variable.
It's worth looking at this link about Vue Reactivity as it helps with understanding how and when data will change automagically.
If you must add the properties after
take a look at $set. It allows you to add props to an object that then get watched by vue. See this fiddle, notice the change:
this.tiles.forEach(function(tile) {
// Tell vue to add and monitor an `active` prop against the tile object
this.$set(tile, 'active', false);
}.bind(this))

Now angularjs elvis is not required?

I was watching a tutorial and learn about Elvis in angularjs. Then I create an object:
cabecaDeGelo{
name: 'jailson',
lastname: 'mendes',
address: {
street: null,
lol: 'lol'
}
}
And tried
Name: {{ cabecaDeGelo.name}} -> works fine
Street: {{ cabecaDeGelo.address.street }} -> works fine
WHAT???
{{ cabecaDeGelo.address.street }} generate a error 'cause doesn't exist! It is NULL! tutorial show a console error. I was thinking it's a version problem? Now I don't need anymore ELVIS?
like: {{ cabecaDeGelo.address?.street }}?
I was confused.
My configuration is#angular/cli: 1.0.4 / node: 6.10.3
Displaying a null or undefined value is not a problem (and has never been, neither in JavaScript, nor in an angular expression).
What is a problem and causes an exception is to display a property of a null or undefined reference.
For example:
const foo = {
bar: null
}
Displaying foo.bar if not a problem. What is a problem if to display, for example, foo.bar.baz, because you can't access a property (baz) of a null (or undefined) reference (foo.bar). That's where the null-safe navigation operator is needed:
{{ foo.bar?.baz }}: OK
{{ foo.bar.baz }}: exception

Using ngRepeat to create different elements at same level

I'd like to use a configuration object to display a list. The problem is that this list has different types of elements it can contain: item, divider, and subheader.
A simple configuration for this list might be:
var list = [
"item",
"item",
"divider",
"subheader",
"item",
"item"
];
How do I write an ngRepeat attribute that would output something like this:
<md-menu-content>
<md-menu-item>...</md-menu-item>
<md-menu-item>...</md-menu-item>
<md-menu-divider></md-menu-divider>
<md-subheader>...</md-subheader>
<md-menu-item>...</md-menu-item>
<md-menu-item>...</md-menu-item>
</md-menu-content>
I'm not even sure this is possible. Looking at the documentation for ngRepeat, it can only be used as an attribute, so something like <ng-repeat>...</ng-repeat> is not an option. md-menu-item, md-menu-divider, and md-subheader also have to be elements as they're part of Angular Material and even if list item and the subheader aren't specifically restricted to being an element (unlike the divider), their CSS styles are written to be for elements only.
I've also played around with ngRepeatStart, but that's an alternative to ngRepeat, and they, as far as I can see, cannot be used together so that ngRepeat would be one level above ngRepeatStart.
Using the directive from this answer, you can do something like the following:
$scope.list = [
"item",
"item",
"divider",
"subheader",
"item",
"item"
];
$scope.attrs = [];
for(var i = 0; i < $scope.list.length; i++){
$scope.attrs.push([{attr: 'md-menu-' + $scope.list[i], value: ''}]);
}
html:
<div ng-repeat="item in attrs" dyn-attr="item">
something
</div>
See this jsfiddle
Of course you can tweak this however you want. The directive of the linked answer adds attributes based on a list, but you'll probably want to create a directive that just uses something like dyn-md-attr="item"
Using the answer by #devqon I have a partial solution to this problem that works, but generates a lot of unnecessary digests that need to be dealt with. Nevertheless, it creates dynamic element with a directive that replaces the current element.
The fiddle is here: http://jsfiddle.net/qk96y2rw/
Here's the code:
myApp.directive('dynAttr', function() {
return {
scope: { list: '=dynAttr' },
link: function(scope, elem, attrs){
//console.log(scope.list);
for(attr in scope.list){
if (scope.list[attr].attr) {
console.log(scope.list[attr].attr);
var replacementElement = document.createElement(scope.list[attr].attr);
replacementElement.innerHTML = elem.html();
elem.replaceWith(replacementElement);
}
}
}
};
});

Resources