Add locale indication to the React page explorer

pull/6491/head
Karl Hobley 2020-10-22 16:24:41 +01:00
rodzic 588deb93d3
commit 123d9cef92
13 zmienionych plików z 194 dodań i 91 usunięć

Wyświetl plik

@ -15,6 +15,7 @@ export interface WagtailPageAPI {
parent: {
id: number;
} | null;
locale?: string;
};
/* eslint-disable-next-line camelcase */
admin_display_title?: string;

Wyświetl plik

@ -88,6 +88,10 @@ $menu-footer-height: 50px;
background-color: $c-explorer-bg-dark;
border-bottom: 1px solid $c-explorer-bg-dark;
color: $color-white;
}
.c-explorer__header__title {
color: inherit;
&:focus {
background-color: $c-explorer-bg-active;
@ -104,7 +108,9 @@ $menu-footer-height: 50px;
}
}
.c-explorer__header__inner {
.c-explorer__header__title__inner {
width: 70%;
float: left;
padding: 1em 0.75em;
overflow: hidden;
text-overflow: ellipsis;
@ -129,6 +135,26 @@ $menu-footer-height: 50px;
}
}
.c-explorer__header__select {
float: right;
position: relative;
padding: 1em 0;
padding-right: 2em;
text-align: right;
span {
background-color: $c-explorer-bg-active;
display: inline-block;
padding: 0.2em 0.5em;
border-radius: 0.25em;
vertical-align: middle;
line-height: 1.5;
text-transform: uppercase;
letter-spacing: 0.03rem;
font-size: 12px;
}
}
.c-explorer__placeholder {
padding: 1em;
color: $color-white;

Wyświetl plik

@ -1,7 +1,7 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { ADMIN_URLS, STRINGS } from '../../config/wagtailConfig';
import { ADMIN_URLS, STRINGS, LOCALE_NAMES } from '../../config/wagtailConfig';
import Button from '../../components/Button/Button';
import Icon from '../../components/Icon/Icon';
@ -19,21 +19,29 @@ interface ExplorerHeaderProps {
*/
const ExplorerHeader: React.FunctionComponent<ExplorerHeaderProps> = ({ page, depth, onClick }) => {
const isRoot = depth === 0;
const isSiteRoot = page.id === 0;
return (
<Button
href={page.id ? `${ADMIN_URLS.PAGES}${page.id}/` : ADMIN_URLS.PAGES}
className="c-explorer__header"
onClick={onClick}
>
<div className="c-explorer__header__inner">
<Icon
name={isRoot ? 'home' : 'arrow-left'}
className="icon--explorer-header"
/>
<span>{page.admin_display_title || STRINGS.PAGES}</span>
</div>
</Button>
<div className="c-explorer__header">
<Button
href={!isSiteRoot ? `${ADMIN_URLS.PAGES}${page.id}/` : ADMIN_URLS.PAGES}
className="c-explorer__header__title "
onClick={onClick}
>
<div className="c-explorer__header__title__inner ">
<Icon
name={isRoot ? 'home' : 'arrow-left'}
className="icon--explorer-header"
/>
<span>{page.admin_display_title || STRINGS.PAGES}</span>
</div>
</Button>
{!isSiteRoot && page.meta.locale &&
<div className="c-explorer__header__select">
<span>{(LOCALE_NAMES.get(page.meta.locale) || page.meta.locale)}</span>
</div>
}
</div>
);
};

Wyświetl plik

@ -2,7 +2,7 @@
import React from 'react';
import { ADMIN_URLS, STRINGS } from '../../config/wagtailConfig';
import { ADMIN_URLS, STRINGS, LOCALE_NAMES } from '../../config/wagtailConfig';
import Icon from '../../components/Icon/Icon';
import Button from '../../components/Button/Button';
import PublicationStatus from '../../components/PublicationStatus/PublicationStatus';
@ -26,6 +26,7 @@ const ExplorerItem: React.FunctionComponent<ExplorerItemProps> = ({ item, onClic
const { id, admin_display_title: title, meta } = item;
const hasChildren = meta.children.count > 0;
const isPublished = meta.status.live && !meta.status.has_unpublished_changes;
const localeName = meta.parent?.id === 1 && meta.locale && (LOCALE_NAMES.get(meta.locale) || meta.locale);
return (
<div className="c-explorer__item">
@ -36,11 +37,12 @@ const ExplorerItem: React.FunctionComponent<ExplorerItemProps> = ({ item, onClic
{title}
</h3>
{!isPublished ? (
{(!isPublished || localeName) &&
<span className="c-explorer__meta">
<PublicationStatus status={meta.status} />
{localeName && <span className="o-pill c-status">{localeName}</span>}
{!isPublished && <PublicationStatus status={meta.status} />}
</span>
) : null}
}
</Button>
<Button
href={`${ADMIN_URLS.PAGES}${id}/edit/`}

Wyświetl plik

@ -162,7 +162,7 @@ class ExplorerPanel extends React.Component<ExplorerPanelProps, ExplorerPanelSta
className="explorer"
paused={paused || !page || page.isFetching}
focusTrapOptions={{
initialFocus: '.c-explorer__header',
initialFocus: '.c-explorer__header__title',
onDeactivate: onClose,
}}
>

Wyświetl plik

@ -1,79 +1,91 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ExplorerHeader #depth at root 1`] = `
<Button
accessibleLabel={null}
<div
className="c-explorer__header"
dialogTrigger={false}
href="/admin/pages/"
isLoading={false}
onClick={[MockFunction]}
preventDefault={true}
target={null}
>
<div
className="c-explorer__header__inner"
<Button
accessibleLabel={null}
className="c-explorer__header__title "
dialogTrigger={false}
href="/admin/pages/undefined/"
isLoading={false}
onClick={[MockFunction]}
preventDefault={true}
target={null}
>
<Icon
className="icon--explorer-header"
name="home"
title={null}
/>
<span>
Pages
</span>
</div>
</Button>
<div
className="c-explorer__header__title__inner "
>
<Icon
className="icon--explorer-header"
name="home"
title={null}
/>
<span>
Pages
</span>
</div>
</Button>
</div>
`;
exports[`ExplorerHeader #page 1`] = `
<Button
accessibleLabel={null}
<div
className="c-explorer__header"
dialogTrigger={false}
href="/admin/pages/a/"
isLoading={false}
onClick={[MockFunction]}
preventDefault={true}
target={null}
>
<div
className="c-explorer__header__inner"
<Button
accessibleLabel={null}
className="c-explorer__header__title "
dialogTrigger={false}
href="/admin/pages/a/"
isLoading={false}
onClick={[MockFunction]}
preventDefault={true}
target={null}
>
<Icon
className="icon--explorer-header"
name="arrow-left"
title={null}
/>
<span>
test
</span>
</div>
</Button>
<div
className="c-explorer__header__title__inner "
>
<Icon
className="icon--explorer-header"
name="arrow-left"
title={null}
/>
<span>
test
</span>
</div>
</Button>
</div>
`;
exports[`ExplorerHeader basic 1`] = `
<Button
accessibleLabel={null}
<div
className="c-explorer__header"
dialogTrigger={false}
href="/admin/pages/"
isLoading={false}
onClick={[MockFunction]}
preventDefault={true}
target={null}
>
<div
className="c-explorer__header__inner"
<Button
accessibleLabel={null}
className="c-explorer__header__title "
dialogTrigger={false}
href="/admin/pages/undefined/"
isLoading={false}
onClick={[MockFunction]}
preventDefault={true}
target={null}
>
<Icon
className="icon--explorer-header"
name="arrow-left"
title={null}
/>
<span>
Pages
</span>
</div>
</Button>
<div
className="c-explorer__header__title__inner "
>
<Icon
className="icon--explorer-header"
name="arrow-left"
title={null}
/>
<span>
Pages
</span>
</div>
</Button>
</div>
`;

Wyświetl plik

@ -7,7 +7,7 @@ exports[`ExplorerPanel general rendering #isError 1`] = `
className="explorer"
focusTrapOptions={
Object {
"initialFocus": ".c-explorer__header",
"initialFocus": ".c-explorer__header__title",
"onDeactivate": [MockFunction],
}
}
@ -91,7 +91,7 @@ exports[`ExplorerPanel general rendering #isFetching 1`] = `
className="explorer"
focusTrapOptions={
Object {
"initialFocus": ".c-explorer__header",
"initialFocus": ".c-explorer__header__title",
"onDeactivate": [MockFunction],
}
}
@ -162,7 +162,7 @@ exports[`ExplorerPanel general rendering #items 1`] = `
className="explorer"
focusTrapOptions={
Object {
"initialFocus": ".c-explorer__header",
"initialFocus": ".c-explorer__header__title",
"onDeactivate": [MockFunction],
}
}
@ -255,7 +255,7 @@ exports[`ExplorerPanel general rendering no children 1`] = `
className="explorer"
focusTrapOptions={
Object {
"initialFocus": ".c-explorer__header",
"initialFocus": ".c-explorer__header__title",
"onDeactivate": [MockFunction],
}
}
@ -317,7 +317,7 @@ exports[`ExplorerPanel general rendering renders 1`] = `
className="explorer"
focusTrapOptions={
Object {
"initialFocus": ".c-explorer__header",
"initialFocus": ".c-explorer__header__title",
"onDeactivate": [MockFunction],
}
}

Wyświetl plik

@ -6,3 +6,10 @@ export const ADMIN_URLS = global.wagtailConfig.ADMIN_URLS;
export const MAX_EXPLORER_PAGES = 200;
export const IS_IE11 = !global.ActiveXObject && 'ActiveXObject' in global;
export const LOCALE_NAMES = new Map();
/* eslint-disable-next-line camelcase */
global.wagtailConfig.LOCALES.forEach(({ code, display_name }) => {
LOCALE_NAMES.set(code, display_name);
});

Wyświetl plik

@ -2,4 +2,14 @@ export {};
declare global {
interface Window { __REDUX_DEVTOOLS_EXTENSION__: any; }
interface WagtailConfig {
I18N_ENABLED: boolean;
LOCALES: {
code: string;
/* eslint-disable-next-line camelcase */
display_name: string;
}[];
}
const wagtailConfig: WagtailConfig;
}

Wyświetl plik

@ -47,6 +47,18 @@ global.wagtailConfig = {
VIEW_CHILD_PAGES_OF_PAGE: 'View child pages of \'{title}\'',
PAGE_EXPLORER: 'Page explorer',
},
WAGTAIL_I18N_ENABLED: true,
LOCALES: [
{
code: 'en',
display_name: 'English'
},
{
code: 'fr',
display_nam: 'French'
}
],
ACTIVE_LOCALE: 'en'
};
global.wagtailVersion = '1.6a1';

Wyświetl plik

@ -27,6 +27,11 @@
EXTRA_CHILDREN_PARAMETERS: '',
};
{% i18n_enabled as i18n_enabled %}
{% locales as locales %}
wagtailConfig.I18N_ENABLED = {% if i18n_enabled %}true{% else %}false{% endif %};
wagtailConfig.LOCALES = {{ locales|safe }};
wagtailConfig.STRINGS = {% js_translation_strings %};
wagtailConfig.ADMIN_URLS = {

Wyświetl plik

@ -14,6 +14,7 @@ from django.template.defaultfilters import stringfilter
from django.template.loader import render_to_string
from django.templatetags.static import static
from django.utils import timezone
from django.utils.encoding import force_str
from django.utils.html import avoid_wrapping, format_html, format_html_join
from django.utils.safestring import mark_safe
from django.utils.timesince import timesince
@ -27,7 +28,7 @@ from wagtail.admin.search import admin_search_areas
from wagtail.admin.staticfiles import versioned_static as versioned_static_func
from wagtail.core import hooks
from wagtail.core.models import (
Collection, CollectionViewRestriction, Page, PageLogEntry, PageViewRestriction,
Collection, CollectionViewRestriction, Locale, Page, PageLogEntry, PageViewRestriction,
UserPagePermissionsProxy)
from wagtail.core.utils import accepts_kwarg, camelcase_to_underscore
from wagtail.core.utils import cautious_slugify as _cautious_slugify
@ -637,3 +638,19 @@ def user_display_name(user):
# we were passed None or something else that isn't a valid user object; return
# empty string to replicate the behaviour of {{ user.get_full_name|default:user.get_username }}
return ''
@register.simple_tag
def i18n_enabled():
return getattr(settings, 'WAGTAIL_I18N_ENABLED', False)
@register.simple_tag
def locales():
return json.dumps([
{
'code': locale.language_code,
'display_name': force_str(locale.get_display_name()),
}
for locale in Locale.objects.all()
])

Wyświetl plik

@ -184,7 +184,7 @@ class TestLocaleSelectorOnList(TestCase, WagtailTestUtils):
reverse('wagtailsnippets:list', args=['tests', 'advert'])
)
self.assertNotContains(response, 'French')
self.assertNotContains(response, 'aria-label="French" class="u-link is-live">')
# Check that the add URLs don't include the locale
add_url = reverse('wagtailsnippets:add', args=['tests', 'advert'])
@ -715,6 +715,9 @@ class TestEditFileUploadSnippet(BaseTestSnippetEditView):
class TestLocaleSelectorOnEdit(BaseTestSnippetEditView):
fixtures = ['test.json']
LOCALE_SELECTOR_HTML = '<a href="javascript:void(0)" aria-label="English" class="c-dropdown__button u-btn-current">'
LOCALE_INDICATOR_HTML = '<use href="#icon-site"></use></svg>\n English'
def setUp(self):
super().setUp()
self.test_snippet = TranslatableSnippet.objects.create(text="This is a test")
@ -725,7 +728,7 @@ class TestLocaleSelectorOnEdit(BaseTestSnippetEditView):
def test_locale_selector(self):
response = self.get()
self.assertContains(response, 'English')
self.assertContains(response, self.LOCALE_SELECTOR_HTML)
switch_to_french_url = reverse('wagtailsnippets:edit', args=['snippetstests', 'translatablesnippet', quote(self.test_snippet_fr.pk)])
self.assertContains(response, f'<a href="{switch_to_french_url}" aria-label="French" class="u-link is-live">')
@ -735,7 +738,7 @@ class TestLocaleSelectorOnEdit(BaseTestSnippetEditView):
response = self.get()
self.assertContains(response, 'English')
self.assertContains(response, self.LOCALE_INDICATOR_HTML)
switch_to_french_url = reverse('wagtailsnippets:edit', args=['snippetstests', 'translatablesnippet', quote(self.test_snippet_fr.pk)])
self.assertNotContains(response, f'<a href="{switch_to_french_url}" aria-label="French" class="u-link is-live">')
@ -744,7 +747,7 @@ class TestLocaleSelectorOnEdit(BaseTestSnippetEditView):
def test_locale_selector_not_present_when_i18n_disabled(self):
response = self.get()
self.assertNotContains(response, 'English')
self.assertNotContains(response, self.LOCALE_SELECTOR_HTML)
switch_to_french_url = reverse('wagtailsnippets:edit', args=['snippetstests', 'translatablesnippet', quote(self.test_snippet_fr.pk)])
self.assertNotContains(response, f'<a href="{switch_to_french_url}" aria-label="French" class="u-link is-live">')
@ -754,7 +757,7 @@ class TestLocaleSelectorOnEdit(BaseTestSnippetEditView):
response = self.get()
self.assertNotContains(response, 'English')
self.assertNotContains(response, self.LOCALE_SELECTOR_HTML)
self.assertNotContains(response, 'aria-label="French" class="u-link is-live">')