mirror of
https://github.com/miniflux/v2.git
synced 2025-09-30 19:22:11 +00:00
Having the CSP built in a function instead of in the template makes it easier to properly construct it. This was also the opportunity to switch from default-src 'self' to default-src 'none', to deny everything that isn't explicitly allowed, instead of allowing everything coming from 'self'. Moreover, as Miniflux is shoving the content of feeds in the same origin as itself, using self doesn't do much security-wise. It's much better to systematically use a nonce-based policy, so that an attacker able to bypass the sanitization will have to guess the nonce to gain arbitrary javascript execution. While the merge-request has been tested locally, it would still be prudent to thoroughly test it before merging, as it has the potential to break the user-interface should weird constructs be used.
193 lines
12 KiB
HTML
193 lines
12 KiB
HTML
{{ define "base" }}
|
|
<!DOCTYPE html>
|
|
<html lang="{{ replace .language "_" "-"}}">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>{{template "title" .}} - Miniflux</title>
|
|
|
|
<meta name="apple-mobile-web-app-title" content="Miniflux">
|
|
<meta name="googlebot" content="notranslate">
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<meta name="referrer" content="no-referrer">
|
|
<meta name="robots" content="noindex,nofollow">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
<meta name="theme-color" content="{{ theme_color .theme "light" }}" media="(prefers-color-scheme: light)">
|
|
<meta name="theme-color" content="{{ theme_color .theme "dark" }}" media="(prefers-color-scheme: dark)">
|
|
|
|
<link rel="manifest" href="{{ route "webManifest" }}" crossorigin="use-credentials">
|
|
|
|
<link rel="icon" type="image/png" sizes="16x16" href="{{ route "appIcon" "filename" "icon-16.png" }}">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="{{ route "appIcon" "filename" "icon-32.png" }}">
|
|
<link rel="icon" type="image/png" sizes="128x128" href="{{ route "appIcon" "filename" "icon-128.png" }}">
|
|
<link rel="icon" type="image/png" sizes="192x192" href="{{ route "appIcon" "filename" "icon-192.png" }}">
|
|
<link rel="apple-touch-icon" sizes="120x120" href="{{ route "appIcon" "filename" "icon-120.png" }}">
|
|
<link rel="apple-touch-icon" sizes="152x152" href="{{ route "appIcon" "filename" "icon-152.png" }}">
|
|
<link rel="apple-touch-icon" sizes="167x167" href="{{ route "appIcon" "filename" "icon-167.png" }}">
|
|
<link rel="apple-touch-icon" sizes="180x180" href="{{ route "appIcon" "filename" "icon-180.png" }}">
|
|
|
|
{{ $cspNonce := nonce }}
|
|
{{ csp .user $cspNonce | safeHTML }}
|
|
<link rel="stylesheet" nonce="{{ $cspNonce }}" type="text/css" href="{{ route "stylesheet" "name" .theme "checksum" .theme_checksum }}">
|
|
<script nonce="{{ $cspNonce }}" src="{{ route "javascript" "name" "app" "checksum" .app_js_checksum }}" type="module"></script>
|
|
{{ if .user -}}
|
|
{{ if .user.Stylesheet -}}
|
|
<style nonce="{{ $cspNonce }}">{{ .user.Stylesheet | safeCSS }}</style>
|
|
{{ end -}}
|
|
{{ if .user.CustomJS -}}
|
|
<script type="module" nonce="{{ $cspNonce }}">{{ .user.CustomJS | safeJS }}</script>
|
|
{{ end -}}
|
|
{{ end -}}
|
|
</head>
|
|
<body
|
|
data-service-worker-url="{{ route "javascript" "name" "service-worker" "checksum" .sw_js_checksum }}"
|
|
{{ if .csrf }}data-csrf-token="{{ .csrf }}"{{ end }}
|
|
data-add-subscription-url="{{ route "addSubscription" }}"
|
|
data-entries-status-url="{{ route "updateEntriesStatus" }}"
|
|
data-refresh-all-feeds-url="{{ route "refreshAllFeeds" }}"
|
|
{{ if .webAuthnEnabled }}
|
|
data-webauthn-register-begin-url="{{ route "webauthnRegisterBegin" }}"
|
|
data-webauthn-register-finish-url="{{ route "webauthnRegisterFinish" }}"
|
|
data-webauthn-login-begin-url="{{ route "webauthnLoginBegin" }}"
|
|
data-webauthn-login-finish-url="{{ route "webauthnLoginFinish" }}"
|
|
data-webauthn-delete-all-url="{{ route "webauthnDeleteAll" }}"
|
|
{{ end }}
|
|
{{ if .user }}
|
|
{{ if not .user.KeyboardShortcuts }}data-disable-keyboard-shortcuts="true"{{ end }}
|
|
data-mark-as-read-on-view="{{ if .user.MarkReadOnView }}true{{ else }}false{{ end }}"
|
|
{{ end }}>
|
|
|
|
{{ if .user }}
|
|
<a class="skip-to-content-link" href="#main">{{ t "skip_to_content" }}</a>
|
|
<header class="header">
|
|
<nav>
|
|
<div class="logo" data-toggle-button-label="{{ t "menu.title" }}">
|
|
<a aria-label="{{ t "menu.home_page" }}" href="{{ route .user.DefaultHomePage }}">
|
|
Mini<span>flux</span>
|
|
</a>
|
|
{{ icon "chevron-down"}}
|
|
</div>
|
|
<ul id="header-menu">
|
|
<li {{ if eq .menu "unread" }}class="active"{{ end }} title="{{ t "tooltip.keyboard_shortcuts" "g u" }}">
|
|
<a href="{{ route "unread" }}"
|
|
data-page="unread"
|
|
{{ if gt .countUnread 0 }}
|
|
aria-label="{{ t "menu.unread" }}, {{ plural "page.unread_entry_count" .countUnread .countUnread }}"
|
|
{{ end }}
|
|
>
|
|
{{ icon "entries" }}{{ t "menu.unread" }}
|
|
{{ if gt .countUnread 0 }}
|
|
<span class="unread-counter-wrapper" aria-hidden="true">(<span class="unread-counter">{{ .countUnread }}</span>)</span>
|
|
{{ end }}
|
|
</a>
|
|
</li>
|
|
<li {{ if eq .menu "starred" }}class="active"{{ end }} title="{{ t "tooltip.keyboard_shortcuts" "g b" }}">
|
|
<a href="{{ route "starred" }}" data-page="starred">{{ icon "star" }}{{ t "menu.starred" }}</a>
|
|
</li>
|
|
<li {{ if eq .menu "history" }}class="active"{{ end }} title="{{ t "tooltip.keyboard_shortcuts" "g h" }}">
|
|
<a href="{{ route "history" }}" data-page="history">{{ icon "history" }}{{ t "menu.history" }}</a>
|
|
</li>
|
|
<li {{ if eq .menu "feeds" }}class="active"{{ end }} title="{{ t "tooltip.keyboard_shortcuts" "g f" }}">
|
|
<a href="{{ route "feeds" }}" data-page="feeds">{{ icon "feeds" }}{{ t "menu.feeds" }}
|
|
{{ if gt .countErrorFeeds 0 }}
|
|
<span class="error-feeds-counter-wrapper">(<span class="error-feeds-counter">{{ .countErrorFeeds }}</span>)</span>
|
|
{{ end }}
|
|
</a>
|
|
<a href="{{ route "addSubscription" }}" title="{{ t "tooltip.keyboard_shortcuts" "+" }}" aria-label="{{ t "menu.add_feed" }}">
|
|
(+)
|
|
</a>
|
|
</li>
|
|
<li {{ if eq .menu "categories" }}class="active"{{ end }} title="{{ t "tooltip.keyboard_shortcuts" "g c" }}">
|
|
<a href="{{ route "categories" }}" data-page="categories">{{ icon "categories" }}{{ t "menu.categories" }}</a>
|
|
</li>
|
|
<li {{ if eq .menu "search" }}class="active"{{ end }} title="{{ t "tooltip.keyboard_shortcuts" "/" }}">
|
|
<a href="{{ route "search" }}" data-page="search">{{ icon "search" }}{{ t "menu.search" }}</a>
|
|
</li>
|
|
<li {{ if eq .menu "settings" }}class="active"{{ end }} title="{{ t "tooltip.keyboard_shortcuts" "g s" }}">
|
|
<a href="{{ route "settings" }}" data-page="settings">{{ icon "settings" }}{{ t "menu.settings" }}</a>
|
|
</li>
|
|
{{ if not hasAuthProxy }}
|
|
<li>
|
|
<a href="{{ route "logout" }}" title="{{ t "tooltip.logged_user" .user.Username }}">{{ icon "logout" }}{{ t "menu.logout" }}</a>
|
|
</li>
|
|
{{ end }}
|
|
</ul>
|
|
</nav>
|
|
</header>
|
|
{{ end }}
|
|
{{ if .flashMessage }}
|
|
<div role="alert" class="flash-message alert alert-success">{{ .flashMessage }}</div>
|
|
{{ end }}
|
|
{{ if .flashErrorMessage }}
|
|
<div role="alert" class="flash-error-message alert alert-error">{{ .flashErrorMessage }}</div>
|
|
{{ end }}
|
|
|
|
{{template "page_header" .}}
|
|
|
|
<main id="main">
|
|
{{template "content" .}}
|
|
</main>
|
|
<template id="keyboard-shortcuts">
|
|
<div id="modal-left">
|
|
<button class="btn-close-modal" aria-label="Close">x</button>
|
|
<h3 tabindex="-1" id="dialog-title">{{ t "page.keyboard_shortcuts.title" }}</h3>
|
|
|
|
<div class="keyboard-shortcuts">
|
|
<p>{{ t "page.keyboard_shortcuts.subtitle.sections" }}</p>
|
|
<ul>
|
|
<li>{{ t "page.keyboard_shortcuts.go_to_unread" }} = <strong>g + u</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.go_to_starred" }} = <strong>g + b</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.go_to_history" }} = <strong>g + h</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.go_to_feeds" }} = <strong>g + f</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.go_to_categories" }} = <strong>g + c</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.go_to_settings" }} = <strong>g + s</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.show_keyboard_shortcuts" }} = <strong>?</strong></li>
|
|
<li>{{ t "menu.add_feed" }} = <strong>+</strong></li>
|
|
</ul>
|
|
|
|
<p>{{ t "page.keyboard_shortcuts.subtitle.items" }}</p>
|
|
<ul>
|
|
<li>{{ t "page.keyboard_shortcuts.go_to_previous_item" }} = <strong>p</strong>, <strong>k</strong>, <strong>⏴</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.go_to_next_item" }} = <strong>n</strong>, <strong>j</strong>, <strong>⏵</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.go_to_feed" }} = <strong>F</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.go_to_top_item" }} = <strong>g + g</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.go_to_bottom_item" }} = <strong>G</strong></li>
|
|
</ul>
|
|
|
|
<p>{{ t "page.keyboard_shortcuts.subtitle.pages" }}</p>
|
|
<ul>
|
|
<li>{{ t "page.keyboard_shortcuts.go_to_previous_page" }} = <strong>h</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.go_to_next_page" }} = <strong>l</strong></li>
|
|
</ul>
|
|
|
|
<p>{{ t "page.keyboard_shortcuts.subtitle.actions" }}</p>
|
|
<ul>
|
|
<li>{{ t "page.keyboard_shortcuts.open_item" }} = <strong>o</strong>, <strong>Enter</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.open_original" }} = <strong>v</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.open_original_same_window" }} = <strong>V</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.open_comments" }} = <strong>c</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.open_comments_same_window" }} = <strong>C</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.toggle_read_status_next" }} = <strong>m</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.toggle_read_status_prev" }} = <strong>M</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.mark_page_as_read" }} = <strong>A</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.download_content" }} = <strong>d</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.toggle_star_status" }} = <strong>f</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.save_article" }} = <strong>s</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.toggle_entry_attachments" }} = <strong>a</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.scroll_item_to_top" }} = <strong>z + t</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.refresh_all_feeds" }} = <strong>R</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.remove_feed" }} = <strong>#</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.go_to_search" }} = <strong>/</strong></li>
|
|
<li>{{ t "page.keyboard_shortcuts.close_modal" }} = <strong>Esc</strong></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<template id="icon-read">{{ icon "read" }}</template>
|
|
<template id="icon-unread">{{ icon "unread" }}</template>
|
|
<template id="icon-star">{{ icon "star" }}</template>
|
|
<template id="icon-unstar">{{ icon "unstar" }}</template>
|
|
<template id="icon-save">{{ icon "save" }}</template>
|
|
</body>
|
|
</html>
|
|
{{ end }}
|