This website is meant to be a quick way to show how to do stuff that people ask (or that I thought would be a nice demo), it will complement theofficial documentation.
It's not meant to be beautiful, rather just show how to get specific stuff done. If one block answers one of your question, make sure to checkthe source to see how it was done. The ordering is reverse chronological but just use the table of contents to guide you to whatever you might want to explore.
Note: an important philosophy here is that if you can write a Julia function that would produce the HTML you want, then write that function and let Franklin call it.
Note 2: the numbering in georgian script in the table of content is on purpose (though for no particularly good reason other than that it looks nice... 🇬🇪)
Dataframe
to HTML tableDataframe
to HTML tableIf you have some data that you would like to manipulate and render nicely inFranklin
, you can use the following snippet relying onDataFrames.jl
andPrettyTables.jl
.
The followingDataframe
:
val = [1,2,3,4]tag = ['a','b','c','d']math = ["\$x\$", "\$y^2\$", "\$\\sqrt{z}\$", "\$\\Omega\$"]DataFrame(; val, tag, math)
will be rendered as:
val | tag | math |
---|---|---|
1 | a | \(x\) |
2 | b | \(y^2\) |
3 | c | \(\sqrt{z}\) |
4 | d | \(\Omega\) |
This done via ahfun_render_table
which can be found inutils.jl
.
How to make a section expand when clicked, so that content is initially hidden? (Based onthis html guide.)
\newcommand{\collaps}[2]{~~~<button type="button" class="collapsible">~~~ #1 ~~~</button><div class="collapsiblecontent">~~~ #2 ~~~</div>~~~}
/* Style the button that is used to open and close the collapsible content */ .collapsible { background-color: #eee; color: #444; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: inherit;}/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */.active, .collapsible:hover { background-color: #ccc;}/* Style the collapsible content. Note: hidden by default */.collapsiblecontent { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}
<script> var coll = document.getElementsByClassName("collapsible"); var i; for (i = 0; i < coll.length; i++) { coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block") { content.style.display = "none"; } else { content.style.display = "block"; } }); }</script>
With these definitions, the expandible code sections could be added!
lists
Item 1
Item 2
And all other stuff processed by Franklin!
Currently if you're saving a figure in a code block, you need to specify where to place that figure, if you don't it will go in the current directory which is the main site directory, typically you don't want that, so one trick is to use the@OUTPUT
macro like so:
using PyPlotfigure(figsize=(8,6))plot(rand(5), rand(5))savefig(joinpath(@OUTPUT, "ex_outpath_1.svg"))
and that directory is among the ones that are automatically checked when you use the\fig
command (e.g.\fig{ex_outpath_1.svg}
):
if you're writing tutorials and have lots of such figures and you don't want your readers to see these weird@OUTPUT
or you can't be bothered to add#hide
everywhere, you can set a variableauto_code_path
totrue
(either locally on a page or globally in your config), what this will do is that each time a cell is executed, Julia will firstcd
to the output path. So the above bit of code now reads:
using PyPlotfigure(figsize=(8,6))plot(rand(5), rand(5), color="red")savefig("ex_outpath_2.svg")
and like before just use\fig{ex_outpath_2.svg}
:
Note: since this was meant to be a non-breaking change,auto_code_path
is set tofalse
by default, you must set it yourself totrue
in yourconfig.md
if you want this to apply everywhere.
This page shows an example using WGLMakie + JSServe. It assumes you're familiar with these two libraries and that you have the latest version of each.
Note that it requires WebGL to work which might not be enabled on all browsers.
Here's a page where the content is generated from aWeave.jl.
If you prefer MathJax over KaTeX for maths rendering, you can use that. For now this is not fully supported and so taking this path may lead to issues, please report them and help fixing those is welcome.
Head tothis page for a demo and setup instructions.
Let's say you have a filecontent.md
and you would like to include it in another page as if it had been written there in the first place. This is pretty easy to do. You could for instance use the following function:
function hfun_insertmd(params) rpath = params[1] fullpath = joinpath(Franklin.path(:folder), rpath) isfile(fullpath) || return "" return read(fullpath, String)end
One thing to note is that all.md
files in your folder will be considered as potential pages to turn into HTML, so if a.md
file is meant to exclusively be used "inserted", you should remove it from Franklin's reach by adding it to theignore
global variable putting something like this in yourconfig.md
:
@def ignore = ["path/to/content.md"]
Here's an example with the insertion of the content of a filefoo/content.md
; the result of{{insertmd foo/content.md}}
is:
This is some content in acontent.md
file, it can contain whatever Franklin-compatible markdown as you want \(\alpha\beta\):
or code for instance
for i in 1:5 println("*"^i)end
***************
You can look atutils.jl
for the definition of thehfun
(same as above), atindex.md
to see how it's called and atfoo/content.md
for the content file. Finally you can also check out theconfig.md
file to see how the content page is ignored.
The date of last modification on the page is kept in thefd_mtime_raw
internal page variable, there is also a pre-formattedfd_mtime
.
Last modified: 2024-04-16 or April 16, 2024
This is a short demo following a discussion on Slack, it shows three things:
how to mark a block as "run here directly and show the output" without having to explicitly add a path and use a\show
that types are now shown properly as they would be in the REPL.
that continuation works from cell to cell (i.e. you can assume that a cell further below another cell has access to what was defined in the first)
s = "hello"struct T; v::Int; end[ Dict(:a => T(1)), Dict(:b => T(2)),]
2-element Vector{Dict{Symbol, T}}: Dict(:a => T(1)) Dict(:b => T(2))
Here's another cell
T(1)print(s)
hello
Suppressed output:
X = randn(2, 3);
It's fairly easy to add a "copy" button to your code blocks using a tool likeclipboard.js
. In fact on this demo page, as you can see, there is a copy button on all code blocks. The steps to reproduce this are:
copy theclipboard.min.js
to/libs/clipboard.min.js
(note that this is an old version of the library,1.4
or something, if you take the most recent version, you will have to adapt the script)
load that in_layout/head.html
adding something like
<script src="/libs/clipboard.min.js"></script>
add Javascript in the_layout/foot.html
, somethinglike this
adjust the CSS, for instancesomething like this
and that's it 🏁.
Following up on#008, here's a custom environment for Tikz diagrams using theTikzPictures.jl package.
Let's first see what you get for your effort:
Cool! Modulo a div class that shrinks the image a bit, the code that was used here is very nearly a copy-paste from an example in thetikz-cd docs, the only difference is one additional bracket with the file name (heretcd1
):
\begin{tikzcd}{tcd1}A \arrow[r, "\phi"] \arrow[d, red] & B \arrow[d, "\psi" red] \\ C \arrow[r, red, "\eta" blue] & D\end{tikzcd}
The correspondingenv_tikzcd
function is in theutils.jl
file and is quite simple.
You can define new commands and new environments using essentially the same syntax as in LaTeX:
\newcommand{\command}[nargs]{def}\newenvironment{environment}[nargs]{pre}{post}
The first one allows to define a command that you can call as\command{...}
and the second one an environment that you can call as
\begin{environment}{...}...\end{environment}
In both cases you can have a number of arguments (or zero) and the output isreprocessed by Franklin (so treated as Franklin-markdown). Here are a few simple examples:
\newcommand{\hello}{**Hello!**}Result: \hello.
Result:Hello!.
\newcommand{\html}[1]{~~~#1~~~}\newcommand{\red}[1]{\html{<span style="color:red">#1</span>}}Result: \red{hello!}.
Result: hello!.
\newenvironment{center}{ \html{<div style="text-align:center">}}{ \html{</div>}}Result: \begin{center}This bit of text is in a centered div.\end{center}
Result:
\newenvironment{figure}[1]{ \html{<figure>}}{ \html{<figcaption>#1</figcaption></figure>}}Result: \begin{figure}{A koala eating a leaf.}\end{figure}
Result:
Much likehfun_*
, you can have commands and environments be effectively defined via Julia code. The main difference is that the output will be treated as Franklin-markdown and so will bereprocessed by Franklin (where for ahfun
, the output is plugged in directly as HTML).
In both cases, a single option bracket is expected, no more no less. It can be empty but it has to be there. See alsothe docs for more information.
Here are two simple examples (see inutils.jl
too):
function lx_capa(com, _) # this first line extracts the content of the brace content = Franklin.content(com.braces[1]) output = replace(content, "a" => "A") return "**$output**"end
Result: \capa{Baba Yaga}.
Result:BAbA YAgA.
function env_cap(com, _) option = Franklin.content(com.braces[1]) content = Franklin.content(com) output = replace(content, option => uppercase(option)) return "~~~<b>~~~$output~~~</b>~~~"end
Result: \begin{cap}{ba}Baba Yaga with a baseball bat\end{cap}
Result:
BaBA Yaga with a BAseBAll BAtOf course these are toy examples and you could have arrived to the same effect some other way. With a bit of practice, you might develop a preference towards using one out of the three options:hfun_*
,lx_*
orenv_*
depending on your context.
When you callserve()
, Franklin first does a full pass which builds all your pages and then waits for changes to happen in a given page before updating that page. If you have a pageA
and a pageB
and that the pageA
calls a function which would need something that will only be defined onceB
is built, you have two cases:
A common one is to have the function used byA
require alocal page variable defined onB
; in that case just usepagevar(...)
. When called, it will itself buildB
so that it can access the page variable defined onB
. This is usually all you need.
A less common one is to have the function used byA
require aglobal page variable such as, for instance, the list of all tags, which is only completeonce all pages have been built. In that case the function to be used byA
should be marked with@delay hfun_...(...)
so that Franklin knows it has to wait for the full pass to be completed before re-buildingA
now being sure that it will have access to the proper scope.
The pagefoo has a tagfoo
and a page variablevar
; let's show both use cases; seeutils.jl
to see the definition of the relevant functions.
Case 1 (local page variable access,var = 5
on page foo):
var read from foo is 5
Case 2 (wait for full build, there's a tagindex
here and a tagfoo
on page foo):
tags: { index foo }
Inserting highlighted code is very easy in Franklin; just use triple backquotes and the name of the language:
struct Point{T} x::T y::Tend
The highlighting is done via highlight.js, it's important to understand there are two modes:
using the_libs/highlight/highlight.pack.js
, this happens when serving locallyand when publishing a websitewithout pre-rendering,
using the fullhighlight.js
package, this happens when pre-rendering and supports all languages.
The first one only has support for selected languages (by default:css
,C/AL
,C++
,yaml
,bash
,ini
,TOML
,markdown
,html
,xml
,r
,julia
,julia-repl
,plaintext
,python
with the minifiedgithub
theme), you can change this by going to thehighlightjs and making your selection.
The recommendation is to:
check whether the language of your choosing is in the default list above (so that when you serve locally things look nice), if not, go to the highlight.js website, get the files, and replace what's in_libs/highlight/
,
use pre-rendering if you can upon deployment.
Here are some more examples with languages that are in the default list:
# some Ra <- 10while (a > 4) { cat(a, "...", sep = "") a <- a - 1}
# some Pythondef foo(): print("Hello world!") return
// some c++#include <iostream>int main() { std::cout << "Hello World!"; return 0;}
/* some CSS */p { border-style: solid; border-right-color: #ff0000;}
Pagination works with{{paginate list num}}
wherelist
is a page variable with elements that you want to paginate (either a list or tuple), andnum
is the number of elements you want per page. There are many ways you can use this, one possible way is to wrap it in a command if you want to insert this in an actual list which is what is demoed here:
Now observe that
Latexify produces a LaTeX string which should basically be passed to KaTeX. To do that you need to recuperate the output, extract the string and pass it into a maths block.
Here there's a bug with\begin{equation}
in Franklin (issue#584) which is why I'm replacing those with$$
but it should be fixed in the near future so that you wouldn't have to use these two "replace" lines:
using Latexifyempty_ary = Array{Float32, 2}(undef, 2, 2)ls = latexify(empty_ary) # this is an L string
\[\left[\begin{array}{cc}-1.1548718e-33 & -1.1549056e-33 \\4.5884e-41 & 4.5884e-41 \\\end{array}\right]\]At the moment (August 2020) no particular class is added on an output (see#531); you can still do something similar by adding a@@code-output
(or whatever appropriate name) around the command that extracts the output and specify this in your css (seeextras.css
):
x = 7
7
If you find yourself writing that a lot, you should probably define a command like
\newcommand{\prettyshow}[1]{@@code-output \show{#1} @@}
and put it in yourconfig.md
file so that it's globally available.
7
On a single page all code blocks share their environment so
x = 5
then
y = x+2
7
This was asked on Slack with the hope it could mimick theData Files functionality of Jekyll where you would have a file like
name,githubEric Mill,konkloneParker Moore,parkrLiu Fengyun,liufengyun
and you'd want to loop over that and do something with it.
Relevant pieces:
see_assets/members.csv
(content is the code block above)
hfun
seeutils.jl
for the definition ofhfun_members_table
; calling{{members_table _assets/members.csv}}
gives
Name | GitHub alias |
---|---|
Eric Mill | konklone |
Parker Moore | parkr |
Liu Fengyun | liufengyun |
seeconfig.md
for the definition of themembers_from_csv
global page variable.
Writing the following
~~~<ul>{{for (name, alias) in members_from_csv}} <li> <a href="https://github.com/{{alias}}">{{name}}</a> </li>{{end}}</ul>~~~
gives:
Notes:
we use aglobal page variable so that we don't reload the CSV file every single time something changes on the caller page.