Hello everyone! I'm frostime.
This tutorial provides a comprehensive introduction to templating and was originally published on the LianDi forum, the official Chinese forum for SiYuan.
I believe it could be beneficial for non-Chinese speakers, so I've translated it into English and am sharing it here.
I used GPT for the initial translation (cause this article is way too long) and then made manual revisions myself. If there are any parts that aren't fluent, I apologize for any inaccuracies.
Additionally, I don't log into this English-language forum frequently, so I may not be able to respond to your queries in real time. I appreciate your understanding and patience regarding any delays in my responses. Thank you.
Tutorial published in Chinese is here: https://ld246.com/article/1715065433237
What is a Template?
A template is a set of 'variables' defined within a text segment. These 'variables' are replaced with 'actual values' according to certain 'rules' during rendering.
For example, our most common daily note path template:
/daily note/{{now | date "2006/01"}}/{{now | date "2006-01-02"}}
Every day when you use the Alt+5 shortcut to create a daily note, SiYuan will render the above template, replacing the variables defined within {{}}
with actual values (in this case, the date), and finally ouputs a meaningful path string like /daily note/2024/01/01-31
.
SiYuan's templates are very powerful in functionality, but to be honest, as a template for a note-taking software, they are quite cumbersome to use.
This article will not help you fully master the template features of SiYuan, but will try to outline the overall landscape of SiYuan's template features. If you want to delve into and master them - then you need to read the relevant documents carefully and practice in your spare time.
In general, I will divide the template features of SiYuan into the following important sections:
-
Template syntax based on Golang
-
Basic syntax
{{ }}
vs.action{}
-
Commonly used template functions
-
Common flow control
- Conditional control
- Loop control
-
-
Usage in the database
-
Markdown block syntax
{: }
block attribute declaration syntax- Super block typesetting declaration syntax
Before the formal explanation, it is necessary to clarify the relationship between these sections:
-
Golang Template Syntax and Sprig
-
The syntax of SiYuan's template is implemented based on the Golang template engine.
-
The Golang template engine does not provide many useful template functions, so SiYuan also integrate another library called "Sprig" - who provides a large number of rich template functions to enhance the functionality of the template.
-
Basic syntax
- The original Golang template syntax is
{{ xxx }}
- But in SiYuan
{{ }}
is also the declaration syntax for embedding blocks. - To avoid conflicts, developers have customized the
.action{ xxx }
syntax to replace the original{{ xxx }}
- The original Golang template syntax is
-
-
Database Template Columns
- In SiYuan's current database, you can create a 'template column', in which you can use the
.action{}
syntax, this enables you to dynamically adjust the display of its content to match the corresponding row's content
- In SiYuan's current database, you can create a 'template column', in which you can use the
-
Markdown Block Syntax
- Markdown itself does not have a block structure; SiYuan implements a extended markdown syntax based on kramdown.
- Based on this syntax, SiYuan can pre-define custom attributes for blocks and create complex super blocks through special syntax in template files.
- Note: This syntax is unrelated to the Golang template and will not work in places like the database, path template!
Preface: Test-Template Plugin
In addition to this tutorial, I have released a plugin "Test Template Syntax" to facilitate everyone's testing of Golang's template syntax.
You can use this plugin to test the template syntax during reading. For details on how to use the plugin, see the documentation.
SiYuan Golang Template Syntax Cheatsheet
The basic template syntax is not intuitive enough, so I have drawn a cheatsheet to show you, which is more convenient to understand with higher information density.
Quick Summary of Basic Syntax
-
Use
{{ }}
or.action{}
to define templates, which are generally function calls. -
Basic concept of functions
- A function can accept zero or several arguments, perform calculations, and return a specific value.
- The arguments and return values of a function have specific types, you are supposed to pass correct type in accordance with function's definition.
-
Variable assignment
- If you use
{{ now }}
syntax, the template engine will directly render it to a specific value. - But if you use
{{ $t := now }}
syntax, the return value ofnow
will be stored in the variable$t
without rendering the specific internal value. You can then use$t
to refer to this variable.
- If you use
-
Using functions
-
A function is a packaged feature that accepts some parameters, performs specific calculations, and returns specific values.
-
The parameter types passed to the function must match the parameter types accepted by the function.
-
|
syntax: Pipeline syntax, which is a very common syntax in the field of computers, for reference.
-
Quick Introduction to Control Flow Syntax
The usage of Go language template control flow can refer to the official documentation: https://pkg.go.dev/text/template#hdr-Actions
Here are the most commonly used ones:
-
if statement: Selectively render and execute part of the template syntax through conditional judgment.
The basic usage is: if the condition in
condition
is judged to be true, renderT1
, otherwise renderT2
.{{if (condition)}} T1 {{else}} T0 {{end}}
For example:
{{ if eq (Weekday now) 1 }} Work hard {{ else }} Take it easy! {{ end }}
In the condition here:
-
First, calculate
Weekday now
to get today's day of the week. -
Then compare it with 1 through
eq
to see if it's Monday.- If it is, render 'Work hard'.
- If not, render 'Take it easy!'
If you don't understand the
eq
function used here, don't worry, we will explain the functions commonly used in if statements in detail later. -
-
range statement
The range statement is used to iterate through a sequence, and the most common syntax is as follows:
{{ range $v := <slice> }} {{ $v }} {{ end }}
Here's what it means:
- Iterate through an iterable
<slice>
(usually a listList
). - Assign each value to
$v
. - We can access this
$v
variable inside the range block.
A simple case is as follows:
{{ $list := list 1 2 3 4 }} {{ range $v := $list }} - Index: {{ $v }} {{ end }}
Here we first create a list through the
list
function (which will be introduced later), and then iterate through this list inrange
, accessing each value in turn.Rendering result:
- Index: 1
- Index: 2
- Index: 3
- Index: 4
- Iterate through an iterable
Here we have introduced the simplest control flow syntax, and there are more advanced usages to explore on your own.
Introduction of Commonly Used Functions
Functions are an indispensable part of using template features well. In the Cheatsheet above, we briefly introduced the basic concept of functions - a function is a packaged feature that can accept zero or more (depending on the function) parameters and calculate an output.
{{ now }} # now function, outputs the current time object
{{ add 1 2 3 4 }} # add function, Accepts an arbitrary number of integer arguments and outputs their cumulative sum.
SiYuan supports many functions, some are supported by the Sprig package and some are built into SiYuan. And the commonly used functions in SiYuan, I have classified them into these categories:
-
Arithmetic Calculation
- Integer arithmetic (int type)
- Floating point arithmetic (float type)
-
Time Calculation
-
String Operations
-
List Calculation
-
Type Conversion (occasionally)
-
Logical Operations (used when writing conditional blocks)
-
Special functions for SiYuan template fragments
For convenience, I have compiled some of the most commonly used functions here for a brief introduction. During the introduction, follow the following format:
-
Fun
represents a function with no parameters, such asnow
-
Fun <int>, <int>
represents a function with a fixed number of parameters, such assub <int> <int>
;<int>
represents the type of the parameter, commonly used types also include-
int
: Integer typeint64
: 64-bit integer type, which needs to be converted with the type conversion function andint
type
-
float
: Floating point number typefloat64
: 64-bit floating point number type, which needs to be converted with the type conversion function andfloat
type
-
list
: List type -
str
: String type -
bool
: Boolean type (true
orfalse
) -
Time
: Time object type (this type will be explained in more detail later)
-
-
Func [< int >,]
represents a function with an indefinite number of parameters, such asadd
which can be followed by multiple integers
I will also provide some usage examples and rendering effects for reference.
Common Numeric Calculation Functions
-
Sprig Functions
-
Integer int calculation: Full documentation can be found at https://masterminds.github.io/sprig/math.html
add [< int64 >,]
:int64
, accumulationsub <int64> <int64>
:int64
, subtractionmul [< int64 >,]
:int64
, multiplicationdiv <int64> <int64>
:int64
, integer divisionmod <int64> <int64>
:int64
, modulomin [< int64 >,]
andmax [< int64 >,]
:int64
, find the minimum and maximum values
-
Floating point float calculation: Full documentation can be found at https://masterminds.github.io/sprig/mathf.html
-
Note: Sprig's numerical calculations are performed on int64 and float64 types, but many functions only accept int or float types, so type conversion functions often need to be used in conjunction, which will be explained in detail in the following sections.
-
-
Built-in Numeric Functions of SiYuan
-
pow <int>
: Exponential calculation, returns an integer -
powf <float>
: Exponential calculation, returns a floating point number -
log <int>
: Logarithmic calculation, returns an integer -
logf <float>
: Logarithmic calculation, returns a floating point number -
FormatFloat <format str> <n float64>
:string
, refer to
-
Example:
- add
- {{ add 1 2 3 4 }} -> 10
- sub
- {{ sub 4 1 }} -> 3
- mul
- {{ mul 1 2 3 4 }} -> 24
- div
- {{ div 5 2 }} -> 2.5
- mod
- {{ mod 5 2 }} -> 1
- min/max
- {{ min 5 1 }} -> 1
- {{ max 5 1 }} -> 5
- pow
- {{ pow 5 2 }} -> 25
- powf
- {{ powf 2.5 2 }} -> 6.25
- log
- {{ log 5 2 }} -> 2
- logf
- {{ logf 5 2 }} -> approximately 2.32
- FormatFloat
- {{ FormatFloat "#,###.##" 2345.6789 }} -> 2,345.68
The above templates will be rendered as:
add
- 10 -> 10
sub
- 3 -> 3
mul
- 24 -> 24
div
- 2 -> 2.5
mod
- 1 -> 1
min/max
- 1 -> 1
- 5 -> 5
pow
- 25 -> 25
powf
- 6.25 -> 6.25
log
- 2 -> 2
logf
- 2.321928094887362 -> approximately 2.32
FormatFloat
- 2,345.68 -> 2,345.68
Common Time Functions
-
Common Functions of Sprig
-
See all functions: https://masterminds.github.io/sprig/date.html
-
now
:Time
, returns the current time -
date <fmt str> <Time>
:str
, formats the input time object into a string-
fmt
uses the fixed time format2006-01-02 15:04:05
(Ref to: StackOverflow) -
How to remember this 🗑️ garbage 😡 magin number
- First, the year is fixed at 2006
- The following month, day, hour, minute, and second start from 01 and increase sequentially to 05
- So the standard format is:
2006-01-02 03:04:05
- But
03:04:05
is in 12-hour format, so if you want to use 24-hour format, it should be converted to2006-01-02 15:04:05
-
-
toDate <fmt str> <str>
:Time
, converts a string to a time object- Note: The built-in
parseTime
function of SiYuan has a better user experience than this function
- Note: The built-in
-
duration <second: int>
:Duration
, converts the passed seconds (int) to aDuration
object
-
-
Built-in Time Functions of SiYuan
ISOWeek <Time>
:int
, returns the week number of the corresponding timeWeekday <Time>
:int
, returns the day of the week corresponding to the time;Sunday=0, Monday=1, ..., Saturday=6
WeekdayCN <Time>
:str
, returns the day of the week corresponding to the time; it returns Chinese Charaters.WeekdayCN2 <Time>
:str
, returns the day of the week corresponding to the time; it returns Chinese Charaters.parseTime <string>
:Time
, parses the passed time string and returns a time type
-
Time
: The time.Time type of Golang, which has many useful properties that can be accessed-
Full function reference: https://pkg.go.dev/time#Time
- Please look for the API documentation in the format
func (t Time) Func(xxx) XXX
- These functions (properties) can be called in the template through
t.Func
- Please look for the API documentation in the format
-
Year
:int
-
Month
:Month
, this is an enumerated type- Although it appears as an English string, it can be used as a numerical type for calculations, such as
add 1 now.Month
- For more details, refer to: https://pkg.go.dev/time#Month
- Although it appears as an English string, it can be used as a numerical type for calculations, such as
-
Day
:int
-
Hour
:int
-
Minute
:int
-
Second
:int
-
Sub <Time>
: calculates the difference between two times, returnsDuration
-
Compare <Time>
: compares two time objects, returnsint
, -1 or 0 or 1 -
AddDate <year int> <month int> <day int>
: calculates the date after N days (months, years) based on the current time object (parameters can be negative)- Note that this function handles months in a confusing way and it is recommended to use only the year and day parameters
- For more details, refer to: Confusing Go time.AddDate
-
Duration
: The time.Duration type of Golang-
Full documentation: https://pkg.go.dev/time#Duration
- Please look for the API documentation in the format
func (d Duration) Func(xxx) XXX
- These functions can be called in the template through
t.Func
- Please look for the API documentation in the format
-
Hours
:float64
, converts Duration to a value in hours -
Minutes
:float64
, converts Duration to a value in minutes -
Seconds
:float64
, converts Duration to a value in seconds -
String
:str
, converts Duration to a string formatted in hours, for example, "72h3m0.5s"
-
-
Example:
- now
- {{ now }}
- date
- {{ date "2006-01-02 15:04:05" now }}
- toDate
- {{ toDate "2006-01-02" "2020-01-01" }}
- duration
- 1800 seconds: {{ duration 1800 }}
- ISOWeek
- Week number: {{ now | ISOWeek }}
- Weekday
- Today is the day:
- {{ now | Weekday }} {{ now | WeekdayCN }} {{ now | WeekdayCN2 }}
- parseTime
- {{ parseTime "2020-01-01 12:00:00" }}
- Time object
- {{ $t := parseTime "2020-01-01 12:00:00" }}
- {{ $t.Year }}/{{ $t.Month }}/{{ $t.Day }} {{ $t.Hour }}:{{ $t.Minute }}:{{ $t.Second }}
- {{ now.Sub $t }}
- {{ $t.Compare now }}
- {{ $t.AddDate 0 0 7 }}
- Duration object
- {{ $du := now.Sub $t }}
- {{ $du }}
- {{ $du.Hours }}; {{ $du.Minutes }}; {{ $du.Seconds }}
- {{ $du.String }}
The above templates will be rendered as:
now
- 2024-06-10 17:48:42.7798636 +0800 CST m=+4928.647211001
date
- 2024-06-10 17:48:42
toDate
- 2020-01-01 00:00:00 +0800 CST
duration
- 1800 seconds: 0s
ISOWeek
- Week number: 24
Weekday
- Today is the day:
- 1 一 一
parseTime
- 2020-01-01 12:00:00 +0800 CST
Time object
-
- 2020/January/1 12:0:0
- 38933h48m42.7798636s
- -1
- 2020-01-08 12:00:00 +0800 CST
Duration object
-
- 38933h48m42.7798636s
- 38933.81188329544; 2.3360287129977266e+06; 1.401617227798636e+08
- 38933h48m42.7798636s
Date calculation may be the most commonly used function for SiYuan users. If you are a daily note user, open your main diary now and check the template of your diary, you may find it looks like this:
/daily note/{{now | date "2006/01"}}/{{now | date "2006-01-02"}}
Now that we have the theoretical foundation, let's take a look at this diary template case:
-
{{}}
is the standard template syntax of Golang, nothing more to say. -
The
now
function returns aTime
object. -
Through the pipeline operation
|
, the result ofnow
is passed to the following, so it is equivalent to runningdate "2006/01"
.You can replace
{{now | date "2006/01"}}
with{{date "2006/01" now}}
; they are completely equivalent. -
date <fmt str> <Time>
is a fixed usage, and here"2006/01"
is also a fixed usage. -
So in the end, this template will be rendered in the format of
yyyy/mm
, combined with the previous one, it will form a path string like/daily note/<year>/<month>
.
Share a few date calculation template snippets
From experience, in normal md templates, the ones that are written more complex are also related to date calculations. Here are a few shared randomly.
-
May be used in the diary
.action{ $weekday := Weekday now } .action{ $datestr := now | date "2006-01-02" } .action{ $datestr_sy := now | date "20060102" } Today is .action{$datestr} on weekday .action{ $weekday } {: custom-dailynote-.action{$datestr_sy} =".action{$datestr_sy}" }
-
Calculate this week's Sunday, last week's Saturday, etc.
.action{ $weekday := Weekday now } .action{ $begSunday := now.AddDate 0 0 (mul -1 $weekday | int) } .action{ $last6 := $begSunday.AddDate 0 0 -1 } .action{ $this6 := $begSunday.AddDate 0 0 6 } - If Sunday is the start of the week, then this week's Sunday is: .action{ $begSunday | date "2006-01-02" } - Last week's Saturday was: .action{ $last6 | date "2006-01-02" } - This week's Saturday is: .action{ $this6 | date "2006-01-02" }
-
Weekly planning
.action{ $week := now | ISOWeek } .action{ $weekday := now | Weekday } .action{ $monday_delta := sub $weekday 1 } .action{if eq $weekday -1} .action{ $monday_delta := 6 } .action{ end } .action{ $monday := now.AddDate 0 0 (mul -1 $monday_delta | int) } .action{ $sunday := $monday.AddDate 0 0 6 } ## Week .action{$week}: .action{$monday | date "2006-01-02"} to .action{$sunday | date "2006-01-02"}
Common String Operation Functions
String operation functions are not used so much in normal md templates, but they may be widely used in database template columns.
Only a small part is listed here, for full documentation, see https://masterminds.github.io/sprig/strings.html and https://masterminds.github.io/sprig/string_slice.html
trim <str>
:str
, removes leading and trailing whitespace charactersrepeat <int> <str>
:str
, repeats the given string a specified number of times.substr <start int> <end int> <str>
:str
, extracts a substring, with the index starting from 0.trunc <int> <str>
:str
, truncates the given string to a maximum length; the<int>
parameter can be negative, representing a reverse truncation from the end.abbrev <int> <str>
:str
, truncates the string but adds an ellipsis...
at the end.contains <part str> <whole str>
:bool
, checks ifwhole
containspart
.cat [<str>,]
:str
, concatenates several strings with spaces in between.replace <from str> <to str> <src str>
:str
, replaces all occurrences offrom
withto
insrc
.- A series of regular expression functions (please refer to the documentation).
join <ch str> <List[str]>
:str
, joins a list of strings with the characterch
.splitList <ch str> <src str>
:List[str]
, splits the givensrc
string into a list based on the characterch
.
Example:
- trim
- {{ trim " aa " }} -> "aa"
- repeat
- {{ repeat 5 "12" }} -> "1212121212"
- substr
- {{ substr 1 3 "abcedfg" }} -> "bc"
- trunc
- {{ trunc 3 "abcedfg" }} -> "abc"
- {{ trunc -3 "abcedfg" }} -> "efg"
- abbrev
- {{ abbrev 5 "hello world" }} -> "he..."
- contains
- {{ contains "bb" "aabb" }} -> true
- cat
- {{ cat "1" "2" "3" }} -> "1 2 3"
- replace
- {{ replace "aa" "bb" "11aaccaa" }} -> "11bbccbb"
- join
- {{ list "hello" "siyuan" | join "?" }} -> "hello?siyuan"
- splitList
- {{ splitList "$" "foo$bar$baz" }} -> ["foo", "bar", "baz"]
Rendering results:
trim
- aa -> "aa"
repeat
- 1212121212 -> "1212121212"
substr
- bc -> "bc"
trunc
- abc -> "abc"
- dfg -> "efg"
abbrev
- he... -> "he..."
contains
- true -> true
cat
- 1 2 3 -> "1 2 3"
replace
- 11bbccbb -> "11bbccbb"
join
- hello?siyuan -> "hello?siyuan"
splitList
- [foo bar baz] -> ["foo", "bar", "baz"]
List Operation Functions
List functions are often used in conjunction with queryBlocks
(see the following sections).
Full documentation can be found here: https://masterminds.github.io/sprig/lists.html
-
list [<value>,]
:List
, turns the following arguments into a list (of the same type). -
first <List>
: Returns the first item in the list. -
last <List>
: Returns the last item in the list. -
append <List> <value>
:List
, adds an element to the end of the list. -
prepend <List> <value>
:List
, adds an element to the beginning of the list. -
concat [<List>,]
:List
, combines multiple lists into one. -
reverse <List>
:List
, reverses the order of the list. -
has <value> <List>
:bool
, checks if the given item is in the list. -
index <List> <int>
:List
, indexes the content in the list based on the index value. -
slice <List> <beg int> {<end int>}
:List
, slices the given list.slice $myList
returns[1 2 3 4 5]
. It is the same asmyList[:]
.slice $myList 3
returns[4 5]
. It is the same asmyList[3:]
.slice $myList 1 3
returns[2 3]
. It is the same asmyList[1:3]
.slice $myList 0 3
returns[1 2 3]
. It is the same asmyList[:3]
.
-
empty <List>
:bool
, checks if the list is empty. -
len <List>
:int
, gets the length of the list.
- list
- {{ list 1 2 3 4 }} -> [1 2 3 4]
- {{ list "a" "b" "c" }} -> ["a" "b" "c"]
- first
- {{ first (list 1 2 3) }} -> 1
- {{ first (list "a" "b" "c") }} -> "a"
- last
- {{ last (list 1 2 3) }} -> 3
- {{ last (list "a" "b" "c") }} -> "c"
- append
- {{ append (list 1 2 3) 4 }} -> [1 2 3 4]
- {{ append (list "a" "b" "c") "d" }} -> ["a" "b" "c" "d"]
- prepend
- {{ prepend (list 1 2 3) 0 }} -> [0 1 2 3]
- {{ prepend (list "a" "b" "c") "z" }} -> ["z" "a" "b" "c"]
- concat
- {{ concat (list 1 2) (list 3 4) }} -> [1 2 3 4]
- {{ concat (list "a" "b") (list "c" "d") }} -> ["a" "b" "c" "d"]
- reverse
- {{ reverse (list 1 2 3) }} -> [3 2 1]
- {{ reverse (list "a" "b" "c") }} -> ["c" "b" "a"]
- has
- {{ has 2 (list 1 2 3) }} -> true
- {{ has "d" (list "a" "b" "c") }} -> false
- index
- {{ index (list 1 2 3 4) 2 }} -> 3
- {{ index (list "a" "b" "c" "d") 0}} -> "a"
- slice
- {{ slice (list 1 2 3 4 5) 1 3 }} -> [2 3]
- {{ slice (list "a" "b" "c" "d") 2 }} -> ["c" "d"]
- len
- {{ list 1 2 3 4 | len }} -> 4
Rendering results:
list
- [1 2 3 4] -> [1 2 3 4]
- [a b c] -> ["a" "b" "c"]
first
- 1 -> 1
- a -> "a"
last
- 3 -> 3
- c -> "c"
append
- [1 2 3 4] -> [1 2 3 4]
- [a b c d] -> ["a" "b" "c" "d"]
prepend
- [0 1 2 3] -> [0 1 2 3]
- [z a b c] -> ["z" "a" "b" "c"]
concat
- [1 2 3 4] -> [1 2 3 4]
- [a b c d] -> ["a" "b" "c" "d"]
reverse
- [3 2 1] -> [3 2 1]
- [c b a] -> ["c" "b" "a"]
has
- true -> true
- false -> false
index
- 3 -> 3
- a -> "a"
slice
- [2 3] -> [2 3]
- [c d] -> ["c" "d"]
len
- 4 -> 4
Type Conversion Functions
In a note-taking software like SiYuan, dealing with data types might seem odd. However, when you start using template features, sometimes you encounter type compatibility issues.
For example, in this example, the purpose is to calculate the date of the last Sunday (here we assume the week starts on Monday).
- First, we use the
Weekday
function to get the current day of the week, with a value range of 0 to 6. - Then we call the
AddDate
function, subtracting the weekday number to get the date of Sunday.
{{ $weekday := Weekday now }}
{{ now.AddDate 0 0 (mul -1 $weekday) }}
However, when running this template, an error occurs:
Parse template failed: template: :2:27: executing "" at <$weekday>: wrong type for value; expected int; got int64 v3.0.17
By the way, let me teach you how to read this error message.
- Focus on
template: :2:27
, which indicates an error at the second line and twenty-seventh character of the input template. - The specific error is: "wrong type for value; expected int; got int64", meaning the type of the input value is incorrect.
The error occurs because Sprig arithmetic functions like mul
return an int64
type, while the Time.AddDate
function accepts an int
type, causing a type incompatibility issue. ( It might be a bit too technical for the average note-taking software user to grasp, I understand. 😡)
To resolve this, we have to use a type conversion function to change int64
to int
type:
{{ $weekday := Weekday now }}
{{ now.AddDate 0 0 (mul -1 $weekday | int) }}
Type conversion functions are also provided by Sprig, and the documentation can be found here: https://masterminds.github.io/sprig/conversion.html
atoi
: Convert a string to an integer.float64
: Convert to afloat64
.int
: Convert to anint
at the system's width.int64
: Convert to anint64
.toDecimal
: Convert a unix octal to anint64
.toString
: Convert to a string.toStrings
: Convert a list, slice, or array to a list of strings.
Logical Operation Functions
Logical operation functions are mainly used within control statements like if
. For example, in this example, eq
is a logical operation function that compares whether the two input parameters are equal (short for equal).
.action{ if eq 1 2 }
1
.action{ else }
2
.action{ end }
In a more practical example, we may need to write several logical operations. It is recommended to enclose each logical operation in parentheses to ensure the correctness of the operation.
.action{ if or (eq 1 2) (ne 2 3) }
1
.action{ else }
2
.action{ end }
The logical operations can be divided into two main categories: Boolean operations and comparison operations. The parameter type <interface{}>
here means any type.
-
Boolean Operations
and [<interface{}>,]
:interface{}
, if all parameters are true, return the last parameter; otherwise, return the first false parameter.or [<interface{}>,]
:interface{}
, if any parameter is true, return the first true parameter; otherwise, return the last parameter.not <interface{}>
:bool
, perform a logical NOT operation on a single parameter; if the parameter is true, return false, and vice versa.
-
Comparison Operations: Compare two types, note that the parameters here must be of the same type.
eq <interface{}>, <interface{}>
:bool
, determine if the two parameters are equal; if equal, return true, otherwise return false.ne <interface{}>, <interface{}>
:bool
, determine if the two parameters are not equal; if not equal, return true, otherwise return false.lt <interface{}>, <interface{}>
:bool
, compare two parameters, if the first is less than the second, return true, otherwise return false.le <interface{}>, <interface{}>
:bool
, compare two parameters, if the first is less than or equal to the second, return true, otherwise return false.gt <interface{}>, <interface{}>
:bool
, compare two parameters, if the first is greater than the second, return true, otherwise return false.ge <interface{}>, <interface{}>
:bool
, compare two parameters, if the first is greater than or equal to the second, return true, otherwise return false.
In addition to the regular logical operation functions, it is also necessary to introduce a set of list and object judgment functions, which are provided by Default Functions | sprig.
-
empty <interface{}>
:bool
, determine if the given object is empty.- In SiYuan, the most common use is to determine if a list is empty.
-
all [<interface{}>,]
:bool
, determine if each object in the given series is non-empty. -
any [<interface{}>,]
:bool
, determine if there is any non-empty object in the given series.
Here is an example:
- and
- {{ and true true }} -> true
- {{ and true false }} -> false
- {{ and 1 2.3 "str" }} -> "str"
- or
- {{ or false false }} -> false
- {{ or true false }} -> true
- not
- {{ not true }} -> false
- {{ not 0 }} -> true
- eq
- {{ eq 2 3 }} -> false
- {{ eq "a" "a" }} -> true
- ne
- {{ ne 4 3 }} -> true
- {{ ne 2 2 }} -> false
- lt
- {{ lt 4 3 }} -> false
- {{ lt "a" "b" }} -> true
- le
- {{ le 2 2 }} -> true
- {{ le 4 3 }} -> false
- gt
- {{ gt 5 3 }} -> true
- {{ gt 2 2 }} -> false
- ge
- {{ ge 3 3 }} -> true
- {{ ge 1 4 }} -> false
- empty
- {{ list 1 | empty }} -> false
- {{ list | empty }} -> true
and
- true -> true
- false -> false
- str -> "str"
or
- false -> false
- true -> true
not
- false -> false
- true -> true
eq
- false -> false
- true -> true
ne
- true -> true
- false -> false
lt
- false -> false
- true -> true
le
- true -> true
- false -> false
gt
- true -> true
- false -> false
ge
- true -> true
- false -> false
empty
- false -> false
- true -> true
Appendix: Using the Ternary Function to Calculate Inline Conditions
We talked about the most commonly used if conditional statement above:
.action{ if eq 1 2 }
1
.action{ else }
2
.action{ end }
If you are experienced in programming, you should know that many languages support calculating inline conditions. Taking JavaScript as an example:
const x = (1 == 2)? 1 : 2;
In template functions, this inline condition calculation can be accomplished using the ternary
function, with the basic usage being: ternary <arg1> <arg2> <cond bool>
.action{ ternary "Show 1" "Show 2" (eq 1 2) }
Special Functions for SiYuan Template Fragments
There are several special built-in template functions introduced in the documentation, these functions can only be used in md template files.
title
: This variable is used to insert the current document's title. For example, if the template content is# .action{.title}
, it will be inserted as a first-level heading in the current document's content after invocation.id
: This variable is used to insert the current document's ID.name
: This variable is used to insert the current document's name.alias
: This variable is used to insert the current document's alias.
These four are essentially accessing some attributes of the container document being inserted.
We can save the following example in an md template file in the template directory.
This is the title of the current inserted document block: .action{.title}
This is the ID of the current inserted document block: .action{.id}
This is the name of the current inserted document block: .action{.name}
This is the alias of the current inserted document block: .action{.alias}
Then, when you insert this template into a document, you will find that these fields are replaced with the specific attributes of the inserted document block.
This is the title of the current inserted document block: SiYuan Template Function Guide for Newcomers
This is the ID of the current inserted document block: 20231014181953-m94pl9u
This is the name of the current inserted document block:
This is the alias of the current inserted document block:
Note that when using them, a .
must be added in front, i.e., .title
instead of title
. As for why to add .
, those interested can refer to this: Template · Go Language Chinese Documentation.
SQL Query Functions
SiYuan also provides two additional template functions for SQL queries.
-
queryBlocks
: This function is used to query the database and returns a list of blocks, please refer to the following example.- ⭐ This one is more commonly used!
-
querySpans
: This function is used to query the database and returns a list of spans, please refer to the following example.- 🤷♂️ This one is rarely used.
Both of these template functions return a list of blocks.
queryBlocks
queryBlocks
is probably the most commonly used; its function is to query the blocks table.
.action{$blocks := queryBlocks "select * from blocks where 1 limit 1"}
The result is a list, let's take the first element: .action{ $b := first $blocks}
The ID of the block .action{$b.ID}
The path of the block .action{$b.Path}
The complete content of the block:
```json
.action{toPrettyJson $b }
```
We use the toPrettyJson
function (see Default Functions | sprig) to turn the result into a JSON, and you can see that a Block object is returned.
The result is a list, let's take the first element:
The ID of the block 20240525165146-sgzhsub
The path of the block /20231217194046-v8vsa8a.sy
The complete content of the block:{ "ID": "20240525165146-sgzhsub", "ParentID": "20231217194046-v8vsa8a", "RootID": "20231217194046-v8vsa8a", "Hash": "af21b8a", "Box": "20231224140619-bpyuay4", "Path": "/20231217194046-v8vsa8a.sy", "HPath": "/Test", "Name": "", "Alias": "", "Memo": "", "Tag": "", "Content": "Test Sample", "FContent": "", "Markdown": "Test Sample", "Length": 11, "Type": "p", "SubType": "", "IAL": "{: id=\"20240525165146-sgzhsub\" updated=\"20240603203006\"}", "Sort": 10, "Created": "20240525165146", "Updated": "20240603203006" }
Here we used the following syntax:
.action{$blocks := queryBlocks "select * from blocks where 1 limit 1"}
But in some cases, you may need to insert some variables into SQL. In this case, you can use the ?
placeholder to insert variables:
.action{$id := "20240507145154-4g8hqau"}
.action{$blocks := queryBlocks "select * from blocks where id='?'" $id}
querySpans
querySpans
is rarely used and is used to query the spans table.
.action{$spans := querySpans "select * from spans where 1 limit 1"}
```json
.action{toJson $spans }
```
[
{
"ID": "20240420235208-65m9ro6",
"BlockID": "20220324170238-169kwsw",
"RootID": "20220324170216-lp8jviy",
"Box": "20220305173526-4yjl33h",
"Path": "/20220316145830-u0u6srg/20220316145830-x6kvftp/20220316145831-f0lgt06/20220324170216-lp8jviy.sy",
"Content": "随想录",
"Markdown": "((20220320164548-ienx5sl '随想录'))",
"Type": "textmark block-ref",
"IAL": ""
}
]
Case Study: Linking to Yesterday's Diary
With queryBlocks
, we can do some wonderful tricks in the template. This section serves as a conclusion to the template syntax part, introducing an interesting case.
The other day, I saw someone asking in the (Chinese) forum: How to set up a diary template to automatically add a link to yesterday's diary on today's diary page?
The requirement he mentioned can be fully accomplished through queryBlocks
.
To implement this feature, you must know two prerequisite knowledge:
- In SiYuan, diary documents will automatically add the attribute
custom-dailynote-<yyyymmdd>
; for example, the diary of 2024-05-01 will automatically add the document attributecustom-dailynote-20240501
. - In SiYuan, the format for hyperlinks is
siyuan://blocks/<block ID>
.
With these two pieces of knowledge, we have a general idea of how to implement this:
- Obtain yesterday's date.
- Construct the diary document attribute based on yesterday's date.
- Query the document that matches the document attribute.
- If found, remove the document's ID and construct the block link.
First, we need to obtain the yyyymmdd
attribute, for which I have already provided the method in the "Common Time Functions" section:
.action{ $datestr_sy := now | date "20060102" }
{: custom-dailynote-.action{$datestr_sy}=".action{$datestr_sy}" }
Here, we need to replace now
with yesterday, by using the following code; which uses the list
and join
functions, that I have also introduced earlier.
.action{ $datestr_sy := now.AddDate 0 0 -1 | date "20060102" }
.action{ $attr := list "custom-dailynote-" $datestr_sy | join "" }
Next, we need to call queryBlock
in the template to query yesterday's diary:
.action{ $datestr_sy := now.AddDate 0 0 -1 | date "20060102" }
.action{ $attr := list "custom-dailynote-" $datestr_sy | join "" }
.action{ $docs := queryBlocks "select * from blocks where type='d' and ial like '%custom-dailynote-20240506%' limit 1" }
After obtaining $docs
, we need to do the following:
- First, check if the list is empty (using the
empty
function), as it is possible that no diary was written yesterday. - If not empty, use the
first
function to retrieve the first document element. - Obtain the document's ID through
.ID
and construct the markdown link.
The final complete template content is as follows:
.action{ $datestr_sy := now.AddDate 0 0 -1 | date "20060102" }
.action{ $attr := list "custom-dailynote-" $datestr_sy | join "" }
.action{ $docs := queryBlocks "select * from blocks where type='d' and ial like '%?%' limit 1" $attr }
.action{ if not (empty $docs) }
.action{ $doc := first $docs }
[.action{$doc.Content}](siyuan://blocks/.action{ $doc.ID })
.action{ end }
Database Template Columns
In SiYuan's database, there is a type of column called a "template column". Through template columns, we can unleash more powerful functions of the database.
Let's see an example of a database effect:
In this database, "Rating" is a template column, which will automatically adjust the display to a different number of stars based on the score value on the left.
Here's how it's done:
-
First, create a new rating column and set it as a template column.
-
Edit the template to:
.action{ $scale := (div .Score 20) } .action{ repeat (int $scale) "⭐" }
It looks very simple! You should remember that we have discussed the three functions used here before:
div
: Performs division and assigns the result to the$scale
variable.- Since the result of
div
is of typeint64
, theint
function is used to convert it to an integer type. - By calling the
repeat
function, the ⭐ symbol is displayed repeatedly according to the value of the$scale
variable.
Of course, the above is not the key point. The key point here is - what is .Score
, and why can we access it?
. Object
Perhaps you still remember that the .
symbol has appeared many times before. For example, now.Year
mentioned earlier means:
- The
Time
object returned by thenow
function. - Use
.
to access theYear
property of theTime
object.
Similarly, here the .
symbol also represents accessing a property. However, since there was no specified object being indexed before, the default data object passed into the template is used here - in SiYuan's database, it corresponds to the row itself where the template column is located.
For example, if a database has columns such as Date
, Score
, Note
, we can access them in the template column through .Date
, .Score
, .Notes
.
Are there any other objects that can be accessed besides the columns themselves?
Of course! If you want to know more detailed accessible fields, here is a simple trick:
- Create a new template column in the database.
- Set the template to
.action{toPrettyJson .}
. - Set the template column to "Wrap column".
Then you can see all the accessible fields in the database in the template column, such as accessing the creation time of the row through .created
.
However, note that not all properties can be accessed through .
, for example, if we try to access an object named .custom-b
, we will get an error - because custom-b does not conform to the naming convention of properties.
For such cases, you can access the property by using index . "name"
, for example, in this case, we fill in .action{ index . "custom-b" }
to access the value of the custom-b
property.
Two Types of Primary Keys
The primary keys in SiYuan's database can be divided into two types:
- Ordinary text primary keys.
- Primary keys bound to a block.
For the convenience of testing, let's create a test block now and fill in the name, properties, notes, and custom properties:
Here are the differences in the internal properties of the two rows:
- Ordinary text primary keys only have attributes such as id, update, created, and primary key.
- Primary keys bound to a block also have additional attributes such as database-related attributes like av-names, custom-avs, block's built-in attributes like naming, alias, etc., and custom properties like "custom-b".
- The ID of a primary key bound to a block points to the bound block, while the ID of an ordinary text primary key is its own ID.
- Note: The ID of an ordinary text primary key does not point to a block, so if you try to query the block corresponding to this ID using SQL, you will not get any meaningful results!
Combined with queryBlocks
For databases where the primary key is bound to a block, since the ID attribute and the corresponding block have opened up communication channels, it is possible to combine template columns with the queryBlocks method to play some more fancy features.
Here is a simple example: automatically obtain the title of the document bound to the block.
-
First, use queryBlocks to execute an SQL query to obtain the corresponding document block. The SQL statement is designed as follows:
select * from blocks where id in (select root_id from blocks where id='?id')
-
Use an if statement to filter out the case where there is no document block.
- Use the
first
function to retrieve the first document block. - Then obtain its Content field.
- Use the
-
For the case where there is no document block, simply output "None".
.action{ $blocks := queryBlocks "select * from blocks where id in (select root_id from blocks where id='?')" .id}
.action{ if not (empty $blocks)}
.action{ (first $blocks).Content }
.action{ else }
None
.action{ end }
The effect is as follows:
Combined with HTML Tags
Template columns also support the use of custom HTML elements. Let's do another test:
-
Create a new numeric column "Proportion" with a value range from 0 to 100.
-
Paste the following template into a template column. It does something simple: reads the value of "Proportion" and replaces it in the div element.
.action{ $portion := index . "Proportion"} <div style=" background: conic-gradient(#4CAF50 0%, #4CAF50 .action{$portion}%, #ddd .action{$portion}%); border-radius: 50%; width: 40px; height: 40px; position: relative;"> <span style=" position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 12px;"> .action{$portion}% </span> </div>
After completing the above steps, the database will render the template column's style according to the value in the "Proportion" column, thus displaying different effects:
Play with JS (Potentially Risky, Use with Caution)
Advanced tricks, regular users use with caution.
Custom HTML + executable JavaScript carries security risks; this example is to show you the extent of what can be done with the database.
If you are not 100% sure of what you are doing, do not play around with this.
Let's look at an amazing example:
As we mentioned earlier, the database's text can directly accommodate HTML tags - so since HTML tags can be inserted, it's reasonable to execute some JavaScript code, right?
Now let's explain the implementation of the effect shown above:
-
First, bind the database's primary key to a block - to ensure that using
.id
can access the block's ID. -
Create a new CSS text column for filling in specific CSS property tables.
-
Create a template column and fill it with the following content.
<button style="margin: 3px;" class="b3-button" onclick='event.stopImmediatePropagation(); runJs.api.setBlockAttrs(".action{.id}", {style: ".action{index . "css"}"});'> Add Style </button>
Let's analyze this template:
-
<button style="margin: 3px;" class="b3-button" />
constructs a button element,b3-button
is a built-in class name in SiYuan. -
The key is the
onclick
element, where we directly fill in the code we want to execute:-
event.stopImmediatePropagation();
- This line of code is to prevent the default data table behavior of SiYuan. If it's removed, SiYuan will pop up an edit box instead of triggering the button click event.
-
runJs.api.setBlockAttrs
-
This is actually an interface exposed externally after installing the
RunJs
plugin.setBlockAttrs
is a kernel API of SiYuan, used to set block attributes for blocks.- Reference: https://github.com/siyuan-note/siyuan/blob/master/API_zh_CN.md#设置块属性
-
You don't necessarily have to install the
RunJs
plugin; you can also add your own code functionalities in a JS code snippet.
-
-
setBlockAttrs
accepts two parameters: the block's ID and the specific block attributes.-
Block ID: obtained through the template function
.action{.id}
. -
Block attributes: since we want to set inline style attributes, the payload body of the API is filled with
{style: "<style code>"}
.- Use
.action{index . "css"}
to obtain the content of the CSS column and fill it into the payload body.
- Use
-
-
SiYuan Markdown Block Syntax
Strictly speaking, SiYuan's Markdown block syntax has nothing to do with template syntax. However, since Markdown block syntax is often used in md template files, it is necessary to introduce it together here.
We all know that SiYuan is based on blocks, and SiYuan is somewhat compatible with Markdown syntax format (for example, in template files) - so what should we do? Markdown's original syntax is a cripple, with no concept of blocks, let alone block attributes - how can this be resolved?
For the most common cases, a Markdown built-in syntax element, such as headings, lists, quotes, etc., will also be converted into a separate block.
For more complex cases, SiYuan has implemented a Markdown dialect based on kramdown. Through this dialect, we can define blocks and their internal properties in a normal Markdown plain text file.
- SiYuan's Markdown extended attribute syntax can not only be used in the md files of templates but also directly in the editor.
- When reading the following examples, you can copy the given md template sample and directly paste it into SiYuan's editor, and SiYuan will still be able to correctly recognize the defined block properties.
Block Attribute Declaration Syntax
SiYuan can declare a block with specific attributes through the inline style sheet (referred to as IAL) in the form of {: }
. The basic syntax format of the inline style sheet is as follows, note that there must be spaces on both sides of the parentheses.
{: <key1>="value1" <key2>="value2" }
For example, you can fill in the following text into the Test.md file under the templates
directory:
This is a simple paragraph, but I will declare a 'memo' inline style and a 'custom-a' custom attribute.
{: memo="This is a block" custom-a="a" }
After applying this template to the SiYuan main text, it will create a block filled with memo and custom-a attributes.
For example, after using the Callout feature (provided by the Savor theme and the callout plugin), if you want to quickly insert a callout through a template - by observation, the callout style mainly depends on adding a custom-b
attribute to a quote block, so you can first write the markdown syntax of the quote block in Test.md, and then add the {: }
attribute table immediately below:
> Meta
> - 🚩 Document Type: Theme Document | Event Record
> - ⭐ Related Topics:
> - 📝 Basic Introduction:
{: custom-b="info" }
Note: The available block attributes include both built-in block attributes and custom attributes.
- The available built-in styles include: id, name, alias, bookmark, memo, style.
- When setting custom styles, don't forget to add the
custom-
prefix.
Special Case: ID Attribute
We can define the ID attribute in the IAL table, like this:
This is a block, can you guess what its ID will be?
{: id="20231004221035-p9r4sh7"}
However, the ID attribute defined in the IAL table will not actually be applied to the block in the editor—that is to say, even if the above template is used, the new block's ID will not be "20231004221035-p9r4sh7", but rather a new block ID generated by SiYuan itself.
The only role of the ID attribute in the IAL is to serve as a placeholder in the IAL to declare a block—because an empty IAL table is invalid.
It may sound redundant—but indeed, in most cases, this trick is of no use.
However, when dealing with complex container blocks, we may have to use this trick to differentiate between the container block and the internal content blocks. Here is an example:
Suppose: We need to write an empty list block and add a
style="border: 1px solid blue;"
attribute to this list block (i.e., add an inline CSS style for the outer border). What should we do?
You might write the following template style:
-
{: style="background: red;" }
However, the above template is invalid.
The reason is that the list block in SiYuan is a very complex nested object. Even a simplest single-item list actually contains three parts:
- The innermost paragraph block
- The list item block outside the paragraph block
- The list block that the list item belongs to
In the above template, SiYuan cannot recognize which block {: style="background: red;" }
is applied to.
In this case, we must clarify the three-layer block structure through a style sheet similar to {: id="20231004221035-p9r4sh7"}
, and it should be written like this:
- {: id="202001010000-abcdefg"}
{: id="202001010000-abcdefg"}
{: style="border: 1px solid blue;" }
Additionally, although the value of id
is practically useless, it must adhere to the ID format specification when filled in, otherwise, it will not be recognized:
yyyymmddHHMMSS-<7 characters or numbers>
Examples like "202001010000-abcdefg", "202001010000-1234567" are all acceptable, while "123" is not.
func NewNodeID() string {
now := time.Now()
return now.Format("20060102150405") + "-" + randStr(7)
}
Super Block Layout
SiYuan supports complex layouts through super blocks, which can also be defined using extended markdown syntax:
-
Multi-line super blocks (vertical layout)
{{{row <internal content> }}}
-
Multi-column super blocks (horizontal layout)
{{{col <internal content> }}}
When writing specifically, just replace <internal content>
with the normal block definition. Then, during rendering, the internal content will be included in a new super block according to the vertical or horizontal layout.
Here is a slightly more complex example. If you understand this example, you will basically master the layout of super block layout.
{{{col
{{{row
### TODO: High Priority
- [x]
}}}
{: style="border: 2px solid blue;"}
{{{row
### TODO: Low Priority
- [x]
}}}
{: style="border: 2px solid blue;"}
}}}
{: style="border: 2px solid red; padding: 10px;"}
After inserting into the editor, the style is as follows:
-
The outermost is a horizontally arranged super block with two vertically arranged super blocks inside.
-
The two columns inside have their own vertically arranged super blocks, including
- A heading block at the top
- A task block at the bottom
Related Reference Materials
Welcome to here!
Here we can learn from each other how to use SiYuan, give feedback and suggestions, and build SiYuan together.
Signup About