Wiki Code

Wiki Code

Wiki Code
turn stick

//quote

Here, I want to introduce Jekyll to you and help you to use it taking our team’s wiki repo as example.

Another, I want to share with you the JS tool I created to help with your markdown pages.

If you are curious about the flowing content, I recommend you to open our source code while reading it.

//quote

What’s the origin idea?

The wiki, as the final presentation platform, is often the last process, and in order to free up time for other work and allow time to test, find bugs, and fix them, I would like to make use of means that do not require frequent copy and paste to form HTML documents in the pursuit of speed. But we also want it to be aesthetically pleasing, to emphasise key points, and to be easy to read.

In fact, this thinking started at the beginning of this year (2024), when iGEM gitlab wasn’t open yet, and I didn’t know there would be a Markdown Template, and I ended up choosing Jekyll as our Wiki framework for speed and scalability reasons.

But I have to admit that if you want a very beautiful website, Jekyll may not fit your needs. But if you has the perspective that “Content is King”, you must try it.

Why Jekyll

So, what are the advantages of Jekyll over html, or Markdown Template given by the iGEM team?

First of all, it’s the high reusability of the code. Jekyll can use “include” to refer to the same html parts, which is of course possible with most of the frameworks, but in addition to that, we can use Jekyll’s “Data file” feature to generate some content faster and more accurately, and due to the use of yml, csv, and other formats, it’s much easier to modify These structures become much easier (for example you can see that my Member page and nav use this technique to quickly adjust the Member’s categorisation hierarchy or the nav’s distribution). And, thanks to front matter, it’s also easier to manage pages.

Secondly, Jekyll scalability is better, Jekyll support scss, can be more convenient to deal with some of the style, more importantly, this can manipulate bootstrap source files (scss format), modify _variables.scss file allows us to more easily modify the overall page colour. And Jekyll has a wealth of plug-ins, for example, I used jekyll-toc this time, which quickly and accurately help me to generate the catalogue, so that I do not have to copy and add one by one.

Instructions

The basic information of Jekyll can be get Here. If you don’t have the basic knowledge of web coding, I recommend you to learn html, css, javascript first, which are the basic of all website viewing. Here I am defaulting to the idea that you are already familiar with some of the basics of web front-end programming.

The following introduction contains a lot of my personal experience, all based on our team’s current wiki, which you can read below while browsing through our gitlab.

If you use Jekyll, then the entire web page we have two places to do the trick (using scripts), one is the generation stage of the web page, that is, Jekyll processing page stage, this stage can be used liquid, Jekyll plug-ins to enrich the generation of the page, the other is the generation of the page after the stage, that is, the web page in the processing of JavaScript! The other is the post-generation stage, which is the JavaScript processing stage in the web page.

We want to generate a variety of pages quickly and accurately, so we can start with both.

Using Jekyll to help build you page

Plan the layout of your page using _layout

We all know that not only wiki, but any form of web page will have recurring layout and content, such as Nav, Footer, Title and so on elements, some of them will appear in any page, some will only appear in part of the page, so when we are designing web pages, we should firstly take into account, which types of web pages to be designed, and what common things will appear in each of them, so that This not only saves us the time of “copying and pasting”, but also allows us to quickly change the content of some basic elements anytime and anywhere.

For example, our team’s footer has gone through many large adjustments in the later stages, and I only need to modify the footer.html in the _include folder to complete the modification, this is because all my pages use _layout in the layout, and all the layouts are used in the default layout, which uses the include footer.html, so the whole body is affected by one hair, can quickly complete the modification.

But that’s only part of it, what’s more interesting is that when it comes to content pages, I just design a page framework, and then add all kinds of generators, interpreters, and then type in a simple md document to generate all kinds of pages I want, and this doesn’t conflict with other pages that don’t need this kind of framework, such as the home page, or the Team-Member pages for example, because they don’t use the Content layout, but instead use the withTitle layout or the default layout. In fact, for reasons of time and lack of manpower, we only designed two layouts, and if we had the chance, I hope it could be three or even four layouts, to accommodate more pages to be envisioned and designed.

Use _data to quickly tweak your data-driven pages

Jekyll handles Data Files, which then allows us to use the data to generate pages. I think the biggest advantage of this feature is that it can be restructured very easily. For example you can see that in our team’s Wiki repo repository there are three files under the _data folder serving two functions, one for generating nav and two for building Team-Member pages, which makes it possible to quickly adjust the different hierarchical structures. For example, we can quickly adjust a person’s tenure and categorisation in the Team-Member page without having to massively copy some of the layout of the Team-Member page.

However, for some reason, when working with files in csv format, I can’t call them directly as described in the Jekyll Doc, I’ve tried a lot of ways, none of them work, so I’ve utilised the loop offset method for the time being, it’s not yet clear why this is the case, it could be that I’ve configured my environment incorrectly, but this method of calling works for me for the time being.

Using _sass to make better use of bootstrap

Think the colours in bootstrap don’t look good? Want bigger fonts and bigger borders? You can now use npm to download the bootstrap source files, and then put all the bootstrap scss files in the _sass folder, Jekyll comes with a scss compiler, you can get a more comfortable, personalised bootstrap.

Handbook of the JS tools

Although markdown translated into html is very convenient, but we also have to admit that the convenience of things, scalability is low, we can not be convenient in markdown directly add special elements, such as the title of the table, such as the picture of the description of the paragraph, such as pdf iframe and so on.

As a result, I’ve built a content-switch-by-md.js system that aims to make it possible for md documents to contain more diverse and personalised content through simple processing.

Content-switch-by-md.js

By referring to this file, adding some of the markup and effects you want to work with, and adding consistent markup to the md file, you can render a more diverse set of elements in the final page.

Usage

var elementSwitcher = new switcher(document.getElementById('content'),"//");
elementSwitcher.RegisterBlockTagFunction("referrence",null);
elementSwitcher.RegisterBlockTagFunction("pdf",(node)=>{
    node.childNodes.forEach((e, index) => {
        if(e.nodeName=="A"&&e.href.endsWith(".pdf")){
            var link = e.href;
            var alter = e.alter;
            var pdf = document.createElement("iframe");
            pdf.src = link;
            pdf.alter = alter;
            e.replaceWith(pdf);
        }
    }); 
    return node;
});


elementSwitcher.RegisterTailTagFunction("no-indent",(node)=>{
    var noIndent = document.createElement("p");
    noIndent.classList.add("no-indent");
    noIndent.textContent=node.textContent;
    return noIndent;
});
elementSwitcher.RegisterTailTagFunction("caption",(node)=>{
    var caption = document.createElement("caption");
    caption.textContent=node.textContent;

    var table = node.nextElementSibling;
    table.appendChild(caption);
    return;
});

elementSwitcher.start();

First construct an instance of the switcher to perform the conversion of the md document, its constructor contains two parameters, one is of class HTMLElement, which represents the content that will be processed, consistent with the position of { {content} } set in _layout. The second parameter is of type str, representing the identifier of the tagged content. For example, if this is “//”, then all the content to be processed in the md document should be marked with “//MyCustomMark” or similar characters.

We then register the name and method of the tag we want to process, the RegisterBlockTagFunction and RegisterTailTagFunction methods correspond to the registration of Block tags and Tail tags respectively. The first parameter indicates the name of the tag, and the second parameter indicates the processing method for the element that meets the requirements. The parameter of the method is the original HTMLElement instance after removing the content of the tag, and the method should return the instance of the processed HTMLElmemnt, and null is the default processing, and the default processing of BlockMarkFunction is to process all of the The default processing of BlockMarkFunction is to add all the conforming elements to a new div whose class is the name of BlockMark, and the default processing of TailMarkFunction is to add the class of the TailMark name to the P element. both functions can return null, which means that the entire paragraph is directly deleted.

When registration is complete, the start() method is called and the conversion is about to begin.

Principle

The switcher iterates over all first-level children of the target element, copying the element into a new one used to replace the target element. If the element is p-tagged and ends with a Block/Tail tag, it enters the corresponding processing session, copying the results of the processing into the new element until the target element is traversed, deleting the target element, and replacing it with the new element that was copied.

BlockMark

There are two types of tags that the switcher can handle, Block tags and Tail tags.

The Block tag builds a new div element, and the content of the div element is what the two Block tags include, like the following:

//referrence

[1]xxxx,xxxx

[2]xxxx,xxxx

//referrence

Here, the switcher builds a new div element containing two p-child elements, “[1]xxxx,xxxx” and “[2]xxxx,xxxx” in this case, and this div element’s class is “referrence”.

BlockMarkFunction

For functions registered for BlockMark processing, it will be called upon detection of a blockmark, and it will be called once for each element that is included in the BlockMark.

For example, we have registered pdf BlockMarkFunction here:

elementSwitcher.RegisterBlockTagFunction("pdf",(node)=>{
    node.childNodes.forEach((e, index) => {
        if(e.nodeName=="A"&&e.href.endsWith(".pdf")){
            var link = e.href;
            var alter = e.alter;
            var pdf = document.createElement("iframe");
            pdf.src = link;
            pdf.alter = alter;
            e.replaceWith(pdf);
        }
    }); 
    return node;
});

And if our md file looks like this:

//pdf

[pdf](https://igem.org/1.pdf)

pdf

//pdf

Here the “node” will be a tag, p tag, #text, etc., we want to get a pdf of the iframe, so in the function we search for node in the possible existence of the a tag, if the a tag src to pdf end, then create a new iframe tag, and replace the original a tag, so as to achieve the conversion. Finally, the conversion will be completed to return to the element, copied to the new element.

In addition, the p-element where the mark is located will be ignored, such as this “//pdf”, will not be copied to the new element, so be sure to place the Block mark alone in a new paragraph, otherwise the rest of the content will be ignored.

TailMark

The Tail tag, on the other hand, appears at the end of a paragraph and will only appear once.

Here, for example:

Where x is a parameter//no-indent

The “//no-indent” flag means that the switcher is going to transform this line.

TailMarkFunction

TailmarkFunction is called after detecting a paragraph ending in a TailMark, and is called with the content after the mark has been removed.

Let’s say we register a function like this

elementSwitcher.RegisterTailTagFunction("no-indent",(node)=>{
    var noIndent = document.createElement("p");
    noIndent.classList.add("no-indent");
    noIndent.textContent=node.textContent;
    return noIndent;
});

And our md document is shown above, so this function is called with a p tag with the content “where x is a parameter”, which is what it would look like with the “//no-indent” tag removed.

This function above simply adds the class named TailMark (no-indent) to the p tag, which can actually be set to null without registering, which is the default handling.

In addition, TailMarkFunction more advantageous function is that you can add Caption to the Table, such as the following function:

elementSwitcher.RegisterTailTagFunction("caption",(node)=>{
    var caption = document.createElement("caption");
    caption.textContent=node.textContent;

    var table = node.nextElementSibling;
    table.appendChild(caption);
    return;
});

Processed md documents:

This is a Table//caption

|A	|B	|
|--	|--	|
|1	|2	|

Here, I set the paragraph where the caption mark is located to be the element immediately above the Table, so TailMarkFunction can directly get the table element and add the caption, and return null to indicate that it directly deletes this original p element.

Restrictions and Todo

There are still some points that need to be improved in this tool. The first is that it does not support in-paragraph markup, which makes it impossible to specialise some of the text in a paragraph. However, since we do not have such a need this time, so we did not continue to improve, if you have such a need, welcome to continue to improve!

Another is the table across the rows and columns of the function has not yet been dealt with perfect, did not have time to integrate into this class. You are welcome to continue designing this feature.

Crossing Rows in a Table

Another pain point in using markdown to build web pages is the inability to build tables that span rows. My thinking here is to iterate through each cell and if the cell is empty, then the rowspan++ of the cell above that cell. The code is shown below, you can just copy and modify the code as needed.

function TableTackle(e){
    if(e.nodeName=="TABLE"){
        var rows = e.rows;
        var cells = rows[0].cells;
        //Create a new two-dimensional array to record whether each cell is empty or not
        var cellStatus = Array.from({ length: rows.length }, () => Array(cells.length).fill(true));
        

        for(var i=0;i<rows.length;i++){
            var cells = rows[i].cells;
            for(var j=0;j<cells.length;j++){
                if(cells[j].textContent==" "||cells[j].textContent==""||cells[j].innerHTML=="&nbsp;"){//Determining Empty Cells
                    cellStatus[i][j]=false;
                }
            }
        }
        for(var i=rows.length-1;i>=0;i--){
            var cells = rows[i].cells;
            for(var j=cells.length-1;j>=0;j--){
                if(cellStatus[i][j]==false){
                    cells[j].remove();
                    for(var k=i;k>=0;k--){
                        if(cellStatus[k][j]){
                            rows[k].cells[j].rowSpan++;
                            break;
                        }
                    }
                }
            }
        }
    }
}

Restrictions and Todo

Currently only the handling of tables that span rows is supported, not yet the handling of tables that span lists. My idea is to set the content of the table that needs to be spanned to a special identifier, and then make the columnspan++ of the table on the left to span the table. But due to time and energy problems have not yet completed, you are welcome to modify!

Some Troubleshooting when building wikis

How to locate the Mathjax fonts in my teams’ storage library.

There is a function in Mathjax JS file that is to add the url for the fonts. The font name is usually addFontURLs. Search it and replace the value with your teams mathjax font library then got it.

If you can still not handle it, you can down load my mathjax file from here and find “igem” to replace the url.

Where Can I get the free font?

I recommend you a website, where has many offical free font with license files. I got our pages’ font from there.

It’s Free Fonts & Typefaces › Fontesk

A thank-you note

Some of our styling (table styling, etc.) came from ZJU-China 2023. They are a great team, while the wiki they wrote won the best wiki of the year award in 2023, and gave our team a lot of inspiration in wiki design, thank you very much.

Their wiki: 2023-zju-china

At first, I imitate their styles to test the Content-switch-by-md.js. I reserve it and you can still see it now by clicking here