Files
studio/templates/repo/view_file.tmpl
T
ozan a845b744b7 feat: HTML preview via S3 with SWR caching
HTML files now render in an iframe served from S3 (tinqs-git-preview
bucket) instead of Gitea's raw endpoint which forces text/plain.

SWR flow: first request uploads blob to S3 synchronously, subsequent
requests redirect to presigned S3 URL instantly. When the blob SHA
changes (new commit), the stale version is served immediately while
the new version uploads in the background.

Security: iframe uses sandbox="allow-scripts" only (no allow-same-origin).
S3 is a different origin from git.arikigame.com, so even if JS runs in
the iframe it cannot access Gitea session cookies or API tokens.

Config: [html_preview] section in app.ini, disabled by default.
Release pipeline auto-adds config on first deploy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-20 23:33:40 +01:00

164 lines
8.7 KiB
Handlebars

<div {{if .ReadmeInList}}id="readme"{{end}} class="{{TabSizeClass .Editorconfig .FileTreePath}} non-diff-file-content"
data-global-init="initRepoFileView" data-raw-file-link="{{.RawFileLink}}">
{{- if .FileError}}
<div class="ui error message">
<div class="text left tw-whitespace-pre">{{.FileError}}</div>
</div>
{{end}}
{{- if .FileWarning}}
<div class="ui warning message">
<div class="text left tw-whitespace-pre">{{.FileWarning}}</div>
</div>
{{end}}
{{if not .ReadmeInList}}
<div id="repo-file-commit-box" class="ui segment list-header tw-mb-4 tw-flex tw-justify-between">
{{template "repo/latest_commit" .}}
{{if .LatestCommit}}
{{if .LatestCommit.Committer}}
<div class="tw-text-text-light age flex-text-block">
{{DateUtils.TimeSince .LatestCommit.Committer.When}}
</div>
{{end}}
{{end}}
</div>
{{end}}
<h4 class="file-header ui top attached header tw-flex tw-items-center tw-justify-between tw-flex-wrap">
<div class="file-header-left tw-flex tw-items-center tw-py-2 tw-pr-4">
{{if .ReadmeInList}}
{{svg "octicon-book" 16 "tw-mr-2"}}
<strong><a class="muted" href="#readme">{{.ReadmeInList}}</a></strong>
{{else}}
{{template "repo/file_info" .}}
{{end}}
</div>
<div class="file-header-right file-actions flex-text-block tw-flex-wrap">
{{if .IsHTMLFile}}
<div class="ui mini buttons tw-mr-1">
<a href="?display=rendered" class="ui mini basic button {{if not .IsDisplayingSource}}active{{end}}">{{ctx.Locale.Tr "repo.file_view_preview"}}</a>
<a href="?display=source" class="ui mini basic button {{if .IsDisplayingSource}}active{{end}}">{{ctx.Locale.Tr "repo.file_view_source"}}</a>
</div>
{{end}}
{{/* Gitea icon toggles for markup etc.; HTML uses Preview/Source buttons above only */}}
{{if and .HasSourceRenderedToggle (not .IsHTMLFile)}}
<div class="ui compact icon buttons file-view-toggle-buttons">
{{if .IsRepresentableAsText}}
<a href="?display=source" class="ui mini basic button file-view-toggle-source {{if .IsDisplayingSource}}active{{end}}" data-tooltip-content="{{ctx.Locale.Tr "repo.file_view_source"}}">{{svg "octicon-code" 15}}</a>
{{end}}
<a href="?display=rendered" class="ui mini basic button file-view-toggle-rendered {{if not .IsDisplayingSource}}active{{end}}" data-tooltip-content="{{ctx.Locale.Tr "repo.file_view_rendered"}}">{{svg "octicon-file" 15}}</a>
</div>
{{end}}
{{if not .ReadmeInList}}
<div class="ui buttons tw-mr-1">
<a class="ui mini basic button" href="{{$.RawFileLink}}">{{ctx.Locale.Tr "repo.file_raw"}}</a>
{{if or .RefFullName.IsBranch .RefFullName.IsTag}}
<a class="ui mini basic button" href="{{.RepoLink}}/src/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}">{{ctx.Locale.Tr "repo.file_permalink"}}</a>
{{end}}
{{if .IsRepresentableAsText}}
<a class="ui mini basic button" href="{{.RepoLink}}/blame/{{.RefTypeNameSubURL}}/{{PathEscapeSegments .TreePath}}">{{ctx.Locale.Tr "repo.blame"}}</a>
{{end}}
<a class="ui mini basic button" href="{{.RepoLink}}/commits/{{.RefTypeNameSubURL}}/{{PathEscapeSegments .TreePath}}">{{ctx.Locale.Tr "repo.file_history"}}</a>
{{if .EscapeStatus.Escaped}}
<button class="ui mini basic button unescape-button tw-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</button>
<button class="ui mini basic button escape-button">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button>
{{end}}
</div>
<a download class="btn-octicon" data-tooltip-content="{{ctx.Locale.Tr "repo.download_file"}}" href="{{$.RawFileLink}}">{{svg "octicon-download"}}</a>
<a class="btn-octicon {{if not .CanCopyContent}}disabled{{end}}" data-global-click="onCopyContentButtonClick"
{{if not .IsDisplayingSource}}data-raw-file-link="{{$.RawFileLink}}"{{end}}
data-tooltip-content="{{if .CanCopyContent}}{{ctx.Locale.Tr "copy_content"}}{{else}}{{ctx.Locale.Tr "copy_type_unsupported"}}{{end}}"
>{{svg "octicon-copy"}}</a>
{{if and .EnableFeed .RefFullName.IsBranch}}
<a class="btn-octicon" href="{{$.RepoLink}}/rss/{{$.RefTypeNameSubURL}}/{{PathEscapeSegments .TreePath}}" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}">
{{svg "octicon-rss"}}
</a>
{{end}}
{{if .Repository.CanEnableEditor}}
{{if .CanEditFile}}
<a class="btn-octicon" data-tooltip-content="{{.EditFileTooltip}}" href="{{.RepoLink}}/_edit/{{PathEscapeSegments .BranchName}}/{{PathEscapeSegments .TreePath}}">{{svg "octicon-pencil"}}</a>
{{else}}
<span class="btn-octicon disabled" data-tooltip-content="{{.EditFileTooltip}}">{{svg "octicon-pencil"}}</span>
{{end}}
{{if .CanDeleteFile}}
<a class="btn-octicon btn-octicon-danger" data-tooltip-content="{{.DeleteFileTooltip}}" href="{{.RepoLink}}/_delete/{{PathEscapeSegments .BranchName}}/{{PathEscapeSegments .TreePath}}">{{svg "octicon-trash"}}</a>
{{else}}
<span class="btn-octicon disabled" data-tooltip-content="{{.DeleteFileTooltip}}">{{svg "octicon-trash"}}</span>
{{end}}
{{end}}
{{else if .EscapeStatus.Escaped}}
<button class="ui mini basic button unescape-button tw-mr-1 tw-hidden">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</button>
<button class="ui mini basic button escape-button tw-mr-1">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button>
{{end}}
{{if and .ReadmeInList .CanEditReadmeFile}}
<a class="btn-octicon" data-tooltip-content="{{ctx.Locale.Tr "repo.editor.edit_this_file"}}" href="{{.RepoLink}}/_edit/{{PathEscapeSegments .BranchName}}/{{PathEscapeSegments .FileTreePath}}">{{svg "octicon-pencil"}}</a>
{{end}}
</div>
</h4>
<div class="ui bottom attached table unstackable segment">
{{if not .RenderAsMarkup}}
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus}}
{{end}}
<div class="file-view {{if eq .RenderAsMarkup "markup-inplace"}}markup {{.MarkupType}}{{else if .IsPlainText}}plain-text{{else if .IsDisplayingSource}}code-view{{end}}">
{{if .IsFileTooLarge}}
{{template "shared/filetoolarge" dict "RawFileLink" .RawFileLink}}
{{else if not .FileSize}}
{{template "shared/fileisempty"}}
{{else if .RenderAsMarkup}}
{{.FileContent}}
{{else if .IsPlainText}}
<pre>{{if .FileContent}}{{.FileContent}}{{end}}</pre>
{{else if .FileContent}}
<table>
<tbody>
{{range $idx, $code := .FileContent}}
{{$line := Eval $idx "+" 1}}
<tr>
<td class="lines-num"><span id="L{{$line}}" data-line-number="{{$line}}"></span></td>
{{ctx.RenderUtils.RenderUnicodeEscapeToggleTd $.EscapeStatus (index $.LineEscapeStatus $idx)}}
<td rel="L{{$line}}" class="lines-code chroma"><code class="code-inner">{{$code}}</code></td>
</tr>
{{end}}
</tbody>
</table>
{{else}}
<div class="view-raw">
{{if .IsImageFile}}
<img alt="{{$.RawFileLink}}" src="{{$.RawFileLink}}">
{{else if .IsVideoFile}}
<video controls src="{{$.RawFileLink}}">
<strong>{{ctx.Locale.Tr "repo.video_not_supported_in_browser"}}</strong>
</video>
{{else if .IsAudioFile}}
<audio controls src="{{$.RawFileLink}}">
<strong>{{ctx.Locale.Tr "repo.audio_not_supported_in_browser"}}</strong>
</audio>
{{else if .IsHTMLFile}}
{{if .HTMLPreviewEnabled}}
<iframe src="{{$.HTMLPreviewLink}}" class="html-preview-iframe" style="width:100%;min-height:600px;border:none;" sandbox="allow-scripts"></iframe>
{{else}}
<iframe src="{{$.RawFileLink}}" class="html-preview-iframe" style="width:100%;min-height:600px;border:none;" sandbox="allow-scripts"></iframe>
{{end}}
{{else}}
<div class="file-view-render-container">
<div class="file-view-raw-prompt tw-p-4">
<a href="{{$.RawFileLink}}" rel="nofollow">{{ctx.Locale.Tr "repo.file_view_raw"}}</a>
</div>
</div>
{{end}}
</div>
{{end}}
</div>
<div class="code-line-menu tippy-target">
{{if $.Permission.CanRead ctx.Consts.RepoUnitTypeIssues}}
<a class="item ref-in-new-issue" role="menuitem" data-url-issue-new="{{.RepoLink}}/issues/new" data-url-param-body-link="{{.Repository.Link}}/src/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}{{if $.HasSourceRenderedToggle}}?display=source{{end}}" rel="nofollow noindex">{{ctx.Locale.Tr "repo.issues.context.reference_issue"}}</a>
{{end}}
<a class="item view_git_blame" role="menuitem" href="{{.Repository.Link}}/blame/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}">{{ctx.Locale.Tr "repo.view_git_blame"}}</a>
<a class="item copy-line-permalink" role="menuitem" data-url="{{.Repository.Link}}/src/commit/{{PathEscape .CommitID}}/{{PathEscapeSegments .TreePath}}{{if $.HasSourceRenderedToggle}}?display=source{{end}}">{{ctx.Locale.Tr "repo.file_copy_permalink"}}</a>
</div>
</div>
</div>