SiYuan Template Function Guide for Newcomers: Template Syntax + Functions + Markdown Block Syntax

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:

  1. 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 }}
  2. 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
  3. 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.

image.png

You can use this plugin to test the template syntax during reading. For details on how to use the plugin, see the documentation.

image.png

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.

image.png

image.png

image.png

image.png

Quick Summary of Basic Syntax

  1. Use {{ }} or .action{} to define templates, which are generally function calls.

  2. Basic concept of functions

    1. A function can accept zero or several arguments, perform calculations, and return a specific value.
    2. The arguments and return values of a function have specific types, you are supposed to pass correct type in accordance with function's definition.
  3. 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 of now will be stored in the variable $t without rendering the specific internal value. You can then use $t to refer to this variable.
  4. 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.

image.png

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:

  1. 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, render T1, otherwise render T2.

    {{if (condition)}} T1 {{else}} T0 {{end}}
    

    For example:

    {{ if eq (Weekday now) 1 }}
    Work hard
    {{ else }}
    Take it easy!
    {{ end }}
    

    In the condition here:

    1. First, calculate Weekday now to get today's day of the week.

    2. Then compare it with 1 through eq to see if it's Monday.

      1. If it is, render 'Work hard'.
      2. 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.

  2. 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 list List).
    • 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 in range, accessing each value in turn.

    Rendering result:

    • Index: 1
    • Index: 2
    • Index: 3
    • Index: 4

Here we have introduced the simplest control flow syntax, and there are more advanced usages to explore on your own.

image.png

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:

  1. Arithmetic Calculation

    • Integer arithmetic (int type)
    • Floating point arithmetic (float type)
  2. Time Calculation

  3. String Operations

  4. List Calculation

  5. Type Conversion (occasionally)

  6. Logical Operations (used when writing conditional blocks)

  7. 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 as now

  • Fun <int>, <int> represents a function with a fixed number of parameters, such as sub <int> <int>; <int> represents the type of the parameter, commonly used types also include

    • int: Integer type

      • int64: 64-bit integer type, which needs to be converted with the type conversion function and int type
    • float: Floating point number type

      • float64: 64-bit floating point number type, which needs to be converted with the type conversion function and float type
    • list: List type

    • str: String type

    • bool: Boolean type (true or false)

    • 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 as add 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, accumulation
      • sub <int64> <int64>: int64, subtraction
      • mul [< int64 >,]: int64, multiplication
      • div <int64> <int64>: int64, integer division
      • mod <int64> <int64>: int64, modulo
      • min [< int64 >,] and max [< 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

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 format 2006-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 to 2006-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
    • duration <second: int>: Duration, converts the passed seconds (int) to a Duration object

  • Built-in Time Functions of SiYuan

    • ISOWeek <Time>: int, returns the week number of the corresponding time
    • Weekday <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
    • 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
    • Day: int

    • Hour: int

    • Minute: int

    • Second: int

    • Sub <Time>: calculates the difference between two times, returns Duration

    • Compare <Time>: compares two time objects, returns int, -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
      • 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:

image.png

/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:

  1. {{}} is the standard template syntax of Golang, nothing more to say.

  2. The now function returns a Time object.

  3. Through the pipeline operation |, the result of now is passed to the following, so it is equivalent to running date "2006/01".

    You can replace {{now | date "2006/01"}} with {{date "2006/01" now}}; they are completely equivalent.

  4. date <fmt str> <Time> is a fixed usage, and here "2006/01" is also a fixed usage.

  5. 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"}
    

image.png

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 characters
  • repeat <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 if whole contains part.
  • cat [<str>,]: str, concatenates several strings with spaces in between.
  • replace <from str> <to str> <src str>: str, replaces all occurrences of from with to in src.
  • A series of regular expression functions (please refer to the documentation).
  • join <ch str> <List[str]>: str, joins a list of strings with the character ch.
  • splitList <ch str> <src str>: List[str], splits the given src string into a list based on the character ch.

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 as myList[:].
    • slice $myList 3 returns [4 5]. It is the same as myList[3:].
    • slice $myList 1 3 returns [2 3]. It is the same as myList[1:3].
    • slice $myList 0 3 returns [1 2 3]. It is the same as myList[: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 a float64.
  • int: Convert to an int at the system's width.
  • int64: Convert to an int64.
  • toDecimal: Convert a unix octal to an int64.
  • 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:

  1. 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 attribute custom-dailynote-20240501.
  2. 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:

  1. Obtain yesterday's date.
  2. Construct the diary document attribute based on yesterday's date.
  3. Query the document that matches the document attribute.
  4. 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:

  1. First, check if the list is empty (using the empty function), as it is possible that no diary was written yesterday.
  2. If not empty, use the first function to retrieve the first document element.
  3. 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:

image.png

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:

  1. First, create a new rating column and set it as a template column.

  2. 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 type int64, the int 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 the now function.
  • Use . to access the Year property of the Time 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".

image.png

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.

image.png

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.

image.png

Two Types of Primary Keys

The primary keys in SiYuan's database can be divided into two types:

  1. Ordinary text primary keys.
  2. 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:

image.png

Here are the differences in the internal properties of the two rows:

  1. Ordinary text primary keys only have attributes such as id, update, created, and primary key.
  2. 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".
  3. 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.
  4. 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!

image.png

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.

  1. 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')
    
  2. Use an if statement to filter out the case where there is no document block.

    1. Use the first function to retrieve the first document block.
    2. Then obtain its Content field.
  3. 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:

image.png

Combined with HTML Tags

Template columns also support the use of custom HTML elements. Let's do another test:

  1. Create a new numeric column "Proportion" with a value range from 0 to 100.

  2. 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:

image.png

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:

recording202406101659539py7qgf.gif

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:

  1. First, bind the database's primary key to a block - to ensure that using .id can access the block's ID.

  2. Create a new CSS text column for filling in specific CSS property tables.

  3. 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:

    1. 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.
    2. runJs.api.setBlockAttrs

    3. setBlockAttrs accepts two parameters: the block's ID and the specific block attributes.

      1. Block ID: obtained through the template function .action{.id}.

      2. 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.

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.

image.png

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" }

image.png

Note: The available block attributes include both built-in block attributes and custom attributes.

  1. The available built-in styles include: id, name, alias, bookmark, memo, style.
  2. 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;" }

image.png

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

image.png

    Welcome to here!

    Here we can learn from each other how to use SiYuan, give feedback and suggestions, and build SiYuan together.

    Signup About
    Please input reply content ...
    • Knuex
      PRO
      This reply was hided due to permission, only author and article author can visit
    • MiscReply
    • alvorithm
      VIP Warrior

      This is great 👍 @frostime , I was thinking of posting the Google Translate version I had been using myself.

    • Bob

      re the Block Attribute Declaration Syntax, does anyone know:

      1. is it possible to also add database attributes with this syntax? simply using the field name does not seem to work
      2. Is it possible to do this after having written some content? currently the only way this seems to work is to write it, press Enter, then go back to the created block and press enter. This is pretty impractical
    • zetashift
      VIP Warrior

      This is amazing, thank you so much!

    • Visit all replies