githaven/templates/repo/wiki/view.tmpl
wxiaoguang 5b89670a31
Use a general Eval function for expressions in templates. (#23927)
One of the proposals in #23328

This PR introduces a simple expression calculator
(templates/eval/eval.go), it can do basic expression calculations.

Many untested template helper functions like `Mul` `Add` can be replaced
by this new approach.

Then these `Add` / `Mul` / `percentage` / `Subtract` / `DiffStatsWidth`
could all use this `Eval`.

And it provides enhancements for Golang templates, and improves
readability.

Some examples:

----

* Before: `{{Add (Mul $glyph.Row 12) 12}}`
* After: `{{Eval $glyph.Row "*" 12 "+" 12}}`

----

* Before: `{{if lt (Add $i 1) (len $.Topics)}}`
* After: `{{if Eval $i "+" 1 "<" (len $.Topics)}}`

## FAQ

### Why not use an existing expression package?

We need a highly customized expression engine:

* do the calculation on the fly, without pre-compiling
* deal with int/int64/float64 types, to make the result could be used in
Golang template.
* make the syntax could be used in the Golang template directly
* do not introduce too much complex or strange syntax, we just need a
simple calculator.
* it needs to strictly follow Golang template's behavior, for example,
Golang template treats all non-zero values as truth, but many 3rd
packages don't do so.

### What's the benefit?

* Developers don't need to add more `Add`/`Mul`/`Sub`-like functions,
they were getting more and more.
Now, only one `Eval` is enough for all cases.
* The new code reads better than old `{{Add (Mul $glyph.Row 12) 12}}`,
the old one isn't familiar to most procedural programming developers
(eg, the Golang expression syntax).
* The `Eval` is fully covered by tests, many old `Add`/`Mul`-like
functions were never tested.

### The performance?

It doesn't use `reflect`, it doesn't need to parse or compile when used
in Golang template, the performance is as fast as native Go template.

### Is it too complex? Could it be unstable?

The expression calculator program is a common homework for computer
science students, and it's widely used as a teaching and practicing
purpose for developers. The algorithm is pretty well-known.

The behavior can be clearly defined, it is stable.
2023-04-07 21:25:49 +08:00

128 lines
5.1 KiB
Handlebars

{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository wiki view">
{{template "repo/header" .}}
{{$title := .title}}
<div class="ui container">
<div class="repo-button-row gt-df gt-ac gt-sb gt-fw">
<div class="gt-df gt-ac">
<div class="choose page">
<div class="ui floating filter dropdown" data-no-results="{{.locale.Tr "repo.pulls.no_results"}}">
<div class="ui basic small button">
<span class="text">
{{.locale.Tr "repo.wiki.page"}}:
<strong>{{$title}}</strong>
</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="menu">
<div class="ui icon search input">
<i class="icon gt-df gt-ac gt-jc gt-m-0">{{svg "octicon-filter" 16}}</i>
<input name="search" placeholder="{{.locale.Tr "repo.wiki.filter_page"}}...">
</div>
<div class="scrolling menu">
{{range .Pages}}
<div class="item {{if eq $.Title .Name}}selected{{end}}" data-url="{{$.RepoLink}}/wiki/{{.SubURL}}">{{.Name}}</div>
{{end}}
</div>
</div>
</div>
</div>
</div>
<div class="gt-df gt-ac">
<div class="ui action small input" id="clone-panel">
{{template "repo/clone_buttons" .}}
{{template "repo/clone_script" .}}
</div>
</div>
</div>
<div class="ui dividing header">
<div class="ui stackable grid">
<div class="eight wide column">
<a class="file-revisions-btn ui basic button" title="{{.locale.Tr "repo.wiki.file_revision"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_revision" ><span>{{.CommitCount}}</span> {{svg "octicon-history"}}</a>
{{$title}}
<div class="ui sub header">
{{$timeSince := TimeSince .Author.When $.locale}}
{{.locale.Tr "repo.wiki.last_commit_info" .Author.Name $timeSince | Safe}}
</div>
</div>
<div class="eight wide right aligned column">
{{if .EscapeStatus.Escaped}}
<a class="ui small button unescape-button gt-hidden">{{.locale.Tr "repo.unescape_control_characters"}}</a>
<a class="ui small button escape-button">{{.locale.Tr "repo.escape_control_characters"}}</a>
{{end}}
{{if and .CanWriteWiki (not .Repository.IsMirror)}}
<div class="ui right">
<a class="ui small button" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_edit">{{.locale.Tr "repo.wiki.edit_page_button"}}</a>
<a class="ui green small button" href="{{.RepoLink}}/wiki?action=_new">{{.locale.Tr "repo.wiki.new_page_button"}}</a>
<a class="ui red small button delete-button" href="" data-url="{{.RepoLink}}/wiki/{{.PageURL}}?action=_delete" data-id="{{.PageURL}}">{{.locale.Tr "repo.wiki.delete_page_button"}}</a>
</div>
{{end}}
</div>
</div>
</div>
{{if .FormatWarning}}
<div class="ui negative message">
<p>{{.FormatWarning}}</p>
</div>
{{end}}
<div class="ui {{if or .sidebarPresent .toc}}grid equal width{{end}}" style="margin-top: 1rem;">
<div class="ui {{if or .sidebarPresent .toc}}eleven wide column{{end}} segment markup wiki-content-main">
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus "root" $}}
{{.content | Safe}}
</div>
{{if or .sidebarPresent .toc}}
<div class="column" style="padding-top: 0;">
{{if .toc}}
<div class="ui segment wiki-content-toc">
<details open>
<summary>
<div class="ui header">{{.locale.Tr "toc"}}</div>
</summary>
{{$level := 0}}
{{range .toc}}
{{if lt $level .Level}}{{range Iterate (Eval .Level "-" $level)}}<ul>{{end}}{{end}}
{{if gt $level .Level}}{{range Iterate (Eval $level "-" .Level)}}</ul>{{end}}{{end}}
{{$level = .Level}}
<li><a href="#{{.ID}}">{{.Text}}</a></li>
{{end}}
{{range Iterate $level}}</ul>{{end}}
</details>
</div>
{{end}}
{{if .sidebarPresent}}
<div class="ui segment wiki-content-sidebar">
{{if and .CanWriteWiki (not .Repository.IsMirror)}}
<a class="ui right floated muted" href="{{.RepoLink}}/wiki/_Sidebar?action=_edit" aria-label="{{.locale.Tr "repo.wiki.edit_page_button"}}">{{svg "octicon-pencil"}}</a>
{{end}}
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .sidebarEscapeStatus "root" $}}
{{.sidebarContent | Safe}}
</div>
{{end}}
</div>
{{end}}
</div>
{{if .footerPresent}}
<div class="ui segment wiki-content-footer">
{{if and .CanWriteWiki (not .Repository.IsMirror)}}
<a class="ui right floated muted" href="{{.RepoLink}}/wiki/_Footer?action=_edit" aria-label="{{.locale.Tr "repo.wiki.edit_page_button"}}">{{svg "octicon-pencil"}}</a>
{{end}}
{{template "repo/unicode_escape_prompt" dict "footerEscapeStatus" .sidebarEscapeStatus "root" $}}
{{.footerContent | Safe}}
</div>
{{end}}
</div>
</div>
<div class="ui small basic delete modal">
<div class="ui icon header">
{{svg "octicon-trash"}}
{{.locale.Tr "repo.wiki.delete_page_button"}}
</div>
<div class="content">
<p>{{.locale.Tr "repo.wiki.delete_page_notice_1" ($title|Escape) | Safe}}</p>
</div>
{{template "base/delete_modal_actions" .}}
</div>
{{template "base/footer" .}}