Move page meta information from the header to a new status side panel component inside of the page editing UI (#8285)

Co-authored-by: Thibaud Colas <thibaudcolas@gmail.com>
pull/8171/head
Steve Stein 2022-04-12 20:21:51 -06:00 zatwierdzone przez GitHub
rodzic bd3efa40cb
commit f323d88765
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
38 zmienionych plików z 459 dodań i 246 usunięć

Wyświetl plik

@ -50,6 +50,7 @@ Changelog
* Add internationalisation UI to modeladmin (Andrés Martano)
* Support chunking in `PageQuerySet.specific()` to reduce memory consumption (Andy Babic)
* Implement new tabs design across the admin interface (Steven Steinwand)
* Move page meta information from the header to a new status side panel component inside of the page editing UI (Steven Steinwand, Karl Hobley)
* Fix: When using `simple_translations` ensure that the user is redirected to the page edit view when submitting for a single locale (Mitchel Cabuloy)
* Fix: When previewing unsaved changes to `Form` pages, ensure that all added fields are correctly shown in the preview (Joshua Munn)
* Fix: When Documents (e.g. PDFs) have been configured to be served inline via `WAGTAILDOCS_CONTENT_TYPES` & `WAGTAILDOCS_INLINE_CONTENT_TYPES` ensure that the filename is correctly set in the `Content-Disposition` header so that saving the files will use the correct filename (John-Scott Atlakson)

Wyświetl plik

@ -1,38 +0,0 @@
.comment-notifications-dropdown {
position: absolute;
display: none;
bottom: -92px;
z-index: 51;
background-color: $color-text-base;
padding: 20px;
border-radius: 6px;
min-width: 260px;
box-sizing: border-box;
border: 1px solid $color-text-base;
&__title {
font-size: 12px;
font-weight: 700;
color: $color-white;
}
&--active {
display: block;
}
&::before {
content: '';
position: absolute;
top: -8px;
width: 0;
height: 0;
z-index: 2;
// Remove once we drop support for Safari 13.
// stylelint-disable-next-line property-disallowed-list
right: 18px;
inset-inline-end: 18px;
border-style: solid;
border-width: 0 8px 8px 8px;
border-color: transparent transparent $color-text-base transparent;
}
}

Wyświetl plik

@ -4,6 +4,7 @@
$border-curvature: 3px;
@include transition(bottom 0.5s ease 1s);
@include row();
z-index: 20;
ul {
@include unlist();

Wyświetl plik

@ -15,6 +15,7 @@ $switch-color-middle-grey: #777;
display: inline-flex;
align-items: center;
margin: 5px 0;
position: relative;
// Disable forms styling that's applied to the <label> tag
width: unset;
@ -83,6 +84,8 @@ $switch-color-middle-grey: #777;
position: absolute;
opacity: 0;
pointer-events: none;
width: 100%;
height: 100%;
}
// Colour changes for when displaying on teal background

Wyświetl plik

@ -135,7 +135,6 @@ These are classes for components.
@import 'components/workflow-tasks';
@import 'components/switch';
@import 'components/comments-controls';
@import 'components/comments-notification-dropdown';
@import 'components/bulk_actions';
@import '../src/components/Sidebar/Sidebar';

Wyświetl plik

@ -1,18 +1,16 @@
/* When a lock/unlock action button is clicked, make a POST request to the relevant view */
function LockUnlockAction(csrfToken, next) {
const actionElements = document.querySelectorAll('[data-locking-action]');
const actionElements = document.querySelectorAll('[data-action-lock-unlock]');
actionElements.forEach((buttonElement) => {
buttonElement.addEventListener(
'click',
(e) => {
// Stop the button from submitting the form
e.preventDefault();
e.stopPropagation();
const formElement = document.createElement('form');
formElement.action = buttonElement.dataset.lockingAction;
formElement.action = buttonElement.dataset.url;
formElement.method = 'POST';
const csrftokenElement = document.createElement('input');

Wyświetl plik

@ -3,7 +3,7 @@ import $ from 'jquery';
$(() => {
/* Interface to set permissions from the explorer / editor */
// eslint-disable-next-line func-names
$('button.action-set-privacy').on('click', function () {
$('button[data-action-set-privacy]').on('click', function () {
// eslint-disable-next-line no-undef
ModalWorkflow({
url: this.getAttribute('data-url'),
@ -53,9 +53,19 @@ $(() => {
responses: {
setPermission(isPublic) {
if (isPublic) {
// Swap the status sidebar text and icon
$('[data-privacy-sidebar-public]').removeClass('w-hidden');
$('[data-privacy-sidebar-private]').addClass('w-hidden');
// Swap other privacy indicators in settings and the header live button
$('.privacy-indicator').removeClass('private').addClass('public');
$('.privacy-indicator-icon use').attr('href', '#icon-view');
} else {
// Swap the status sidebar text and icon
$('[data-privacy-sidebar-public]').addClass('w-hidden');
$('[data-privacy-sidebar-private]').removeClass('w-hidden');
// Swap other privacy indicators in settings and the headers live button icon
$('.privacy-indicator').removeClass('public').addClass('private');
$('.privacy-indicator-icon use').attr('href', '#icon-no-view');
}

Wyświetl plik

@ -3,7 +3,7 @@ import $ from 'jquery';
$(() => {
/* Interface to view the workflow status from the explorer / editor */
// eslint-disable-next-line func-names
$('button.action-workflow-status').on('click', function () {
$('button[data-action-workflow-status]').on('click', function () {
// eslint-disable-next-line no-undef
ModalWorkflow({
url: this.getAttribute('data-url'),

Wyświetl plik

@ -20,6 +20,7 @@ Here are other changes related to the redesign:
* Add support for adding custom attributes for link menu items in the slim sidebar (Thibaud Colas)
* Implement new slim page editor header with breadcrumb (Steven Steinwand, Karl Hobley)
* Implement new tabs design across the admin interface (Steven Steinwand)
* Move page meta information from the header to a new status side panel component inside of the page editing UI (Steven Steinwand, Karl Hobley)
### Removal of special-purpose field panel types

Wyświetl plik

@ -224,47 +224,6 @@ class DeleteMenuItem(ActionMenuItem):
return reverse("wagtailadmin_pages:delete", args=(context["page"].id,))
class LockMenuItem(ActionMenuItem):
name = "action-lock"
label = _("Lock")
aria_label = _("Apply editor lock")
icon_name = "lock"
classname = "action-secondary"
template_name = "wagtailadmin/pages/action_menu/lock_unlock_menu_item.html"
def is_shown(self, context):
return (
context["view"] == "edit"
and not context["page"].locked
and self.get_user_page_permissions_tester(context).can_lock()
)
def get_url(self, context):
return reverse("wagtailadmin_pages:lock", args=(context["page"].id,))
def get_context_data(self, parent_context):
context = super().get_context_data(parent_context)
context["aria_label"] = self.aria_label
return context
class UnlockMenuItem(LockMenuItem):
name = "action-unlock"
label = _("Unlock")
aria_label = _("Apply editor lock")
icon_name = "lock-open"
def is_shown(self, context):
return (
context["view"] == "edit"
and context["page"].locked
and self.get_user_page_permissions_tester(context).can_unlock()
)
def get_url(self, context):
return reverse("wagtailadmin_pages:unlock", args=(context["page"].id,))
class SaveDraftMenuItem(ActionMenuItem):
name = "action-save-draft"
label = _("Save Draft")
@ -307,8 +266,6 @@ def _get_base_page_action_menu_items():
BASE_PAGE_ACTION_MENU_ITEMS = [
SaveDraftMenuItem(order=0),
DeleteMenuItem(order=10),
LockMenuItem(order=15),
UnlockMenuItem(order=15),
UnpublishMenuItem(order=20),
PublishMenuItem(order=30),
CancelWorkflowMenuItem(order=40),

Wyświetl plik

@ -1,16 +1,20 @@
from django.conf import settings
from django.forms import Media
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy
from wagtail.admin.ui.components import Component
from wagtail.models import UserPagePermissionsProxy
class BaseSidePanel(Component):
def __init__(self, page):
def __init__(self, page, request):
self.page = page
self.request = request
def get_context_data(self, parent_context):
return {"panel": self, "page": self.page}
return {"panel": self, "page": self.page, "request": self.request}
class StatusSidePanel(BaseSidePanel):
@ -21,6 +25,54 @@ class StatusSidePanel(BaseSidePanel):
toggle_aria_label = gettext_lazy("Toggle status")
toggle_icon_name = "info-circle"
def get_context_data(self, parent_context):
context = super().get_context_data(parent_context)
user_perms = UserPagePermissionsProxy(self.request.user)
if self.page.id:
context.update(
{
"lock_url": reverse(
"wagtailadmin_pages:lock", args=(self.page.id,)
),
"unlock_url": reverse(
"wagtailadmin_pages:unlock", args=(self.page.id,)
),
"user_can_lock": user_perms.for_page(self.page).can_lock(),
"user_can_unlock": user_perms.for_page(self.page).can_unlock(),
"locale": None,
"translations": [],
}
)
else:
context.update(
{
"locale": None,
"translations": [],
}
)
if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
context.update(
{
"locale": self.page.locale,
"translations": [
{
"locale": translation.locale,
"url": reverse(
"wagtailadmin_pages:edit", args=[translation.id]
),
}
for translation in self.page.get_translations()
.only("id", "locale", "depth")
.select_related("locale")
if user_perms.for_page(translation).can_edit()
],
}
)
return context
class CommentsSidePanel(BaseSidePanel):
name = "comments"
@ -45,19 +97,11 @@ class PageSidePanels:
self.request = request
self.page = page
if page:
# Editing
self.side_panels = [
StatusSidePanel(page),
CommentsSidePanel(page),
# PreviewSidePanel(page),
]
else:
# Creating
self.side_panels = [
StatusSidePanel(page),
CommentsSidePanel(page),
]
self.side_panels = [
StatusSidePanel(page, self.request),
CommentsSidePanel(page, self.request),
# PreviewSidePanel(page),
]
def __iter__(self):
return iter(self.side_panels)

Wyświetl plik

@ -116,14 +116,6 @@
line-height: 2.5em;
}
.action-workflow-status {
font-weight: 600;
span {
font-weight: 300;
}
}
.human-readable-date {
display: inline;
}
@ -138,54 +130,55 @@
}
.form-side {
position: fixed;
// Remove once we drop support for Safari 13.
// stylelint-disable-next-line property-disallowed-list
right: -420px;
inset-inline-end: -420px;
width: 25%;
height: 100%;
max-width: 420px;
transition: right $menu-transition-duration ease,
inset-inline-end $menu-transition-duration ease;
border-inline-start: 1px solid theme('colors.grey.100');
background-color: $color-white;
z-index: 90;
@apply w-fixed
w-z-[90]
w-right-0
w-top-[100px]
sm:w-top-[50px]
sm:w-max-w-[500px]
w-w-full
sm:w-w-[70%]
xl:w-w-[30%]
w-transform
w-translate-x-full
w-h-full
w-px-5
w-py-10
md:w-p-10
w-bg-white
w-box-border
w-transition
w-duration-300
w-border-l
w-border-grey-100;
&--open {
// Remove once we drop support for Safari 13.
// stylelint-disable-next-line property-disallowed-list
right: 0;
inset-inline-end: 0;
@apply w-translate-x-0;
}
&__close-button {
color: theme('colors.primary.DEFAULT');
background-color: $color-white;
padding: 10px;
display: none;
@apply w-text-primary w-absolute w-left-2 w-top-2 hover:w-text-primary-200 w-bg-white w-p-2.5 w-hidden w-transition;
.icon {
width: 20px;
height: 20px;
@apply w-w-5 w-h-5;
}
}
&--open .form-side__close-button {
display: block;
&--open .form-side__close-button,
&--open .form-side__panel {
@apply w-block;
}
&__panel {
display: none;
padding-inline-start: 10px;
&--active {
display: block;
}
@apply w-hidden w-pl-2.5;
}
}
}
.has-messages .form-side {
@apply w-top-[170px] md:w-top-[120px];
}
// An object is the basic wrapper around any field or group of fields in the editor interface
.object {
@include nice-padding();

Wyświetl plik

@ -5,7 +5,7 @@
{% if not collection.is_root %}
<div class="privacy-indicator {% if is_public %}public{% else %}private{% endif %}">
{% trans "Privacy" %}
<button data-url="{% url 'wagtailadmin_collections:set_privacy' collection.id %}" class="status-tag primary action-set-privacy">
<button data-url="{% url 'wagtailadmin_collections:set_privacy' collection.id %}" data-action-set-privacy class="status-tag primary">
{# labels are shown/hidden in CSS according to the 'private' / 'public' class on view-permission-indicator #}
<span class="label-public icon icon-view" aria-label="{% trans 'Set collection privacy. Current status: Public' %}">
{% trans "Public" %}

Wyświetl plik

@ -1,6 +1,4 @@
<svg id="icon-expand-right" viewBox="0 0 15 13" >
<g opacity=".8" fill="currentColor">
<path d="M8.26953 2.81445c-.21484.21485-.19336.53711 0 .75196l2.59957 2.44921H4.70312c-.30078 0-.51562.23633-.51562.51563v.6875c0 .30078.21484.51563.51562.51563h6.16598L8.26953 10.2051c-.19336.2148-.19336.5371 0 .7519l.47266.4727c.21484.1933.53711.1933.73047 0l4.18944-4.18947c.1934-.19335.1934-.51562 0-.73046L9.47266 2.3418c-.19336-.19336-.51563-.19336-.73047 0l-.47266.47265Z"/>
<rect y="1" width="2" height="12" rx="1"/>
</g>
<svg id="icon-expand-right" viewBox="0 0 15 13">
<path d="M8.26953 2.81445c-.21484.21485-.19336.53711 0 .75196l2.59957 2.44921H4.70312c-.30078 0-.51562.23633-.51562.51563v.6875c0 .30078.21484.51563.51562.51563h6.16598L8.26953 10.2051c-.19336.2148-.19336.5371 0 .7519l.47266.4727c.21484.1933.53711.1933.73047 0l4.18944-4.18947c.1934-.19335.1934-.51562 0-.73046L9.47266 2.3418c-.19336-.19336-.51563-.19336-.73047 0l-.47266.47265Z"/>
<rect y="1" width="2" height="12" rx="1"/>
</svg>

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 548 B

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 489 B

Wyświetl plik

@ -0,0 +1,3 @@
<svg id="icon-globe" viewBox="0 0 512 512">
<path d="M352 256C352 278.2 350.8 299.6 348.7 320H163.3C161.2 299.6 159.1 278.2 159.1 256C159.1 233.8 161.2 212.4 163.3 192H348.7C350.8 212.4 352 233.8 352 256zM503.9 192C509.2 212.5 512 233.9 512 256C512 278.1 509.2 299.5 503.9 320H380.8C382.9 299.4 384 277.1 384 256C384 234 382.9 212.6 380.8 192H503.9zM493.4 160H376.7C366.7 96.14 346.9 42.62 321.4 8.442C399.8 29.09 463.4 85.94 493.4 160zM344.3 160H167.7C173.8 123.6 183.2 91.38 194.7 65.35C205.2 41.74 216.9 24.61 228.2 13.81C239.4 3.178 248.7 0 256 0C263.3 0 272.6 3.178 283.8 13.81C295.1 24.61 306.8 41.74 317.3 65.35C328.8 91.38 338.2 123.6 344.3 160H344.3zM18.61 160C48.59 85.94 112.2 29.09 190.6 8.442C165.1 42.62 145.3 96.14 135.3 160H18.61zM131.2 192C129.1 212.6 127.1 234 127.1 256C127.1 277.1 129.1 299.4 131.2 320H8.065C2.8 299.5 0 278.1 0 256C0 233.9 2.8 212.5 8.065 192H131.2zM194.7 446.6C183.2 420.6 173.8 388.4 167.7 352H344.3C338.2 388.4 328.8 420.6 317.3 446.6C306.8 470.3 295.1 487.4 283.8 498.2C272.6 508.8 263.3 512 255.1 512C248.7 512 239.4 508.8 228.2 498.2C216.9 487.4 205.2 470.3 194.7 446.6H194.7zM190.6 503.6C112.2 482.9 48.59 426.1 18.61 352H135.3C145.3 415.9 165.1 469.4 190.6 503.6V503.6zM321.4 503.6C346.9 469.4 366.7 415.9 376.7 352H493.4C463.4 426.1 399.8 482.9 321.4 503.6V503.6z"/>
</svg>

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 1.3 KiB

Wyświetl plik

@ -9,7 +9,7 @@
<div class="privacy-indicator {% if is_public %}public{% else %}private{% endif %}">
{% trans "Privacy" %}
{% if page_perms.can_set_view_restrictions %}
<button data-url="{% url 'wagtailadmin_pages:set_privacy' page.id %}" class="status-tag primary action-set-privacy">
<button data-url="{% url 'wagtailadmin_pages:set_privacy' page.id %}" data-action-set-privacy class="status-tag primary">
{# labels are shown/hidden in CSS according to the 'private' / 'public' class on view-permission-indicator #}
<span class="label-public icon icon-view" aria-label="{% trans 'Set page privacy. Current status: Public' %}">
{% trans "Public" %}

Wyświetl plik

@ -1,4 +0,0 @@
{% load wagtailadmin_tags %}
<button class="button{% if classname %} {{ classname }}{% endif %}" data-locking-action="{{ url }}" aria-label="{{ aria_label }}">
{% if icon_name %}{% icon name=icon_name %}{% endif %}{{ label }}
</button>

Wyświetl plik

@ -20,7 +20,7 @@
</p>
{% if page.id and page_perms.can_set_view_restrictions %}
<button type="button" data-url="{% url 'wagtailadmin_pages:set_privacy' page.id %}" class="button action-set-privacy">
<button type="button" data-url="{% url 'wagtailadmin_pages:set_privacy' page.id %}" data-action-set-privacy class="button">
{% trans "Set page privacy" %}
</button>
{% endif %}

Wyświetl plik

@ -0,0 +1,33 @@
{% load wagtailadmin_tags %}
<div class="w-py-6">
{% block content %}
<section class="w-flex w-space-x-3" aria-labelledby="status-sidebar-{{ title|cautious_slugify }}">
{% icon name=icon_name class_name='w-w-5 w-h-5 w-text-primary w-flex-shrink-0' %}
<div class="w-flex w-flex-1 w-items-start w-justify-between">
<div class="w-flex w-flex-col w-flex-1 w-pr-5">
<h3 id="status-sidebar-{{ title|cautious_slugify }}" class="w-label-1 !w-mt-0 w-mb-1">
<span class="w-sr-only">{{ screen_reader_title_prefix }}</span>
{{ title }}
</h3>
{% if help_text %}
<div class="w-help-text">{{ help_text }}</div>{% endif %}
</div>
{% block action %}
{% if hide_action %}
<a class="w-text-14 w-text-teal-200 w-no-underline hover:w-underline w-transition"
href="{{ action_url }}"
aria-describedby="status-sidebar-{{ title|cautious_slugify }}"
>
{{ action_text }}
</a>
{% endif %}
{% endblock %}
</div>
</section>
{% endblock %}
{% block bottom %}
{% endblock %}
</div>

Wyświetl plik

@ -0,0 +1,39 @@
{% extends 'wagtailadmin/pages/side_panels/includes/action_list_item.html' %}
{% load i18n wagtailadmin_tags %}
{% block content %}
{% trans 'Page Locale: ' as screen_reader_title_prefix %}
{% if translations %}
{% blocktrans trimmed with count=page.get_translations.count asvar help_text %}
Translated to {{ count }} other locales
{% endblocktrans %}
{% else %}
{% trans 'No other translations' as help_text %}
{% endif %}
{% with icon_name='globe' title=page.locale.get_display_name %}
{{ block.super }}
{% endwith %}
{% endblock %}
{% block action %}
{% if translations %}
<div data-button-with-dropdown>
{% trans 'Switch locales' as action_text %}
{% include 'wagtailadmin/pages/side_panels/includes/side_panel_button.html' with attr='data-button-with-dropdown-toggle' text=action_text %}
<div class="w-hidden w-text-white w-flex w-flex-col w-justify-start w-py-2" data-button-with-dropdown-content>
{% for translation in translations %}
<a href="{{ translation.url }}"
lang="{{ translation.locale.language_code }}"
class="w-inline-flex w-items-center w-text-white hover:w-text-white hover:w-bg-primary-200 w-py-2 w-px-4 w-font-bold w-no-underline w-transition">
{{ translation.locale.get_display_name }}
</a>
{% endfor %}
</div>
</div>
{% else %}
{# No locales to switch to #}
{% endif %}
{% endblock %}

Wyświetl plik

@ -0,0 +1,33 @@
{% extends 'wagtailadmin/pages/side_panels/includes/action_list_item.html' %}
{% load wagtailadmin_tags i18n %}
{% block content %}
{% trans 'Page locking: ' as screen_reader_title_prefix %}
{% if page.locked %}
{% trans 'Locked' as title %}
{% trans 'Only you can edit this page. Unlock it to allow others to edit' as help_text %}
{% with icon_name='lock' %}
{{ block.super }}
{% endwith %}
{% else %}
{% trans 'Unlocked' as title %}
{% trans 'Anyone can edit this page. Lock it to prevent others from editing' as help_text %}
{% with icon_name='lock-open' %}
{{ block.super }}
{% endwith %}
{% endif %}
{% endblock %}
{% block action %}
{% if user_can_unlock and page.locked %}
{% url 'wagtailadmin_pages:unlock' page.id as unlock_url %}
{% trans 'Unlock' as unlock_text %}
{% include 'wagtailadmin/pages/side_panels/includes/side_panel_button.html' with attr='data-action-lock-unlock' data_url=unlock_url text=unlock_text %}
{% endif %}
{% if user_can_lock and not page.locked %}
{% url 'wagtailadmin_pages:lock' page.id as lock_url %}
{% trans 'Lock' as lock_text %}
{% include 'wagtailadmin/pages/side_panels/includes/side_panel_button.html' with attr='data-action-lock-unlock' data_url=lock_url text=lock_text %}
{% endif %}
{% endblock %}

Wyświetl plik

@ -0,0 +1,34 @@
{% extends 'wagtailadmin/pages/side_panels/includes/action_list_item.html' %}
{% load i18n wagtailadmin_tags %}
{% block content %}
{% test_page_is_public page as is_public %}
{# The swap between public and private text is done using JS inside of privacy-switch.js when the response from the modal comes back #}
<div class="{% if not is_public %}w-hidden{% endif %}" data-privacy-sidebar-public>
{% trans 'Page visibility: ' as screen_reader_title_prefix %}
{% trans 'Visible to all' as title %}
{% trans 'Once live anyone can view' as help_text %}
{% with icon_name='view' %}
{{ block.super }}
{% endwith %}
</div>
<div class="{% if is_public %}w-hidden{% endif %}" data-privacy-sidebar-private>
{% trans 'Private' as title %}
{% trans 'Not visible to the public' as help_text %}
{% with icon_name='no-view' %}
{{ block.super }}
{% endwith %}
</div>
{% endblock %}
{% block action %}
{% page_permissions page as page_perms %}
{% if page.id and page_perms.can_set_view_restrictions %}
{% trans 'Set privacy' as set_privacy_text %}
{% url 'wagtailadmin_pages:set_privacy' page.id as privacy_url %}
{% include 'wagtailadmin/pages/side_panels/includes/side_panel_button.html' with attr='data-action-set-privacy' data_url=privacy_url text=set_privacy_text %}
{% else %}
{# Empty actions block because of lack of permissions #}
{% endif %}
{% endblock %}

Wyświetl plik

@ -0,0 +1,104 @@
{% extends 'wagtailadmin/pages/side_panels/includes/action_list_item.html' %}
{% load wagtailadmin_tags i18n %}
{% block content %}
<div class="w-space-y-3">
{% trans 'Page status: ' as screen_reader_title_prefix %}
{% with workflow_state=page.current_workflow_state draft_revision=page.get_latest_revision live_revision=page.live_revision %}
{# Live section #}
{% if page.live %}
{% trans 'Live' as title %}
{% if live_revision %}
{% timesince_last_update live_revision.created_at user_display_name=live_revision.user|user_display_name use_shorthand=True as help_text %}
{% endif %}
{% with icon_name='doc-full-inverse' %}
{% if page.has_unpublished_changes or workflow_state %}
{% with hide_action=True %}
{{ block.super }}
{% endwith %}
{% else %}
{{ block.super }}
{% endif %}
{% endwith %}
{% endif %}
{# Draft and In moderation settings #}
{% if workflow_state %}
{# In Moderation Settings #}
{% trans 'In Moderation' as title %}
{% if workflow_state.created_at %}
{% timesince_last_update workflow_state.created_at user_display_name=workflow_state.requested_by|user_display_name use_shorthand=True as help_text %}
{% endif %}
{% url 'wagtailadmin_pages:workflow_status' page.id as action_url %}
{% trans 'View details' as action_text %}
{# Icon #}
{% with icon_name='draft' hide_action=False %}
{{ block.super }}
{% endwith %}
{# Draft Settings #}
{% elif page.has_unpublished_changes %}
{% trans 'Draft' as title %}
{% if draft_revision.created_at %}
{% timesince_last_update draft_revision.created_at user_display_name=draft_revision.user|user_display_name use_shorthand=True as help_text %}
{% endif %}
{# Icon #}
{% with icon_name='draft' %}
{{ block.super }}
{% endwith %}
{% endif %}
{% endwith %}
</div>
{% endblock %}
{% block action %}
{% if workflow_state %}
{% include 'wagtailadmin/pages/side_panels/includes/side_panel_button.html' with attr='data-action-workflow-status' data_url=action_url text=action_text %}
{% else %}
{% url 'wagtailadmin_pages:history' page.id as action_url %}
{% trans 'View history' as action_text %}
{{ block.super }}
{% endif %}
{% endblock %}
{% block bottom %}
{# Workflow Status #}
{% with workflow_state=page.current_workflow_state draft_revision=page.get_latest_revision %}
{% if workflow_state %}
<div class="w-flex w-space-x-3 w-mt-3">
{% icon name='warning' class_name='w-w-4 w-h-4 w-text-info-100 w-shrink-0' %}
<div class="w-label-3 w-flex-1">
{% if workflow_state.status == 'needs_changes' %}
{% trans " Changes requested" %}
{{ workflow_state.current_task_state.finished_at|naturaltime }}
{% else %}
{% if workflow_state.status == "in_progress" %}
{% trans "Awaiting" %}
{% trans "since" as since_text %}
{% else %}
{{ workflow_state.get_status_display }}
{% endif %}
{{ workflow_state.current_task_state.task.name }}
{{ workflow_state.current_task_state.started_at|naturaltime }}
{% endif %}
</div>
</div>
{% endif %}
{# Scheduled publishing #}
{% if draft_revision and draft_revision.approved_go_live_at %}
<div class="w-flex w-space-x-3">
{% icon name='info-circle' class_name='w-w-4 w-h-4 w-text-info-100 w-shrink-0' %}
<div class="w-label-3 w-flex-1">
{% trans 'This will publish at ' %}{{ draft_revision.approved_go_live_at }}
</div>
</div>
{% endif %}
{% endwith %}
{% endblock %}

Wyświetl plik

@ -0,0 +1,9 @@
{% comment %}
Variables this template accepts:
text - The text that shows up on the button
classes - custom css classes for styling or js
data_url - A url for javascript to use
attr - custom attributes for the button
{% endcomment %}
<button type="button" class="{{ classes }} w-bg-transparent w-text-14 w-p-0 w-text-teal-200 hover:w-underline" {% if data_url %}data-url="{{ data_url }}"{% endif %} {{ attr }}>{{ text }}</button>

Wyświetl plik

@ -1 +1,33 @@
{{ page.status_string|title }}
{% load wagtailadmin_tags i18n %}
<div class="w-divide-y w-divide-grey-100">
{% if page.id %}
{% include 'wagtailadmin/pages/side_panels/includes/page_workflow_status.html' %}
{% endif %}
{% if locale %}
{% include 'wagtailadmin/pages/side_panels/includes/page_locale_status.html' %}
{% endif %}
{% if page.id %}
{% include 'wagtailadmin/pages/side_panels/includes/page_locked_status.html' %}
{% endif %}
{% include 'wagtailadmin/pages/side_panels/includes/page_privacy_status.html' %}
{# Page type / First Published information #}
<div class="w-pt-5 w-space-y-1">
<div class="w-label-3">
{% with page_model=page.content_type.model_class %}
<strong>{{ page_model.get_verbose_name }}</strong>
{% with description=page_model.get_page_description %}
{% if description %}
({{ description }})
{% endif %}
{% endwith %}
{% endwith %}
</div>
{% if page.first_published_at %}
<div class="w-help-text">{% trans 'First published' %} {{ page.first_published_at }}</div>
{% endif %}
</div>
</div>

Wyświetl plik

@ -29,7 +29,7 @@
{% include "wagtailadmin/shared/page_side_panel_toggles.html" %}
{% include "wagtailadmin/shared/page_status_tag_new.html" with page=page %}
{% include "wagtailadmin/shared/page_status_tag_new.html" with page=page_for_status %}
{% endwith %}
{% endblock %}

Wyświetl plik

@ -4,7 +4,7 @@
{# Padding left on mobile to give space for navigation toggle, #}
<div class="w-pl-[50px] w-min-h-[50px] sm:w-pl-0 sm:w-pr-2 w-w-full w-flex-1 w-overflow-x-auto w-box-border">
<div class="w-flex w-flex-1 w-justify-between sm:w-justify-start w-items-center">
<div class="w-flex w-flex-1 w-justify-between sm:w-justify-start w-items-center w-overflow-hidden">
{% block header_content %}
{% endblock %}
</div>

Wyświetl plik

@ -5,7 +5,7 @@
class="{{ nav_icon_button_classes }}"
aria-label="{{ panel.toggle_aria_label }}"
data-tippy-content="{{ panel.title }}"
data-tippy-offset="[0, 6]"
data-tippy-offset="[0,0]"
data-tippy-placement="bottom"
data-side-panel-toggle="{{ panel.name }}"
aria-expanded="false"

Wyświetl plik

@ -7,7 +7,7 @@
{% for panel in side_panels %}
<div class="form-side__panel" data-side-panel="{{ panel.name }}" hidden>
<h2 id="side-panel-{{ panel.name }}-title" aria-label="{{ panel.title }}"></h2>
<h2 id="side-panel-{{ panel.name }}-title" class="w-sr-only">{{ panel.title }}</h2>
{% component panel %}
</div>
{% endfor %}

Wyświetl plik

@ -7,9 +7,9 @@
- `classes` - String of extra css classes to pass to this component
{% endcomment %}
{% test_page_is_public page as is_public %}
{% if page.live and page.url is not None %}
{% test_page_is_public page as is_public %}
<a href="{{ page.url }}" target="_blank" rel="noreferrer"
class="
page-status-tag
@ -34,7 +34,6 @@
data-tippy-offset="[0, 13]"
aria-label="{% if is_public %}{% trans 'Visible to all. Visit the live page' %}{% else %}{% trans 'Private. Visit the live page' %}{% endif %}"
data-tippy-content="{% if is_public %}{% trans 'Visible to all' %}{% else %}{% trans 'Private' %}{% endif %}"
data-tippy-offset="[0, 20]"
data-tippy-placement="bottom">
{% with icon_classes='privacy-indicator-icon w-w-4 w-h-4 w-mr-1' %}

Wyświetl plik

@ -5,7 +5,7 @@ from urllib.parse import urljoin
from django import template
from django.conf import settings
from django.contrib.admin.utils import quote
from django.contrib.humanize.templatetags.humanize import intcomma
from django.contrib.humanize.templatetags.humanize import intcomma, naturaltime
from django.contrib.messages.constants import DEFAULT_TAGS as MESSAGE_TAGS
from django.db.models import Min, QuerySet
from django.shortcuts import resolve_url as resolve_url_func
@ -53,6 +53,7 @@ from wagtail.users.utils import get_gravatar_url
register = template.Library()
register.filter("intcomma", intcomma)
register.filter("naturaltime", naturaltime)
@register.inclusion_tag("wagtailadmin/shared/breadcrumb.html", takes_context=True)
@ -713,7 +714,9 @@ def timesince_simple(d):
@register.simple_tag
def timesince_last_update(last_update, time_prefix="", use_shorthand=True):
def timesince_last_update(
last_update, time_prefix="", user_display_name="", use_shorthand=True
):
"""
Returns:
- the time of update if last_update is today, if any prefix is supplied, the output will use it
@ -726,12 +729,11 @@ def timesince_last_update(last_update, time_prefix="", use_shorthand=True):
else:
time_str = last_update.strftime("%H:%M")
return (
time_str
if not time_prefix
else "%(prefix)s %(formatted_time)s"
% {"prefix": time_prefix, "formatted_time": time_str}
)
time_prefix = f"{time_prefix} " if time_prefix else time_prefix
by_user = f" by {user_display_name}" if user_display_name else user_display_name
return f"{time_prefix}{time_str}{by_user}"
else:
if use_shorthand:
return timesince_simple(last_update)

Wyświetl plik

@ -1050,7 +1050,6 @@ class TestPageCreation(TestCase, WagtailTestUtils):
'<button type="submit" name="action-submit" value="Submit for moderation" class="button">Submit for moderation</button>',
)
@unittest.expectedFailure # TODO: Page editor header rewrite
def test_create_sets_locale_to_parent_locale(self):
# We need to make sure the page's locale it set to the parent in the create view so that any customisations
# for that language will take effect.
@ -1452,16 +1451,13 @@ class TestLocaleSelector(TestCase, WagtailTestUtils):
)
)
self.assertContains(response, '<li class="header-meta--locale">')
self.assertContains(response, 'id="status-sidebar-english"')
add_translation_url = reverse(
"wagtailadmin_pages:add",
args=["tests", "eventpage", self.translated_events_page.id],
)
self.assertContains(
response,
f'<a href="{add_translation_url}" aria-label="French" class="u-link is-live">',
)
self.assertContains(response, f'href="{add_translation_url}"')
@override_settings(WAGTAIL_I18N_ENABLED=False)
def test_locale_selector_not_present_when_i18n_disabled(self):
@ -1472,18 +1468,14 @@ class TestLocaleSelector(TestCase, WagtailTestUtils):
)
)
self.assertNotContains(response, '<li class="header-meta--locale">')
self.assertNotContains(response, "Page Locale:")
add_translation_url = reverse(
"wagtailadmin_pages:add",
args=["tests", "eventpage", self.translated_events_page.id],
)
self.assertNotContains(
response,
f'<a href="{add_translation_url}" aria-label="French" class="u-link is-live">',
)
self.assertNotContains(response, f'href="{add_translation_url}"')
@unittest.expectedFailure # TODO: Page editor header rewrite
def test_locale_selector_not_present_without_permission_to_add(self):
# Remove user's permissions to add in the French tree
group = Group.objects.get(name="Moderators")
@ -1509,16 +1501,13 @@ class TestLocaleSelector(TestCase, WagtailTestUtils):
)
)
self.assertContains(response, '<li class="header-meta--locale">')
self.assertContains(response, 'id="status-sidebar-english"')
add_translation_url = reverse(
"wagtailadmin_pages:add",
args=["tests", "eventpage", self.translated_events_page.id],
)
self.assertNotContains(
response,
f'<a href="{add_translation_url}" aria-label="French" class="u-link is-live">',
)
self.assertNotContains(response, f'href="{add_translation_url}"')
@override_settings(WAGTAIL_I18N_ENABLED=True)
@ -1539,7 +1528,7 @@ class TestLocaleSelectorOnRootPage(TestCase, WagtailTestUtils):
)
)
self.assertContains(response, '<li class="header-meta--locale">')
self.assertContains(response, 'id="status-sidebar-english"')
add_translation_url = (
reverse(
@ -1548,10 +1537,7 @@ class TestLocaleSelectorOnRootPage(TestCase, WagtailTestUtils):
)
+ "?locale=fr"
)
self.assertContains(
response,
f'<a href="{add_translation_url}" aria-label="French" class="u-link is-live">',
)
self.assertContains(response, f'href="{add_translation_url}"')
@override_settings(WAGTAIL_I18N_ENABLED=False)
def test_locale_selector_not_present_when_i18n_disabled(self):
@ -1562,7 +1548,7 @@ class TestLocaleSelectorOnRootPage(TestCase, WagtailTestUtils):
)
)
self.assertNotContains(response, '<li class="header-meta--locale">')
self.assertNotContains(response, "Page Locale:")
add_translation_url = (
reverse(
@ -1571,10 +1557,7 @@ class TestLocaleSelectorOnRootPage(TestCase, WagtailTestUtils):
)
+ "?locale=fr"
)
self.assertNotContains(
response,
f'<a href="{add_translation_url}" aria-label="French" class="u-link is-live">',
)
self.assertNotContains(response, f'href="{add_translation_url}"')
class TestPageSubscriptionSettings(TestCase, WagtailTestUtils):

Wyświetl plik

@ -1,6 +1,5 @@
import datetime
import os
import unittest
from unittest import mock
from django.conf import settings
@ -115,10 +114,7 @@ class TestPageEdit(TestCase, WagtailTestUtils):
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response["Content-Type"], "text/html; charset=utf-8")
# TODO: Page editor header rewrite
# self.assertContains(
# response, '<li class="header-meta--status">Published</li>', html=True
# )
self.assertContains(response, 'id="status-sidebar-live"')
# Test InlinePanel labels/headings
self.assertContains(response, "<legend>Speaker lineup</legend>")
@ -162,10 +158,7 @@ class TestPageEdit(TestCase, WagtailTestUtils):
reverse("wagtailadmin_pages:edit", args=(self.unpublished_page.id,))
)
self.assertEqual(response.status_code, 200)
# TODO: Page editor header rewrite
# self.assertContains(
# response, '<li class="header-meta--status">Draft</li>', html=True
# )
self.assertContains(response, 'id="status-sidebar-draft"')
def test_edit_multipart(self):
"""
@ -967,7 +960,6 @@ class TestPageEdit(TestCase, WagtailTestUtils):
)
self.assertContains(response, "Some content with a draft edit")
@unittest.expectedFailure # TODO: Page editor header rewrite
def test_editor_page_shows_live_url_in_status_when_draft_edits_exist(self):
# If a page has draft edits (ie. page has unpublished changes)
# that affect the URL (eg. slug) we should still ensure the
@ -995,7 +987,6 @@ class TestPageEdit(TestCase, WagtailTestUtils):
self.assertContains(response, input_field_for_draft_slug, html=True)
self.assertNotContains(response, input_field_for_live_slug, html=True)
@unittest.expectedFailure # TODO: Page editor header rewrite
def test_editor_page_shows_custom_live_url_in_status_when_draft_edits_exist(self):
# When showing a live URL in the status button that differs from the draft one,
# ensure that we pick up any custom URL logic defined on the specific page model
@ -1183,11 +1174,8 @@ class TestPageEdit(TestCase, WagtailTestUtils):
self.assertEqual(response.status_code, 200)
self.assertEqual(response["Content-Type"], "text/html; charset=utf-8")
# Should still have status in the header
# TODO: Page editor header rewrite
# self.assertContains(
# response, '<li class="header-meta--status">Published</li>', html=True
# )
# Should still have status in the sidebar
self.assertContains(response, 'id="status-sidebar-live"')
# Check the edit_alias.html template was used instead
self.assertTemplateUsed(response, "wagtailadmin/pages/edit_alias.html")
@ -2267,21 +2255,17 @@ class TestLocaleSelector(TestCase, WagtailTestUtils):
)
self.user = self.login()
@unittest.expectedFailure # TODO: Page editor header rewrite
def test_locale_selector(self):
response = self.client.get(
reverse("wagtailadmin_pages:edit", args=[self.christmas_page.id])
)
self.assertContains(response, '<li class="header-meta--locale">')
self.assertContains(response, 'id="status-sidebar-english"')
edit_translation_url = reverse(
"wagtailadmin_pages:edit", args=[self.translated_christmas_page.id]
)
self.assertContains(
response,
f'<a href="{edit_translation_url}" aria-label="French" class="u-link is-live">',
)
self.assertContains(response, f'href="{edit_translation_url}"')
@override_settings(WAGTAIL_I18N_ENABLED=False)
def test_locale_selector_not_present_when_i18n_disabled(self):
@ -2289,17 +2273,13 @@ class TestLocaleSelector(TestCase, WagtailTestUtils):
reverse("wagtailadmin_pages:edit", args=[self.christmas_page.id])
)
self.assertNotContains(response, '<li class="header-meta--locale">')
self.assertNotContains(response, "Page Locale:")
edit_translation_url = reverse(
"wagtailadmin_pages:edit", args=[self.translated_christmas_page.id]
)
self.assertNotContains(
response,
f'<a href="{edit_translation_url}" aria-label="French" class="u-link is-live">',
)
self.assertNotContains(response, f'href="{edit_translation_url}"')
@unittest.expectedFailure # TODO: Page editor header rewrite
def test_locale_dropdown_not_present_without_permission_to_edit(self):
# Remove user's permissions to edit French tree
en_events_index = Page.objects.get(url_path="/home/events/")
@ -2323,15 +2303,12 @@ class TestLocaleSelector(TestCase, WagtailTestUtils):
reverse("wagtailadmin_pages:edit", args=[self.christmas_page.id])
)
self.assertContains(response, '<li class="header-meta--locale">')
self.assertContains(response, 'id="status-sidebar-english"')
edit_translation_url = reverse(
"wagtailadmin_pages:edit", args=[self.translated_christmas_page.id]
)
self.assertNotContains(
response,
f'<a href="{edit_translation_url}" aria-label="French" class="u-link is-live">',
)
self.assertNotContains(response, f'href="{edit_translation_url}"')
class TestPageSubscriptionSettings(TestCase, WagtailTestUtils):

Wyświetl plik

@ -1,5 +1,3 @@
import unittest
from django.test import TestCase
from django.urls import reverse
from django.utils.http import urlencode
@ -125,7 +123,6 @@ class TestButtonsHooks(TestCase, WagtailTestUtils):
"Another useless dropdown button in &quot;One more more button&quot; dropdown",
)
@unittest.expectedFailure # TODO: Page editor header rewrite
def test_register_page_header_buttons(self):
def page_header_buttons(page, page_perms, next_url=None):
yield wagtailadmin_widgets.Button(
@ -141,7 +138,7 @@ class TestButtonsHooks(TestCase, WagtailTestUtils):
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response, "wagtailadmin/pages/listing/_button_with_dropdown.html"
response, "wagtailadmin/pages/listing/_modern_dropdown.html"
)
self.assertContains(response, "Another useless header button")

Wyświetl plik

@ -145,6 +145,16 @@ class TestTimesinceTags(TestCase):
timesince = timesince_last_update(dt, time_prefix="my prefix")
self.assertEqual(timesince, "my prefix {}".format(formatted_time))
# Check user output
timesince = timesince_last_update(dt, user_display_name="Gary")
self.assertEqual(timesince, "{} by Gary".format(formatted_time))
# Check user and prefix output
timesince = timesince_last_update(
dt, time_prefix="my prefix", user_display_name="Gary"
)
self.assertEqual(timesince, "my prefix {} by Gary".format(formatted_time))
def test_timesince_last_update_before_today_shows_timeago(self):
dt = timezone.now() - timedelta(weeks=1, days=2)

Wyświetl plik

@ -1,6 +1,5 @@
import json
import logging
import unittest
from unittest import mock
from django.conf import settings
@ -906,7 +905,6 @@ class TestSubmitToWorkflow(TestCase, WagtailTestUtils):
self.assertEqual(task_state.task.specific, self.task_1)
self.assertEqual(task_state.status, task_state.STATUS_IN_PROGRESS)
@unittest.expectedFailure # TODO: Page editor header rewrite
def test_submit_for_approval_changes_status_in_header_meta(self):
edit_url = reverse("wagtailadmin_pages:edit", args=(self.page.id,))
@ -2358,7 +2356,6 @@ class TestWorkflowStatus(TestCase, WagtailTestUtils):
self.assertTemplateUsed(response, "wagtailadmin/workflows/workflow_status.html")
@unittest.expectedFailure # TODO: Page editor header rewrite
def test_status_through_workflow_cycle(self):
self.login(self.superuser)
response = self.client.get(self.edit_url)
@ -2366,7 +2363,7 @@ class TestWorkflowStatus(TestCase, WagtailTestUtils):
self.page.save_revision()
response = self.client.get(self.edit_url)
self.assertContains(response, "Draft saved", 1)
self.assertContains(response, 'id="status-sidebar-draft"')
self.submit()
response = self.client.get(self.edit_url)
@ -2393,17 +2390,15 @@ class TestWorkflowStatus(TestCase, WagtailTestUtils):
)
response = self.workflow_action("approve")
self.assertContains(response, "Published")
self.assertContains(response, 'id="status-sidebar-live"')
@unittest.expectedFailure # TODO: Page editor header rewrite
def test_status_after_cancel(self):
# start workflow, then cancel
self.submit()
self.submit("action-cancel-workflow")
response = self.client.get(self.edit_url)
self.assertContains(response, "Draft saved")
self.assertContains(response, 'id="status-sidebar-draft"')
@unittest.expectedFailure # TODO: Page editor header rewrite
def test_status_after_restart(self):
self.submit()
response = self.workflow_action("approve")
@ -2435,15 +2430,14 @@ class TestWorkflowStatus(TestCase, WagtailTestUtils):
response = self.client.get(workflow_status_url)
self.assertIn("good work", response.json().get("html"))
@unittest.expectedFailure # TODO: Page editor header rewrite
def test_workflow_edit_locked_message(self):
self.submit()
self.login(self.submitter)
response = self.client.get(self.edit_url)
needle = "This page is awaiting <b>'test_task_1'</b> in the <b>'test_workflow'</b> workflow. Only reviewers for this task can edit the page."
self.assertTagInHTML(needle, str(response.content), count=1)
self.assertContains(response, needle)
self.login(self.moderator)
response = self.client.get(self.edit_url)
self.assertNotInHTML(needle, str(response.content))
self.assertNotContains(response, needle)

Wyświetl plik

@ -330,7 +330,7 @@ class CreateView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View):
action_menu = PageActionMenu(
self.request, view="create", parent_page=self.parent_page
)
side_panels = PageSidePanels(self.request, None)
side_panels = PageSidePanels(self.request, self.page)
context.update(
{

Wyświetl plik

@ -970,6 +970,7 @@ def register_icons(icons):
"folder-open-inverse.svg",
"folder.svg",
"form.svg",
"globe.svg",
"grip.svg",
"group.svg",
"help.svg",