1
0
mirror of https://github.com/webrecorder/pywb.git synced 2025-03-15 00:03:28 +01:00

ui/templates: fixes based on feedback from @ldko!

templates: add placeholder templates (footer.html, head.html)
templates: allow 'base_html', 'footer_html', 'head_html', 'header_html' to be added via wb-manager 'add-template' cmd
ui: fix logo path, support linking
ui: make url on banner an input field
docs: clarify docs around templates, paths
This commit is contained in:
Ilya Kreymer 2022-01-23 17:55:24 -08:00 committed by Tessa Walsh
parent 29860bcb24
commit 6260b226ce
16 changed files with 121 additions and 39 deletions

View File

@ -60,6 +60,15 @@ When using the custom banner, it is possible to configure a logo by setting ``ui
If omitted, the standard pywb logo will be used by default. If omitted, the standard pywb logo will be used by default.
If set, the logo should point to a file in the static directory (default is ``static`` but can be changed via the ``static_dir`` config option).
For example, to use the file ``./static/my-logo.png`` as the logo, set:
.. code:: yaml
ui:
logo: my-logo.png
Updating the Vue UI Updating the Vue UI
------------------- -------------------

View File

@ -61,6 +61,9 @@ can also be overriden:
* ``footer.html`` -- Template for adding content as the "footer" of the ``<body>`` tag of the ``base`` template * ``footer.html`` -- Template for adding content as the "footer" of the ``<body>`` tag of the ``base`` template
Note: The default pywb ``head.html`` and ``footer.html`` are currently blank. They can be populated to customize the rendering, add analytics, etc... as needed.
The ``base.html`` template also provides five blocks that can be supplied by templates that extend it. The ``base.html`` template also provides five blocks that can be supplied by templates that extend it.
* ``title`` -- Block for supplying the title for the page * ``title`` -- Block for supplying the title for the page
@ -157,7 +160,7 @@ Template variables:
* ``{{ ui }}`` - an optional ``ui`` dictionary from ``config.yaml``, if any * ``{{ ui }}`` - an optional ``ui`` dictionary from ``config.yaml``, if any
* ``{{ static_prefix }}`` - the prefix from which static files will be accessed from, e.g. ``http://localhost:8080/static/`` * ``{{ static_prefix }}`` - the prefix from which static files will be accessed from, e.g. ``http://localhost:8080/static/``.
Replay and Banner Templates Replay and Banner Templates
@ -186,6 +189,8 @@ Template variables:
* ``{{ wb_prefix }}`` - the collection prefix, e.g. ``http://localhost:8080/pywb/`` * ``{{ wb_prefix }}`` - the collection prefix, e.g. ``http://localhost:8080/pywb/``
* ``{{ host_prefix }}`` - the pywb server origin, e.g. ``http://localhost:8080``
* ``{{ config }}`` - provides the contents of the ``config.yaml`` as a dictionary. * ``{{ config }}`` - provides the contents of the ``config.yaml`` as a dictionary.
* ``{{ ui }}`` - an optional ``ui`` dictionary from ``config.yaml``, if any. * ``{{ ui }}`` - an optional ``ui`` dictionary from ``config.yaml``, if any.
@ -232,10 +237,10 @@ Template variables:
* ``{{ wb_url }}`` - A complete ``WbUrl`` object, which contains the ``url``, ``timestamp`` and ``mod`` properties, representing the replay url. * ``{{ wb_url }}`` - A complete ``WbUrl`` object, which contains the ``url``, ``timestamp`` and ``mod`` properties, representing the replay url.
* ``{{ is_framed }}`` - true/false if currently in framed mode.
* ``{{ wb_prefix }}`` - the collection prefix, e.g. ``http://localhost:8080/pywb/`` * ``{{ wb_prefix }}`` - the collection prefix, e.g. ``http://localhost:8080/pywb/``
* ``{{ is_proxy }}`` - set to true if page is being loaded via an HTTP/S proxy (checks if WSGI env has ``wsgiprox.proxy_host`` set)
.. _custom-top-frame: .. _custom-top-frame:
@ -332,7 +337,7 @@ The following template variables are available to all templates.
* ``{{ env.pywb_proxy_magic }}`` - if set, indicates pywb is accessed via proxy. See :ref:`https-proxy` * ``{{ env.pywb_proxy_magic }}`` - if set, indicates pywb is accessed via proxy. See :ref:`https-proxy`
* ``{{ static_prefix }}`` - path to use for loading static files. * ``{{ static_prefix }}`` - URL path to use for loading static files.
UI Configuration UI Configuration

View File

@ -25,12 +25,13 @@ To enable the logo set the ``ui.logo`` property in ``config.yaml`` to point to t
The URL can be any image URL, including a URL served from the static directory. The URL can be any image URL, including a URL served from the static directory.
For example, to add the default pywb logo to the banner, use the following in the config: For example, to add the default pywb logo to the banner, use the following in the config, which will
load the logo from ``./static/pywb-logo-sm.png``
.. code:: yaml .. code:: yaml
ui: ui:
logo: /static/pywb-logo-sm.png logo: pywb-logo-sm.png
New Vue-based UI (Alpha) New Vue-based UI (Alpha)
@ -66,7 +67,7 @@ It is possible to change these settings via ``config.yaml``:
* ``static_prefix`` - sets the URL path used in pywb to serve static content (default ``static``) * ``static_prefix`` - sets the URL path used in pywb to serve static content (default ``static``)
* ``static_dir`` - sets the directory name used to read static files (default ``static``) * ``static_dir`` - sets the directory name used to read static files on disk (default ``static``)
While pywb can serve static files, it is recommended to use an existing web server to serve static files, especially if already using it in production. While pywb can serve static files, it is recommended to use an existing web server to serve static files, especially if already using it in production.

View File

@ -15,6 +15,11 @@ banner_html: banner.html
head_insert_html: head_insert.html head_insert_html: head_insert.html
frame_insert_html: frame_insert.html frame_insert_html: frame_insert.html
base_html: base.html
header_html: header.html
footer_html: footer.html
head_html: head.html
query_html: query.html query_html: query.html
search_html: search.html search_html: search.html
not_found_html: not_found.html not_found_html: not_found.html
@ -39,6 +44,12 @@ html_templates:
- not_found_html - not_found_html
- home_html - home_html
- base_html
- header_html
- head_html
- footer_html
- error_html - error_html
- proxy_cert_download_html - proxy_cert_download_html
- proxy_select_html - proxy_select_html

View File

@ -237,17 +237,20 @@ directory structure expected by pywb
v = defaults[n] v = defaults[n]
print('- {0}: (pywb/{1})'.format(n, v)) print('- {0}: (pywb/{1})'.format(n, v))
def _confirm_overwrite(self, full_path, msg): def _confirm_overwrite(self, full_path, msg, ignore=False):
if not os.path.isfile(full_path): if not os.path.isfile(full_path):
return True return True
if ignore:
return False
res = get_input(msg) res = get_input(msg)
try: try:
res = strtobool(res) res = strtobool(res)
except ValueError: except ValueError:
res = False res = False
if not res: if not res and not ignore:
raise IOError('Skipping, {0} already exists'.format(full_path)) raise IOError('Skipping, {0} already exists'.format(full_path))
def _get_template_path(self, template_name, verb): def _get_template_path(self, template_name, verb):
@ -268,7 +271,7 @@ directory structure expected by pywb
return full_path, filename return full_path, filename
def add_template(self, template_name, force=False): def add_template(self, template_name, force=False, ignore=False):
full_path, filename = self._get_template_path(template_name, 'add') full_path, filename = self._get_template_path(template_name, 'add')
msg = ('Template file "{0}" ({1}) already exists. ' + msg = ('Template file "{0}" ({1}) already exists. ' +
@ -276,7 +279,11 @@ directory structure expected by pywb
msg = msg.format(full_path, template_name) msg = msg.format(full_path, template_name)
if not force: if not force:
self._confirm_overwrite(full_path, msg) res = self._confirm_overwrite(full_path, msg, ignore)
if ignore and not res:
return
os.makedirs(os.path.dirname(full_path), exist_ok=True)
data = resource_string('pywb', filename) data = resource_string('pywb', filename)
with open(full_path, 'w+b') as fh: with open(full_path, 'w+b') as fh:
@ -286,6 +293,9 @@ directory structure expected by pywb
msg = 'Copied default template "{0}" to "{1}"' msg = 'Copied default template "{0}" to "{1}"'
print(msg.format(filename, full_path)) print(msg.format(filename, full_path))
if template_name != "base_html":
self.add_template("base_html", force=False, ignore=True)
def remove_template(self, template_name, force=False): def remove_template(self, template_name, force=False):
full_path, filename = self._get_template_path(template_name, 'remove') full_path, filename = self._get_template_path(template_name, 'remove')

View File

@ -164,8 +164,9 @@ This file is part of pywb, https://github.com/webrecorder/pywb
logo.setAttribute("class", "_wb_linked_logo"); logo.setAttribute("class", "_wb_linked_logo");
var logoContents = ""; var logoContents = "";
logoContents += "<img src='" + window.banner_info.logoImg + "' alt='" + window.banner_info.logoAlt + "'>"; var logoUrl = window.banner_info.staticPrefix + "/" + window.banner_info.logoImg;
logoContents += "<img src='" + window.banner_info.logoImg + "' class='_wb_mobile' alt='" + window.banner_info.logoAlt + "'>"; logoContents += "<img src='" + logoUrl + "' alt='" + window.banner_info.logoAlt + "'>";
logoContents += "<img src='" + logoUrl + "' class='_wb_mobile' alt='" + window.banner_info.logoAlt + "'>";
logo.innerHTML = logoContents; logo.innerHTML = logoContents;
this.banner.appendChild(logo); this.banner.appendChild(logo);

File diff suppressed because one or more lines are too long

View File

@ -34,7 +34,7 @@ window.banner_info = {
<link rel='stylesheet' href='{{ static_prefix }}/vue_banner.css'/> <link rel='stylesheet' href='{{ static_prefix }}/vue_banner.css'/>
<script src="{{ static_prefix }}/vue/vueui.js"></script> <script src="{{ static_prefix }}/vue/vueui.js"></script>
<script> <script>
VueUI.main("{{ static_prefix }}", "{{ url }}", "{{ wb_prefix }}", "{{ timestamp }}"); VueUI.main("{{ static_prefix }}", "{{ url }}", "{{ wb_prefix }}", "{{ timestamp }}", "{{ ui.logo }}");
</script> </script>
{% if ui.vue_timeline_banner %} {% if ui.vue_timeline_banner %}

View File

@ -0,0 +1,2 @@
{# place content to be added at the very end of the <body> tag in this file below #}

1
pywb/templates/head.html Normal file
View File

@ -0,0 +1 @@
{# place optional content to be injected into the <head> of every page in this file below #}

View File

@ -1,3 +1,4 @@
{# place content to be added at the very beginning of the <body> tag in this file below #}
<header> <header>
{% if not err_msg and locales|length > 1 %} {% if not err_msg and locales|length > 1 %}
<div class="language-select"> <div class="language-select">

View File

@ -10,7 +10,7 @@
{% for route in routes %} {% for route in routes %}
<li> <li>
<a href="{{ env['pywb.app_prefix'] + ('/' + env.pywb_lang if env.pywb_lang else '') + '/' + route }}">{{ '/' + route }}</a> <a href="{{ env['pywb.app_prefix'] + ('/' + env.pywb_lang if env.pywb_lang else '') + '/' + route }}">{{ '/' + route }}</a>
{% if all_metadata and all_metadata[route] %} {% if all_metadata and all_metadata[route] and all_metadata[route].title %}
({{ all_metadata[route].title }}) ({{ all_metadata[route].title }})
{% endif %} {% endif %}
</li> </li>

View File

@ -75,7 +75,7 @@
renderCal.init(); renderCal.init();
{% else %} {% else %}
VueUI.main("{{ static_prefix }}", "{{ url }}", "{{ prefix }}"); VueUI.main("{{ static_prefix }}", "{{ url }}", "{{ prefix }}", undefined, "{{ ui.logo }}");
{% endif %} {% endif %}

View File

@ -1,4 +1,4 @@
__version__ = '2.6.9' __version__ = '2.7.0b1'
if __name__ == '__main__': if __name__ == '__main__':
print(__version__) print(__version__)

View File

@ -2,7 +2,7 @@
<div class="app" :class="{expanded: showTimelineView}" data-app="webrecorder-replay-app"> <div class="app" :class="{expanded: showTimelineView}" data-app="webrecorder-replay-app">
<div class="banner"> <div class="banner">
<div class="line"> <div class="line">
<div class="logo"><img :src="config.logoImg" /></div> <div class="logo"><a href="/"><img :src="config.logoImg" style="max-width: 80px" /></a></div>
<div class="timeline-wrap"> <div class="timeline-wrap">
<div class="line"> <div class="line">
<div class="breadcrumbs-wrap"> <div class="breadcrumbs-wrap">
@ -35,13 +35,15 @@
</div> </div>
</div> </div>
<div class="snapshot-title"> <div class="snapshot-title">
<div>{{ config.url }}</div> <form @submit="gotoUrl">
<input id="theurl" type="text" :value="config.url"></input>
</form>
<div v-if="currentSnapshot && !showFullView"> <div v-if="currentSnapshot && !showFullView">
<span v-if="config.title">{{ config.title }}</span> <span v-if="config.title">{{ config.title }}</span>
Current capture: {{currentSnapshot.getTimeDateFormatted()}} Current capture: {{currentSnapshot.getTimeDateFormatted()}}
</div> </div>
</div> </div>
<CalendarYear v-if="showFullView" <CalendarYear v-if="showFullView && currentPeriod && currentPeriod.children.length"
:period="currentPeriod" :period="currentPeriod"
:current-snapshot="currentSnapshot" :current-snapshot="currentSnapshot"
@goto-period="gotoPeriod"> @goto-period="gotoPeriod">
@ -124,6 +126,13 @@ export default {
this.$emit("show-snapshot", snapshot); this.$emit("show-snapshot", snapshot);
this.showFullView = false; this.showFullView = false;
}, },
gotoUrl(event) {
event.preventDefault();
const newUrl = document.querySelector("#theurl").value;
if (newUrl !== this.url) {
window.location.href = this.config.prefix + "*/" + newUrl;
}
},
init() { init() {
this.config.url = this.config.initialView.url; this.config.url = this.config.initialView.url;
if (this.config.initialView.title) { if (this.config.initialView.title) {
@ -134,8 +143,10 @@ export default {
this.showTimelineView = true; this.showTimelineView = true;
} else { } else {
this.showFullView = false; this.showFullView = false;
this.showFullView = true; this.showTimelineView = true;
this.setSnapshot(this.config.initialView); if (this.currentPeriod.children.length) {
this.setSnapshot(this.config.initialView);
}
} }
if (window.sessionStorage) { if (window.sessionStorage) {
const currentPeriodId = window.sessionStorage.getItem(this.sessionStorageUrlKey); const currentPeriodId = window.sessionStorage.getItem(this.sessionStorageUrlKey);
@ -242,4 +253,7 @@ export default {
font-weight: bold; font-weight: bold;
font-size: 16px; font-size: 16px;
} }
#theurl {
width: 400px;
}
</style> </style>

View File

@ -6,16 +6,17 @@ import Vue from "vue/dist/vue.esm.browser";
// =========================================================================== // ===========================================================================
export function main(staticPrefix, url, prefix, timestamp) { export function main(staticPrefix, url, prefix, timestamp, logoUrl) {
new CDXLoader(staticPrefix, url, prefix, timestamp); new CDXLoader(staticPrefix, url, prefix, timestamp, logoUrl);
} }
// =========================================================================== // ===========================================================================
class CDXLoader { class CDXLoader {
constructor(staticPrefix, url, prefix, timestamp) { constructor(staticPrefix, url, prefix, timestamp, logoUrl) {
this.opts = {}; this.opts = {};
this.prefix = prefix; this.prefix = prefix;
this.staticPrefix = staticPrefix; this.staticPrefix = staticPrefix;
this.logoUrl = logoUrl;
this.isReplay = (timestamp !== undefined); this.isReplay = (timestamp !== undefined);
@ -44,8 +45,7 @@ class CDXLoader {
this.opts.initialView = {url, timestamp}; this.opts.initialView = {url, timestamp};
// TODO: make configurable this.opts.logoImg = this.staticPrefix + "/" + (this.logoUrl ? this.logoUrl : "pywb-logo-sm.png");
this.opts.logoImg = staticPrefix + "/pywb-logo-sm.png";
this.loadCDX(queryURL).then((cdxList) => { this.loadCDX(queryURL).then((cdxList) => {
this.app = this.initApp(cdxList, this.opts, (snapshot) => this.loadSnapshot(snapshot)); this.app = this.initApp(cdxList, this.opts, (snapshot) => this.loadSnapshot(snapshot));
@ -60,7 +60,7 @@ class CDXLoader {
app.$set(app, "snapshots", pywbData.snapshots); app.$set(app, "snapshots", pywbData.snapshots);
app.$set(app, "currentPeriod", pywbData.timeline); app.$set(app, "currentPeriod", pywbData.timeline);
app.$set(app, "config", {...app.config, ...config}); app.$set(app, "config", {...app.config, ...config, prefix: this.prefix});
app.$mount("#app"); app.$mount("#app");