kopia lustrzana https://github.com/wagtail/wagtail
Add locale indication to the React page explorer
rodzic
588deb93d3
commit
123d9cef92
|
@ -15,6 +15,7 @@ export interface WagtailPageAPI {
|
|||
parent: {
|
||||
id: number;
|
||||
} | null;
|
||||
locale?: string;
|
||||
};
|
||||
/* eslint-disable-next-line camelcase */
|
||||
admin_display_title?: string;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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/`}
|
||||
|
|
|
@ -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,
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
`;
|
||||
|
|
|
@ -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],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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()
|
||||
])
|
||||
|
|
|
@ -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">')
|
||||
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue