From 123d9cef92ba31edb9c5f6d6b9775ea35f0a7135 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Thu, 22 Oct 2020 16:24:41 +0100 Subject: [PATCH] Add locale indication to the React page explorer --- client/src/api/admin.ts | 1 + client/src/components/Explorer/Explorer.scss | 28 +++- .../components/Explorer/ExplorerHeader.tsx | 36 +++-- .../src/components/Explorer/ExplorerItem.tsx | 10 +- .../src/components/Explorer/ExplorerPanel.tsx | 2 +- .../__snapshots__/ExplorerHeader.test.js.snap | 132 ++++++++++-------- .../__snapshots__/ExplorerPanel.test.js.snap | 10 +- client/src/config/wagtailConfig.js | 7 + client/src/custom.d.ts | 10 ++ client/tests/stubs.js | 12 ++ .../templates/wagtailadmin/admin_base.html | 5 + .../admin/templatetags/wagtailadmin_tags.py | 19 ++- wagtail/snippets/tests.py | 13 +- 13 files changed, 194 insertions(+), 91 deletions(-) diff --git a/client/src/api/admin.ts b/client/src/api/admin.ts index 1dc807d24d..fbca6cf434 100644 --- a/client/src/api/admin.ts +++ b/client/src/api/admin.ts @@ -15,6 +15,7 @@ export interface WagtailPageAPI { parent: { id: number; } | null; + locale?: string; }; /* eslint-disable-next-line camelcase */ admin_display_title?: string; diff --git a/client/src/components/Explorer/Explorer.scss b/client/src/components/Explorer/Explorer.scss index 890a141d9b..e32eb189db 100644 --- a/client/src/components/Explorer/Explorer.scss +++ b/client/src/components/Explorer/Explorer.scss @@ -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; diff --git a/client/src/components/Explorer/ExplorerHeader.tsx b/client/src/components/Explorer/ExplorerHeader.tsx index 68714b49ee..a3e04f20fe 100644 --- a/client/src/components/Explorer/ExplorerHeader.tsx +++ b/client/src/components/Explorer/ExplorerHeader.tsx @@ -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 = ({ page, depth, onClick }) => { const isRoot = depth === 0; + const isSiteRoot = page.id === 0; return ( - +
+ + {!isSiteRoot && page.meta.locale && +
+ {(LOCALE_NAMES.get(page.meta.locale) || page.meta.locale)} +
+ } +
); }; diff --git a/client/src/components/Explorer/ExplorerItem.tsx b/client/src/components/Explorer/ExplorerItem.tsx index e8f7fa99c4..fd4932200a 100644 --- a/client/src/components/Explorer/ExplorerItem.tsx +++ b/client/src/components/Explorer/ExplorerItem.tsx @@ -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 = ({ 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 (
@@ -36,11 +37,12 @@ const ExplorerItem: React.FunctionComponent = ({ item, onClic {title} - {!isPublished ? ( + {(!isPublished || localeName) && - + {localeName && {localeName}} + {!isPublished && } - ) : null} + } +
+ + + Pages + +
+ +
`; exports[`ExplorerHeader #page 1`] = ` - +
+ + + test + +
+ + `; exports[`ExplorerHeader basic 1`] = ` - +
+ + + Pages + +
+ + `; diff --git a/client/src/components/Explorer/__snapshots__/ExplorerPanel.test.js.snap b/client/src/components/Explorer/__snapshots__/ExplorerPanel.test.js.snap index d5ae974855..f521545826 100644 --- a/client/src/components/Explorer/__snapshots__/ExplorerPanel.test.js.snap +++ b/client/src/components/Explorer/__snapshots__/ExplorerPanel.test.js.snap @@ -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], } } diff --git a/client/src/config/wagtailConfig.js b/client/src/config/wagtailConfig.js index e0b2849550..e4087023c5 100644 --- a/client/src/config/wagtailConfig.js +++ b/client/src/config/wagtailConfig.js @@ -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); +}); diff --git a/client/src/custom.d.ts b/client/src/custom.d.ts index 9c9b2f2fd1..f5dc8c6bfd 100644 --- a/client/src/custom.d.ts +++ b/client/src/custom.d.ts @@ -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; } diff --git a/client/tests/stubs.js b/client/tests/stubs.js index 1afbccbac9..832b43a00b 100644 --- a/client/tests/stubs.js +++ b/client/tests/stubs.js @@ -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'; diff --git a/wagtail/admin/templates/wagtailadmin/admin_base.html b/wagtail/admin/templates/wagtailadmin/admin_base.html index 15e64087a1..cc0b9e19bb 100644 --- a/wagtail/admin/templates/wagtailadmin/admin_base.html +++ b/wagtail/admin/templates/wagtailadmin/admin_base.html @@ -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 = { diff --git a/wagtail/admin/templatetags/wagtailadmin_tags.py b/wagtail/admin/templatetags/wagtailadmin_tags.py index 9d2c6edbad..97d4e95bb9 100644 --- a/wagtail/admin/templatetags/wagtailadmin_tags.py +++ b/wagtail/admin/templatetags/wagtailadmin_tags.py @@ -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() + ]) diff --git a/wagtail/snippets/tests.py b/wagtail/snippets/tests.py index 5b78345bd8..3a14bad70c 100644 --- a/wagtail/snippets/tests.py +++ b/wagtail/snippets/tests.py @@ -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 = '' + LOCALE_INDICATOR_HTML = '\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'') @@ -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'') @@ -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'') @@ -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">')