Hakyll get list of tags from list of posts - hakyll

I'm using Hakyll to make a blog index page where all my posts are listed. Each post has fields of date, title, preview and list of tags.
<!-- /blog.html -->
<div class="list">
$for(posts)$
<div class="entry-date">$date$</div>
<div class="entry-taglist">
$for(tags)$
<div class="tag">$body$</div>
$endfor$
</div>
<div class="entry-title">$title$</div>
<div class="entry-preview">$preview$</div>
$endfor$
</div>
The posts have the context as such:
---
title: S.P.Q.R.
tags: foo, bar1, bar2
---
How do I populate the context to satisfy the above template?
Here is my attempt following this post:
main = do
...
match "blog.md" $ do
route $ setExtension "html"
compile $ do
posts <- recentFirst =<< loadAll "posts/*"
let indexCtx =
listField "posts" tagContext (return posts) `mappend`
defaultContext in
pandocCompiler
>>= loadAndApplyTemplate "templates/blogindex.html" indexCtx
>>= relativizeUrls
postCtx :: Context String
postCtx =
dateField "date" "%d/%m/%Y" `mappend`
defaultContext
listContextWith :: Context String -> String -> Context a
listContextWith ctx s = listField s ctx $ do
identifier <- getUnderlying
metadata <- getMetadata identifier
let metas = maybe [] (map trim . splitAll ",") $ lookupString s metadata
return $ map (\x -> Item (fromFilePath x) x) metas
listContext :: String -> Context a
listContext = listContextWith postCtx
tagContext = listContext "tags" <> defaultContext
The page returns an empty div for .entry-taglist.

This is a bad workaround, but I just take in the tags as a string of comma separated values and modify the page's tags html elements in js
document.querySelectorAll('.entry-tags').forEach((x,i,o) =>
x.innerHTML = (x.innerHTML.split(",").map(x => '<div class="tag">' + x.trim() + '</div>').join("\n"))
);

Related

Cypress - verify if each table row in one column contains the same item

I have a table, however some kind of ag-grid, created by DIV's, not real table element:
<div role="row" row-index="1" >
<div col-id="name">Name 1</div>
<div col-id="age">25</div>
</div>
<div role="row" row-index="2" >
<div col-id="name">Name 1</div>
<div col-id="age">25</div>
</div>
I want to verify, if EACH field with col-id="name" contains the same item. I am testing kind of filtering, so if user filters the Name 1, I want to check each row, if there is only Name 1 and nothing else.
As each field which I need to check has the same col-id, I tried it this way:
cy.get('div[col-id="name"]').contains('Name 1')
or
cy.get('div[col-id="name"]').should('contain','Name 1')
but both cases passes even if some field contains another name, because it finds at least one match.
How to assert that each field with col-id="name" should contain ONLY Name 1 ?
Maybe use .each() to test the elements individually
cy.get('div[col-id="name"]')
.each($el => {
expect($el.text()).to.eq('Name 1')
})
Without .each()
cy.get('div[col-id="name"]')
.should($els => {
const names = [...$els].map(el => el.innerText)
expect(names.every(name => name === 'Name 1').to.eq(true)
})
You can verify the combined text of all elements
cy.get('div[col-id="name"]')
.invoke('text')
.should('eq', 'Name 1'.repeat(2)) // assuming two elements, equals "Name 1Name1"
or, this is better for asynchronous fetching of name
cy.get('div[col-id="name"]')
.should('have.text', 'Name 1'.repeat(2)) // equals "Name 1Name1"
When element count is unknown
cy.get('div[col-id="name"]')
.then($els => {
const count = $els.length
cy.wrap($els).should('have.text', 'Name 1'.repeat(count))
})
In case you don't want to apply assertion and just print out statements stating whether the element was found/not found, you can do like this:
cy.get('div[col-id="name"]').each(($ele, index) => {
if ($ele.text().trim() == 'Name 1') {
cy.log('Item Found at position ' + index)
} else {
cy.log(
'Expected Item Not found at position ' +
index +
'. Instead the item was ' +
$ele.text().trim()
)
}
})

html agility pack text inside in li

I am getting data inside <'li> tag like this:
var doc = new HtmlDocument();
doc.LoadHtml(html);
var list = new List<string>(doc.DocumentNode.SelectNodes("//li")
.Select(li => li.InnerText));
but if li has another tag inside like <em> that been ignored.
How can I keep everything inside in <li> without using InnerHtml?
thanks
What you want is OuterHtml.
Quoting from MDN:
The outerHTML attribute of the Element DOM interface gets the
serialized HTML fragment describing the element including its
descendants. It can also be set to replace the element with nodes
parsed from the given string.
To only obtain the HTML representation of the contents of an element,
or to replace the contents of an element, use the innerHTML property
instead.
var doc = new HtmlDocument();
doc.LoadHtml(#"
<ul>
<li>
<em>item 1</em>
</li>
<li>
<span>item</span> <em>2</em> <br/>
</li>
</ul>
");
var ul = doc.DocumentNode.Element("ul");
var lis = ul.Elements("li");
foreach(var li in lis)
{
Console.WriteLine("----------------- inner html -------------------");
Console.WriteLine(li.InnerText); //prints "Item N" (content only)
Console.WriteLine("----------------- outer html -------------------");
Console.WriteLine(li.OuterHtml); //prints <li> + all descending tags + </li>
}

check if there is a div that has the words `some text` and has the `i` tag

I have a class:
<div class = "abc def">
<i style="...."></i>
some text 2
</div>
<div class = "abc def">
<i></i>
some text
</div>
<div class = "abc def">
1 some text
</div>
how can I check if there is a div that has the words some text and there is the i tag in this div?
for this example, I have to get the first and the second div. the third div doesn't have the i tag, so then I won't get him.
I think it should be:
elements = driver.findElement(By.xpath(//div[contains(text(), 'some text')]));
if (elements.length > 0) {
for(var i = 0; i < elements.length; i++) {
if (elements[i].find('<i') != null) {
alert('the item: ' + i + 'is found');
}
}
}
The XPath expression more or less equals the natural language version:
//div[contains(., 'some text') and i]
If the <i/> tag may be contained within other elements, use .//i instead. In most cases you want to use . instead of text(), this joins all text nodes and scans the combined result, so <em>some</em> text would be matched, too.

Html Aglity pack extra <A> tag

The extra <A> in the following causes selectnode() to return too many elements. How can I remove the extra characters?
<DIV align=center><STRONG><A><A class=white
href="javascript: event_info = openWin('/events/search/index_results.cfm?action=plan&event_number=2013292001&cde_comp_group=CONF&cde_comp_type=&NEW_END_DATE1>=&key_stkhldr_event=&mixed_breed=N', 'eventinfo', 'width=800,height=600,toolbar=1,location=0>,directories=0,status=0,menuBar=0,scrollBars=1,resizable=1' ); event_info.focus()"><STRONG>Labrador
Retriever Club of the Piedmont</STRONG></A> </STRONG></DIV
>
You could select only those <a> tags, which have e.g. href attribute set:
var doc = new HtmlDocument();
doc.LoadHtml(html);
var anchors = doc.DocumentNode
.SelectNodes("//a[#href]")
.ToList();
foreach (var anchor in anchors)
{
//process your node here
}

Sorting Div's With PrototypeJS

I am looking for some help in sorting div's with PrototypeJS. Here is what I have so far:
document.observe('dom:loaded',function(){
$$('.sortcol').invoke('observe', 'click', function() {
if (this.hasClassName('desc')) {
var desc = false;
this.removeClassName('desc');
} else {
var desc = true;
this.addClassName('desc');
}
var colname = this.className;
var contentid = this.up(2).id;
sortColumn(contentid,colname,desc);
});
});
function sortColumn(contentid,colname,desc) {
$$('#'+contentid).select('.'+colname).sort(function(a,b){
if (desc) {
return (a.text.toLowerCase() >= b.text.toLowerCase() ) ? -1 : 1;
} else {
return (a.text.toLowerCase() < b.text.toLowerCase() ) ? -1 : 1;
}
});
}
Example data:
<div id="contentbox_Users" class="userList">
<div class="userListHeader">
<div class="userListHeaderCell col1">First Name</div>
<div class="userListHeaderCell col2">Last Name</div>
</div>
<div id="contentbox_People">
<div class="userListRow">
<div class="userListCell col1">John</div>
<div class="userListCell col2">Smith</div>
</div>
<div class="userListRow">
<div class="userListCell col1">Bob</div>
<div class="userListCell col2">Ray</div>
</div>
<div class="userListRow">
<div class="userListCell col1">Fred</div>
<div class="userListCell col2">Jones</div>
</div>
</div>
</div>
Basically anything with a class "sortcol", when it is clicked, I want it to sort by the column name clicked (class). The first issue is I need to be able to get the class name correctly when there is multiple classes. The classes are all like col1, col2, etc. How would I find the correct class?
The second thing is changing sortColumn so that it keeps column data together (each row is wrapped by another div) and output the result, replacing the current data.
This needs to be done in prototypejs and I can't change the code to tables.
Thanks in advance for the help.
For the first part of your question it would be much easier if the column name was it's own attribute like rel or data-*, but you say you cannot change the HTML. It is possible to pick out the likeliest class with regex...
var colname = this.className.match(/\bcol\d+\b/).first()
But this is unnecessary if we assume every row has the same columns in the same order. This would be a safer assumption if a table were used.
var colnumber = this.up().childElements().indexOf(this);
The second part of your question is easy, just sort the rows instead of the cells.
Your draft sortColumn function doesn't actually change the elements - select returns an array of element references, not their container - so you need to do something with the resulting array. Luckily any append or insert action of an element causes it to be removed from it's parent first, so simply append them once more and they'll assume the correct order. No replacing is needed, I've seen libraries that bizarrely convert the elements to HTML, concatenate that then reinsert it!?!
The following has been tested.
document.observe('dom:loaded',function() {
$$('.userListHeaderCell').invoke('observe', 'click', function() {
this.toggleClassName('desc');
var colnumber = this.up().childElements().indexOf(this);
var content = this.up(2); // use the element directly instead of it's ID
sortColumn(content, colnumber, this.hasClassName('desc'));
});
});
function sortColumn(content, colnumber, desc) {
content.select('.userListRow').sort(function(a,b){
var atext = a.down(colnumber).innerHTML.stripTags().toLowerCase();
var btext = b.down(colnumber).innerHTML.stripTags().toLowerCase();
return atext.localeCompare(btext) * (desc ? -1 : 1);
}).each(Element.prototype.appendChild, content);
}
This to me seems like you are creating tabular data. So why not use a table? And once you use a table, there are many sorting scripts out there. A quick google came up with this one.

Resources