Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,7 @@ export abstract class Constants {
// common: "bash", "c", "csharp", "cpp", "css", "diff", "go", "xml", "json", "java", "javascript", "kotlin", "less", "lua", "makefile", "markdown", "objectivec", "php", "php-template", "perl", "plaintext", "python", "python-repl", "r", "ruby", "rust", "scss", "sql", "shell", "swift", "ini", "typescript", "vbnet", "yaml", "properties", "1c", "armasm", "avrasm", "actionscript", "ada", "angelscript", "accesslog", "apache", "applescript", "arcade", "arduino", "asciidoc", "aspectj", "abnf", "autohotkey", "autoit", "awk", "basic", "bnf", "dos", "brainfuck", "cal", "cmake", "csp", "cos", "capnproto", "ceylon", "clean", "clojure", "clojure-repl", "coffeescript", "coq", "crystal", "d", "dns", "dart", "delphi", "dts", "django", "dockerfile", "dust", "erb", "elixir", "elm", "erlang", "erlang-repl", "excel", "ebnf", "fsharp", "fix", "flix", "fortran", "gcode", "gams", "gauss", "glsl", "gml", "gherkin", "golo", "gradle", "groovy", "haml", "hsp", "http", "handlebars", "haskell", "haxe", "hy", "irpf90", "isbl", "inform7", "x86asm", "jboss-cli", "julia", "julia-repl", "ldif", "llvm", "lsl", "latex", "lasso", "leaf", "lisp", "livecodeserver", "livescript", "mel", "mipsasm", "matlab", "maxima", "mercury", "axapta", "routeros", "mizar", "mojolicious", "monkey", "moonscript", "n1ql", "nsis", "nestedtext", "nginx", "nim", "nix", "node-repl", "ocaml", "openscad", "ruleslanguage", "oxygene", "pf", "parser3", "pony", "pgsql", "powershell", "processing", "prolog", "protobuf", "puppet", "purebasic", "profile", "q", "qml", "reasonml", "rib", "rsl", "roboconf", "sas", "sml", "sqf", "step21", "scala", "scheme", "scilab", "smali", "smalltalk", "stan", "stata", "stylus", "subunit", "tp", "taggerscript", "tcl", "tap", "thrift", "twig", "vbscript", "vbscript-html", "vhdl", "vala", "verilog", "vim", "wasm", "mathematica", "wren", "xl", "xquery", "zephir", "crmsh", "dsconfig", "graphql",
// third: "yul", "solidity", "abap", "hlsl", "gdscript"
public static readonly ALIAS_CODE_LANGUAGES: string[] = [
"js", "ts", "html", "toml", "c#", "bat",
"js", "ts", "html", "toml", "c#", "bat", "siyuan-template"
];
public static readonly SIYUAN_RENDER_CODE_LANGUAGES: string[] = [
"abc", "plantuml", "mermaid", "flowchart", "echarts", "mindmap", "graphviz", "math"
Expand Down
233 changes: 233 additions & 0 deletions app/stage/protyle/js/highlight.js/third-languages.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,236 @@ hljs.registerLanguage("hlsl",(()=>{"use strict";const e={className:"number",

// https://github.com/highlightjs/highlightjs-gdscript
hljs.registerLanguage("gdscript",function(){"use strict";var e=e||{};function r(e){return{aliases:["godot","gdscript"],keywords:{keyword:"and in not or self void as assert breakpoint class class_name extends is func setget signal tool yield const enum export onready static var break continue if elif else for pass return match while remote sync master puppet remotesync mastersync puppetsync",built_in:"Color8 ColorN abs acos asin atan atan2 bytes2var cartesian2polar ceil char clamp convert cos cosh db2linear decimals dectime deg2rad dict2inst ease exp floor fmod fposmod funcref get_stack hash inst2dict instance_from_id inverse_lerp is_equal_approx is_inf is_instance_valid is_nan is_zero_approx len lerp lerp_angle linear2db load log max min move_toward nearest_po2 ord parse_json polar2cartesian posmod pow preload print_stack push_error push_warning rad2deg rand_range rand_seed randf randi randomize range_lerp round seed sign sin sinh smoothstep sqrt step_decimals stepify str str2var tan tanh to_json type_exists typeof validate_json var2bytes var2str weakref wrapf wrapi bool int float String NodePath Vector2 Rect2 Transform2D Vector3 Rect3 Plane Quat Basis Transform Color RID Object NodePath Dictionary Array PoolByteArray PoolIntArray PoolRealArray PoolStringArray PoolVector2Array PoolVector3Array PoolColorArray",literal:"true false null"},contains:[e.NUMBER_MODE,e.HASH_COMMENT_MODE,{className:"comment",begin:/"""/,end:/"""/},e.QUOTE_STRING_MODE,{variants:[{className:"function",beginKeywords:"func"},{className:"class",beginKeywords:"class"}],end:/:/,contains:[e.UNDERSCORE_TITLE_MODE]}]}}return e.exports=function(e){e.registerLanguage("gdscript",r)},e.exports.definer=r,e.exports.definer||e.exports}());



// https://github.com/siyuan-note/siyuan/pull/15345
hljs.registerLanguage('template', function (hljs) {

const markdownRules = hljs.getLanguage('markdown') || { contains: [] };
const goRules = hljs.getLanguage('go') || { contains: [] };

// 内置函数规则
const BUILT_IN_FUNCTIONS = {
className: 'built_in',
begin: /\b(queryBlocks|querySpans|querySQL|getHPathByID|getBlock|statBlock|runeCount|wordCount|toPrettyJson|parseTime|Weekday|WeekdayCN|WeekdayCN2|ISOWeek|pow|powf|log|logf|FormatFloat|now|date|toDate|duration|AddDate|Sub|sub|add|mul|mod|div|min|max|Compare|Year|Month|Day|Hour|Minute|Second|Hours|Minutes|Seconds|String|trim|repeat|substr|trunc|abbrev|contains|cat|replace|join|splitList|list|first|last|append|prepend|concat|reverse|has|index|slice|len|atoi|float64|int|int64|toDecimal|toString|toStrings|dict|get)\b/,
Comment thread
88250 marked this conversation as resolved.
relevance: 10
};

// 变量规则 - 以$开头,不包括.号
const VARIABLE_RULE = {
className: 'variable',
begin: /\$[a-zA-Z_][a-zA-Z0-9_]*/,
relevance: 10
};

// 关键字和操作符
const KEYWORDS_OPERATORS = {
className: 'keyword',
begin: /\b(if|else|end|range|not|and|or|eq|ne|lt|le|gt|ge|empty|all|any|ternary|true|false)\b/,
relevance: 10
};

// SQL 内容的通用规则
const SQL_CONTENT_RULES = [
// SQL 关键字 - 大小写不敏感
{
className: 'keyword',
begin: /\b(SELECT|FROM|WHERE|AND|OR|ORDER|BY|GROUP|HAVING|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|INDEX|TABLE|DATABASE|LIKE|IN|NOT|IS|NULL|DISTINCT|LIMIT|OFFSET|JOIN|INNER|LEFT|RIGHT|OUTER|ON|AS|COUNT|SUM|AVG|MIN|MAX|CASE|WHEN|THEN|ELSE|END)\b/i,
relevance: 10
},
// SQL 函数 - 大小写不敏感
{
className: 'built_in',
begin: /\b(COUNT|SUM|AVG|MIN|MAX|LENGTH|UPPER|LOWER|SUBSTR|TRIM|REPLACE|CONCAT)\b/i,
relevance: 8
},
// SQL 中的字符串 (单引号)
{
className: 'string',
begin: /'/,
end: /'/,
contains: [{ begin: /''/ }]
},
// 数字
{
className: 'number',
begin: /\b\d+(\.\d+)?\b/,
relevance: 0
},
// 思源笔记特定的字段
{
className: 'attr',
begin: /\b(id|parent_id|root_id|hash|box|path|hpath|name|alias|memo|tag|content|fcontent|markdown|length|type|subtype|ial|sort|created|updated)\b/,
relevance: 8
},
// 问号占位符
{
className: 'symbol',
begin: /\?/,
relevance: 5
},
// .action{} 嵌套在 SQL 中
{
begin: /(\.action\{)/,
end: /(\})/,
beginScope: 'selector-pseudo',
endScope: 'selector-pseudo',
contains: [VARIABLE_RULE, BUILT_IN_FUNCTIONS, KEYWORDS_OPERATORS],
relevance: 10
},
];

// 直接的 SQL 语句规则 - 用于双大括号中的完整SQL
const DIRECT_SQL_STATEMENT = {
// 匹配以 SELECT 开头的语句
begin: /\b(SELECT)\b/i,
end: /(?=\}\}|$)/,
returnBegin: true,
className: 'string',
contains: SQL_CONTENT_RULES,
relevance: 20
};

// SQL 字符串规则 - 专门用于包含 SQL 语句的字符串
const SQL_STRING = {
className: 'string',
begin: /"/,
end: /"/,
contains: SQL_CONTENT_RULES,
relevance: 8
};

// 检测 SQL 查询函数后的字符串
const SQL_FUNCTION_CALL = {
begin: /\b(queryBlocks|querySpans|querySQL)\s+/,
end: /(?=\s+\$|\s*\)|\s*$)/,
returnBegin: true,
contains: [
// 函数名
{
className: 'built_in',
begin: /\b(queryBlocks|querySpans|querySQL)\b/,
relevance: 10
},
// 跟在函数名后的 SQL 字符串
SQL_STRING
],
relevance: 15
};

// 普通字符串规则
const STRING_RULE = {
className: 'string',
variants: [
{ begin: /"/, end: /"/ },
{ begin: /'/, end: /'/ }
],
relevance: 5
};

// 注释规则
const COMMENT_RULE = {
className: 'comment',
variants: [
{ begin: /\/\*/, end: /\*\// },
{ begin: /\/\//, end: /$/ }
],
relevance: 10
};

// .action 块规则
const ACTION_BLOCK = {
begin: /(\.action\{)/,
end: /(\})/,
beginScope: 'selector-pseudo',
endScope: 'selector-pseudo',
contains: [
COMMENT_RULE,
SQL_FUNCTION_CALL, // SQL 函数调用(优先级较高)
VARIABLE_RULE,
BUILT_IN_FUNCTIONS,
KEYWORDS_OPERATORS,
STRING_RULE, // 普通字符串(优先级较低)
// 数字
{
className: 'number',
begin: /\b\d+(\.\d+)?\b/,
relevance: 0
},
// 操作符
{
className: 'operator',
begin: /(:=|==|!=|<=|>=|<|>|=)/,
relevance: 5
},
...goRules.contains,
],
relevance: 10
};

// 双大括号块规则 - 增强 SQL 支持
const CURLY_BLOCK = {
begin: /(\{\{)/,
end: /(\}\})/,
beginScope: 'selector-pseudo',
endScope: 'selector-pseudo',
contains: [
COMMENT_RULE,
DIRECT_SQL_STATEMENT, // 直接的SQL语句(最高优先级)
VARIABLE_RULE,
BUILT_IN_FUNCTIONS,
KEYWORDS_OPERATORS,
STRING_RULE,
...goRules.contains,
],
relevance: 10
};

// 思源块属性规则
const BLOCK_ATTR_RULE = {
className: 'comment',
begin: /\{:/,
end: /\}(?=\s*$)/,
contains: [
{
className: 'string',
begin: /\"/,
end: /\"/,
contains: [
{
begin: /(\.action\{)/,
end: /(\})/,
beginScope: 'selector-pseudo',
endScope: 'selector-pseudo',
contains: [
VARIABLE_RULE,
BUILT_IN_FUNCTIONS,
KEYWORDS_OPERATORS,
STRING_RULE,
],
relevance: 10
},
]
},
{
className: 'title',
begin: /\b\w+=/,
}
],
relevance: 10
};

return {
name: 'template',
aliases: ['siyuan-template', 'template'],
case_insensitive: true,
contains: [
ACTION_BLOCK,
CURLY_BLOCK,
BLOCK_ATTR_RULE,
...markdownRules.contains,
]
};

});
22 changes: 22 additions & 0 deletions kernel/model/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,28 @@ func DocSaveAsTemplate(id, name string, overwrite bool) (code int, err error) {
return ast.WalkContinue
})

var unlinks []*ast.Node
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
if !entering {
return ast.WalkContinue
}

if ast.NodeCodeBlockFenceInfoMarker == n.Type {
if lang := string(n.CodeBlockInfo); "siyuan-template" == lang || "template" == lang {
// 将模板代码转换为段落文本 https://github.com/siyuan-note/siyuan/pull/15345
unlinks = append(unlinks, n.Parent)
p := treenode.NewParagraph(n.Parent.ID)
// 代码块内可能会有多个空行,但是这里不需要分块处理,后面渲染一个文本节点即可
p.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: n.Next.Tokens})
n.Parent.InsertBefore(p)
}
}
return ast.WalkContinue
})
for _, n := range unlinks {
n.Unlink()
}

luteEngine := NewLute()
formatRenderer := render.NewFormatRenderer(tree, luteEngine.RenderOptions)
md := formatRenderer.Render()
Expand Down