@plone/volto 19.0.0-alpha.7 → 19.0.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/cypress/docker/prefixed-rules.yml +26 -0
  3. package/cypress/docker/prefixed.yml +24 -0
  4. package/locales/ca/LC_MESSAGES/volto.po +1 -1
  5. package/locales/de/LC_MESSAGES/volto.po +1 -1
  6. package/locales/en/LC_MESSAGES/volto.po +1 -1
  7. package/locales/es/LC_MESSAGES/volto.po +1 -1
  8. package/locales/eu/LC_MESSAGES/volto.po +1 -1
  9. package/locales/fi/LC_MESSAGES/volto.po +1 -1
  10. package/locales/fr/LC_MESSAGES/volto.po +1 -1
  11. package/locales/hi/LC_MESSAGES/volto.po +1 -1
  12. package/locales/it/LC_MESSAGES/volto.po +1 -1
  13. package/locales/ja/LC_MESSAGES/volto.po +1 -1
  14. package/locales/nl/LC_MESSAGES/volto.po +1 -1
  15. package/locales/pt/LC_MESSAGES/volto.po +1 -1
  16. package/locales/pt_BR/LC_MESSAGES/volto.po +1 -1
  17. package/locales/ro/LC_MESSAGES/volto.po +1 -1
  18. package/locales/ru/LC_MESSAGES/volto.po +1 -1
  19. package/locales/volto.pot +1 -1
  20. package/locales/zh_CN/LC_MESSAGES/volto.po +1 -1
  21. package/package.json +10 -9
  22. package/razzle.config.js +16 -0
  23. package/src/components/manage/Blocks/Listing/ImageGallery.jsx +6 -4
  24. package/src/components/manage/Contents/ContentsBreadcrumbs.jsx +3 -3
  25. package/src/components/theme/Image/Image.jsx +11 -8
  26. package/src/components/theme/LanguageSelector/LanguageSelector.tsx +89 -0
  27. package/src/components/theme/RequestTimeout/RequestTimeout.jsx +1 -1
  28. package/src/config/index.js +12 -10
  29. package/src/config/server.js +0 -2
  30. package/src/express-middleware/devproxy.js +14 -4
  31. package/src/helpers/Api/APIResourceWithAuth.js +8 -3
  32. package/src/helpers/Api/Api.js +7 -4
  33. package/src/helpers/AsyncConnect/ssr.js +4 -1
  34. package/src/helpers/Html/Html.jsx +13 -4
  35. package/src/helpers/Sitemap/Sitemap.js +4 -4
  36. package/src/helpers/Url/Url.js +32 -2
  37. package/src/helpers/Url/Url.test.js +62 -0
  38. package/src/server.jsx +40 -8
  39. package/src/start-client.jsx +9 -2
  40. package/src/start-server.js +9 -3
  41. package/tsconfig.json +3 -4
  42. package/types/components/theme/LanguageSelector/LanguageSelector.d.ts +3 -10
  43. package/types/helpers/Url/Url.d.ts +14 -0
  44. package/types/reducers/index.d.ts +1 -0
  45. package/types/start-client.d.ts +0 -1
  46. package/package-why.json +0 -34
  47. package/src/components/theme/LanguageSelector/LanguageSelector.jsx +0 -79
  48. /package/src/components/theme/LanguageSelector/{LanguageSelector.test.jsx → LanguageSelector.test.tsx} +0 -0
@@ -6,6 +6,7 @@
6
6
  import superagent from 'superagent';
7
7
  import config from '@plone/volto/registry';
8
8
  import { addHeadersFactory } from '@plone/volto/helpers/Proxy/Proxy';
9
+ import { stripSubpathPrefix } from '@plone/volto/helpers/Url/Url';
9
10
 
10
11
  /**
11
12
  * Get a resource image/file with authenticated (if token exist) API headers
@@ -16,9 +17,9 @@ import { addHeadersFactory } from '@plone/volto/helpers/Proxy/Proxy';
16
17
  export const getAPIResourceWithAuth = (req) =>
17
18
  new Promise((resolve, reject) => {
18
19
  const { settings } = config;
19
- const APISUFIX = settings.legacyTraverse ? '' : '/++api++';
20
-
20
+ const apiSuffix = settings.legacyTraverse ? '' : '/++api++';
21
21
  let apiPath = '';
22
+
22
23
  if (settings.internalApiPath && __SERVER__) {
23
24
  apiPath = settings.internalApiPath;
24
25
  } else if (__DEVELOPMENT__ && settings.devProxyToApiPath) {
@@ -26,8 +27,12 @@ export const getAPIResourceWithAuth = (req) =>
26
27
  } else {
27
28
  apiPath = settings.apiPath;
28
29
  }
30
+
31
+ //strip subpath if any
32
+ const contentPath = stripSubpathPrefix(req.path);
33
+
29
34
  const request = superagent
30
- .get(`${apiPath}${__DEVELOPMENT__ ? '' : APISUFIX}${req.path}`)
35
+ .get(`${apiPath}${__DEVELOPMENT__ ? '' : apiSuffix}${contentPath}`)
31
36
  .maxResponseSize(settings.maxResponseSize)
32
37
  .responseType('blob');
33
38
  const authToken = req.universalCookies.get('auth_token');
@@ -7,7 +7,10 @@ import superagent from 'superagent';
7
7
  import Cookies from 'universal-cookie';
8
8
  import config from '@plone/volto/registry';
9
9
  import { addHeadersFactory } from '@plone/volto/helpers/Proxy/Proxy';
10
- import { stripQuerystring } from '@plone/volto/helpers/Url/Url';
10
+ import {
11
+ stripQuerystring,
12
+ stripSubpathPrefix,
13
+ } from '@plone/volto/helpers/Url/Url';
11
14
 
12
15
  const methods = ['get', 'post', 'put', 'patch', 'del'];
13
16
 
@@ -19,11 +22,10 @@ const methods = ['get', 'post', 'put', 'patch', 'del'];
19
22
  */
20
23
  export function formatUrl(path) {
21
24
  const { settings } = config;
22
- const APISUFIX = settings.legacyTraverse ? '' : '/++api++';
25
+ const apiSuffix = settings.legacyTraverse ? '' : '/++api++';
23
26
 
24
27
  if (path.startsWith('http://') || path.startsWith('https://')) return path;
25
28
 
26
- const adjustedPath = path[0] !== '/' ? `/${path}` : path;
27
29
  let apiPath = '';
28
30
  if (settings.internalApiPath && __SERVER__) {
29
31
  apiPath = settings.internalApiPath;
@@ -31,7 +33,8 @@ export function formatUrl(path) {
31
33
  apiPath = settings.apiPath;
32
34
  }
33
35
 
34
- return `${apiPath}${APISUFIX}${adjustedPath}`;
36
+ const contentPath = stripSubpathPrefix(path[0] !== '/' ? `/${path}` : path);
37
+ return `${apiPath}${apiSuffix}${contentPath}`;
35
38
  }
36
39
 
37
40
  /**
@@ -1,6 +1,7 @@
1
1
  import { matchRoutes } from 'react-router-config';
2
2
  import { mapSeries, isPromise } from './utils';
3
3
  import { endGlobalLoad } from '@plone/volto/actions/asyncConnect/asyncConnect';
4
+ import { stripSubpathPrefix } from '@plone/volto/helpers/Url/Url';
4
5
 
5
6
  export function filterComponents(branch) {
6
7
  return branch.reduce((result, { route, match }) => {
@@ -18,7 +19,9 @@ export function loadAsyncConnect({
18
19
  filter = () => true,
19
20
  ...rest
20
21
  }) {
21
- const layered = filterComponents(matchRoutes(routes, location.pathname));
22
+ const layered = filterComponents(
23
+ matchRoutes(routes, stripSubpathPrefix(location.pathname)),
24
+ );
22
25
 
23
26
  if (layered.length === 0) {
24
27
  return Promise.resolve();
@@ -9,6 +9,7 @@ import Helmet from '@plone/volto/helpers/Helmet/Helmet';
9
9
  import serialize from 'serialize-javascript';
10
10
  import join from 'lodash/join';
11
11
  import BodyClass from '@plone/volto/helpers/BodyClass/BodyClass';
12
+ import { addSubpathPrefix } from '@plone/volto/helpers/Url/Url';
12
13
  import { runtimeConfig } from '@plone/volto/runtime_config';
13
14
  import config from '@plone/volto/registry';
14
15
 
@@ -126,14 +127,22 @@ class Html extends Component {
126
127
  }}
127
128
  />
128
129
 
129
- <link rel="icon" href="/favicon.ico" sizes="any" />
130
- <link rel="icon" href="/icon.svg" type="image/svg+xml" />
130
+ <link
131
+ rel="icon"
132
+ href={addSubpathPrefix('/favicon.ico')}
133
+ sizes="any"
134
+ />
135
+ <link
136
+ rel="icon"
137
+ href={addSubpathPrefix('/icon.svg')}
138
+ type="image/svg+xml"
139
+ />
131
140
  <link
132
141
  rel="apple-touch-icon"
133
142
  sizes="180x180"
134
- href="/apple-touch-icon.png"
143
+ href={addSubpathPrefix('/apple-touch-icon.png')}
135
144
  />
136
- <link rel="manifest" href="/site.webmanifest" />
145
+ <link rel="manifest" href={addSubpathPrefix('/site.webmanifest')} />
137
146
  <meta name="generator" content="Plone 6 - https://plone.org" />
138
147
  <meta name="viewport" content="width=device-width, initial-scale=1" />
139
148
  <meta name="mobile-web-app-capable" content="yes" />
@@ -22,10 +22,10 @@ export const SITEMAP_BATCH_SIZE = 5000;
22
22
  export const generateSitemap = (_req, start = 0, size = undefined) =>
23
23
  new Promise((resolve) => {
24
24
  const { settings } = config;
25
- const APISUFIX = settings.legacyTraverse ? '' : '/++api++';
25
+ const apiSuffix = settings.legacyTraverse ? '' : '/++api++';
26
26
  const apiPath = settings.internalApiPath ?? settings.apiPath;
27
27
  const request = superagent.get(
28
- `${apiPath}${APISUFIX}/@search?metadata_fields=modified&b_start=${start}&b_size=${
28
+ `${apiPath}${apiSuffix}/@search?metadata_fields=modified&b_start=${start}&b_size=${
29
29
  size !== undefined ? size : 100000000
30
30
  }&use_site_search_settings=1`,
31
31
  );
@@ -64,10 +64,10 @@ export const generateSitemap = (_req, start = 0, size = undefined) =>
64
64
  export const generateSitemapIndex = (_req, gzip = false) =>
65
65
  new Promise((resolve) => {
66
66
  const { settings } = config;
67
- const APISUFIX = settings.legacyTraverse ? '' : '/++api++';
67
+ const apiSuffix = settings.legacyTraverse ? '' : '/++api++';
68
68
  const apiPath = settings.internalApiPath ?? settings.apiPath;
69
69
  const request = superagent.get(
70
- `${apiPath}${APISUFIX}/@search?metadata_fields=modified&b_size=0&use_site_search_settings=1`,
70
+ `${apiPath}${apiSuffix}/@search?metadata_fields=modified&b_size=0&use_site_search_settings=1`,
71
71
  );
72
72
  request.set('Accept', 'application/json');
73
73
  const authToken = _req.universalCookies.get('auth_token');
@@ -193,7 +193,7 @@ export function addAppURL(url) {
193
193
  */
194
194
  export function expandToBackendURL(path) {
195
195
  const { settings } = config;
196
- const APISUFIX = settings.legacyTraverse ? '' : '/++api++';
196
+ const apiSuffix = settings.legacyTraverse ? '' : '/++api++';
197
197
  let adjustedPath;
198
198
  if (path.startsWith('http://') || path.startsWith('https://')) {
199
199
  // flattenToAppURL first if we get a full URL
@@ -210,7 +210,7 @@ export function expandToBackendURL(path) {
210
210
  apiPath = settings.apiPath;
211
211
  }
212
212
 
213
- return `${apiPath}${APISUFIX}${adjustedPath}`;
213
+ return `${apiPath}${apiSuffix}${adjustedPath}`;
214
214
  }
215
215
 
216
216
  /**
@@ -258,6 +258,36 @@ export function isUrl(url) {
258
258
  return urlRegex().test(url);
259
259
  }
260
260
 
261
+ /**
262
+ * Add subpath path if set in settings
263
+ * @method addSubpathPrefix
264
+ * @param {string} src pathname
265
+ * @returns {string} prefixed subpath pathname
266
+ */
267
+ export function addSubpathPrefix(src) {
268
+ let url = src;
269
+ const { subpathPrefix } = config.settings;
270
+ if (isInternalURL(src) && subpathPrefix && !src.startsWith(subpathPrefix)) {
271
+ url = subpathPrefix + src; //add subpathPrefix to src if it's an internal url and not a static resource.
272
+ }
273
+ return url;
274
+ }
275
+
276
+ /**
277
+ * strip subpath path particulary from api calls
278
+ * @method stripSubpathPrefix
279
+ * @param {string} src pathname
280
+ * @returns {string} pathname
281
+ */
282
+ export function stripSubpathPrefix(src) {
283
+ let url = src;
284
+ const { subpathPrefix } = config.settings;
285
+ if (subpathPrefix && src.match(new RegExp(`^${subpathPrefix}(/|$)`))) {
286
+ url = src.slice(subpathPrefix.length);
287
+ }
288
+ return url;
289
+ }
290
+
261
291
  /**
262
292
  * Get field url
263
293
  * @method getFieldURL
@@ -19,6 +19,8 @@ import {
19
19
  normaliseMail,
20
20
  normalizeTelephone,
21
21
  flattenScales,
22
+ addSubpathPrefix,
23
+ stripSubpathPrefix,
22
24
  } from './Url';
23
25
 
24
26
  beforeEach(() => {
@@ -533,4 +535,64 @@ describe('Url', () => {
533
535
  });
534
536
  });
535
537
  });
538
+
539
+ describe('Subpath tests', () => {
540
+ beforeEach(() => {
541
+ settings.subpathPrefix = '/site';
542
+ });
543
+ describe('addSubpathPrefix', () => {
544
+ it('adds subpath prefix to internal URLs', () => {
545
+ expect(addSubpathPrefix('/some-page')).toBe('/site/some-page');
546
+ expect(addSubpathPrefix('/news/article')).toBe('/site/news/article');
547
+ });
548
+
549
+ it('does not add subpath prefix to URLs that already have it', () => {
550
+ expect(addSubpathPrefix('/site/some-page')).toBe('/site/some-page');
551
+ });
552
+
553
+ it('does not add subpath prefix to external URLs', () => {
554
+ expect(addSubpathPrefix('https://example.com/page')).toBe(
555
+ 'https://example.com/page',
556
+ );
557
+ });
558
+
559
+ it('handles empty subpath prefix', () => {
560
+ settings.subpathPrefix = '';
561
+ expect(addSubpathPrefix('/some-page')).toBe('/some-page');
562
+ });
563
+
564
+ it('handles undefined subpath prefix', () => {
565
+ settings.subpathPrefix = undefined;
566
+ expect(addSubpathPrefix('/some-page')).toBe('/some-page');
567
+ });
568
+ });
569
+
570
+ describe('stripSubpathPrefix', () => {
571
+ beforeEach(() => {
572
+ settings.subpathPrefix = '/site';
573
+ });
574
+ it('removes subpath prefix from URLs that have it', () => {
575
+ expect(stripSubpathPrefix('/site/some-page')).toBe('/some-page');
576
+ expect(stripSubpathPrefix('/site/news/article')).toBe('/news/article');
577
+ });
578
+
579
+ it('leaves URLs unchanged if they do not have the prefix', () => {
580
+ expect(stripSubpathPrefix('/other/some-page')).toBe('/other/some-page');
581
+ });
582
+
583
+ it('handles the case where URL is exactly the subpath prefix', () => {
584
+ expect(stripSubpathPrefix('/site')).toBe('');
585
+ });
586
+
587
+ it('handles empty subpath prefix', () => {
588
+ settings.subpathPrefix = '';
589
+ expect(stripSubpathPrefix('/some-page')).toBe('/some-page');
590
+ });
591
+
592
+ it('handles undefined subpath prefix', () => {
593
+ settings.subpathPrefix = undefined;
594
+ expect(stripSubpathPrefix('/some-page')).toBe('/some-page');
595
+ });
596
+ });
597
+ });
536
598
  });
package/src/server.jsx CHANGED
@@ -18,8 +18,9 @@ import { CookiesProvider } from 'react-cookie';
18
18
  import cookiesMiddleware from 'universal-cookie-express';
19
19
  import debug from 'debug';
20
20
 
21
- import routes from '@root/routes';
21
+ import routes from '@plone/volto/routes';
22
22
  import config from '@plone/volto/registry';
23
+ import okMiddleware from '@plone/volto/express-middleware/ok';
23
24
 
24
25
  import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
25
26
  import Html from '@plone/volto/helpers/Html/Html';
@@ -39,7 +40,10 @@ import ErrorPage from '@plone/volto/error';
39
40
  import languages from '@plone/volto/constants/Languages.cjs';
40
41
 
41
42
  import configureStore from '@plone/volto/store';
42
- import { ReduxAsyncConnect, loadOnServer } from './helpers/AsyncConnect';
43
+ import {
44
+ ReduxAsyncConnect,
45
+ loadOnServer,
46
+ } from '@plone/volto/helpers/AsyncConnect';
43
47
 
44
48
  let locales = {};
45
49
 
@@ -69,10 +73,30 @@ const server = express()
69
73
  })
70
74
  .use(cookiesMiddleware());
71
75
 
76
+ // If Volto is being served under a subpath,
77
+ // make sure the static files are available there too.
78
+ // (The static middleware loads too early to access the config.)
79
+ if (config.settings.subpathPrefix) {
80
+ server.use(
81
+ config.settings.subpathPrefix,
82
+ express.static(
83
+ process.env.BUILD_DIR
84
+ ? path.join(process.env.BUILD_DIR, 'public')
85
+ : process.env.RAZZLE_PUBLIC_DIR,
86
+ {
87
+ redirect: false, // Avoid /my-prefix from being redirected to /my-prefix/
88
+ },
89
+ ),
90
+ );
91
+ }
92
+
72
93
  const middleware = (config.settings.expressMiddleware || []).filter((m) => m);
73
94
 
74
95
  server.all('*', setupServer);
75
- if (middleware.length) server.use('/', middleware);
96
+ if (middleware.length) {
97
+ server.use(config.settings.subpathPrefix || '/', middleware);
98
+ }
99
+ server.use('/', okMiddleware());
76
100
 
77
101
  server.use(function (err, req, res, next) {
78
102
  if (err) {
@@ -160,8 +184,10 @@ function setupServer(req, res, next) {
160
184
  res.locals.detectedHost = `${
161
185
  req.headers['x-forwarded-proto'] || req.protocol
162
186
  }://${req.headers.host}`;
163
- config.settings.apiPath = res.locals.detectedHost;
164
- config.settings.publicURL = res.locals.detectedHost;
187
+ config.settings.apiPath =
188
+ res.locals.detectedHost + config.settings.subpathPrefix;
189
+ config.settings.publicURL =
190
+ res.locals.detectedHost + config.settings.subpathPrefix;
165
191
  }
166
192
 
167
193
  res.locals = {
@@ -253,7 +279,11 @@ server.get('/*', (req, res) => {
253
279
  <ChunkExtractorManager extractor={extractor}>
254
280
  <CookiesProvider cookies={req.universalCookies}>
255
281
  <Provider store={store} onError={reactIntlErrorHandler}>
256
- <StaticRouter context={context} location={req.url}>
282
+ <StaticRouter
283
+ context={context}
284
+ location={req.url}
285
+ basename={config.settings.subpathPrefix}
286
+ >
257
287
  <ReduxAsyncConnect routes={routes} helpers={api} />
258
288
  </StaticRouter>
259
289
  </Provider>
@@ -290,8 +320,8 @@ server.get('/*', (req, res) => {
290
320
  markup={markup}
291
321
  store={store}
292
322
  criticalCss={readCriticalCss(req)}
293
- apiPath={res.locals.detectedHost || config.settings.apiPath}
294
- publicURL={res.locals.detectedHost || config.settings.publicURL}
323
+ apiPath={config.settings.apiPath}
324
+ publicURL={config.settings.publicURL}
295
325
  />,
296
326
  )}
297
327
  `,
@@ -334,6 +364,8 @@ export const defaultReadCriticalCss = () => {
334
364
 
335
365
  // Exposed for the console bootstrap info messages
336
366
  server.apiPath = config.settings.apiPath;
367
+ server.internalApiPath = config.settings.internalApiPath;
368
+ server.subpathPrefix = config.settings.subpathPrefix;
337
369
  server.devProxyToApiPath = config.settings.devProxyToApiPath;
338
370
  server.proxyRewriteTarget = config.settings.proxyRewriteTarget;
339
371
  server.publicURL = config.settings.publicURL;
@@ -18,8 +18,6 @@ import Api from '@plone/volto/helpers/Api/Api';
18
18
  import { persistAuthToken } from '@plone/volto/helpers/AuthToken/AuthToken';
19
19
  import ScrollToTop from '@plone/volto/helpers/ScrollToTop/ScrollToTop';
20
20
 
21
- export const history = createBrowserHistory();
22
-
23
21
  function reactIntlErrorHandler(error) {
24
22
  debug('i18n')(error);
25
23
  }
@@ -27,6 +25,15 @@ function reactIntlErrorHandler(error) {
27
25
  export default function client() {
28
26
  const api = new Api();
29
27
 
28
+ if (window.env.RAZZLE_SUBPATH_PREFIX) {
29
+ config.settings.subpathPrefix = window.env.RAZZLE_SUBPATH_PREFIX;
30
+ }
31
+ const history = createBrowserHistory({
32
+ basename: config.settings.subpathPrefix
33
+ ? config.settings.subpathPrefix
34
+ : '/',
35
+ });
36
+
30
37
  const store = configureStore(window.__data, history, api);
31
38
  persistAuthToken(store);
32
39
 
@@ -18,14 +18,20 @@ export default function server() {
18
18
 
19
19
  server
20
20
  .listen(port, bind_address, () => {
21
- console.log(`API server (API_PATH) is set to: ${app.apiPath}`);
22
-
23
- if (app.devProxyToApiPath)
21
+ console.log(
22
+ `The Volto server will make API requests to: ${app.internalApiPath || app.apiPath}/++api++`,
23
+ );
24
+ console.log(
25
+ `The Volto client will make API requests to: ${app.apiPath}/++api++`,
26
+ );
27
+
28
+ if (app.devProxyToApiPath) {
24
29
  console.log(
25
30
  `Proxying API requests from ${app.publicURL}/++api++ to ${
26
31
  app.devProxyToApiPath
27
32
  }${app.proxyRewriteTarget || ''}`,
28
33
  );
34
+ }
29
35
  console.log(`🎭 Volto started at ${bind_address}:${port} 🚀`);
30
36
 
31
37
  if (!process.env.RAZZLE_PUBLIC_URL)
package/tsconfig.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ESNext",
4
- "lib": ["DOM", "DOM.Iterable", "ESNext"],
3
+ "target": "es2022",
4
+ "lib": ["es2022", "dom", "dom.iterable"],
5
5
  "types": ["vitest/globals", "jest"],
6
- "module": "commonjs",
6
+ "module": "preserve",
7
7
  "allowJs": true,
8
8
  "skipLibCheck": true,
9
9
  "esModuleInterop": true,
@@ -11,7 +11,6 @@
11
11
  "strict": true,
12
12
  "forceConsistentCasingInFileNames": true,
13
13
  "strictPropertyInitialization": false,
14
- "moduleResolution": "Node",
15
14
  "resolveJsonModule": true,
16
15
  "isolatedModules": true,
17
16
  "noEmit": true,
@@ -1,11 +1,4 @@
1
+ declare const LanguageSelector: ({ onClickAction, }: {
2
+ onClickAction?: () => void;
3
+ }) => import("react/jsx-runtime").JSX.Element;
1
4
  export default LanguageSelector;
2
- declare function LanguageSelector(props: any): import("react/jsx-runtime").JSX.Element;
3
- declare namespace LanguageSelector {
4
- namespace propTypes {
5
- let onClickAction: any;
6
- }
7
- namespace defaultProps {
8
- export function onClickAction_1(): void;
9
- export { onClickAction_1 as onClickAction };
10
- }
11
- }
@@ -78,6 +78,20 @@ export function isInternalURL(url: string): boolean;
78
78
  * @returns {boolean} True if is a valid url
79
79
  */
80
80
  export function isUrl(url: string): boolean;
81
+ /**
82
+ * Add subpath path if set in settings
83
+ * @method addSubpathPrefix
84
+ * @param {string} src pathname
85
+ * @returns {string} prefixed subpath pathname
86
+ */
87
+ export function addSubpathPrefix(src: string): string;
88
+ /**
89
+ * strip subpath path particulary from api calls
90
+ * @method stripSubpathPrefix
91
+ * @param {string} src pathname
92
+ * @returns {string} pathname
93
+ */
94
+ export function stripSubpathPrefix(src: string): string;
81
95
  /**
82
96
  * Normalize URL, adds protocol (if required eg. user has not entered the protocol)
83
97
  * @method normalizeUrl
@@ -50,6 +50,7 @@ declare namespace reducers {
50
50
  export { site };
51
51
  export { navroot };
52
52
  }
53
+ import { intlReducer } from 'react-intl-redux';
53
54
  import reduxAsyncConnect from './asyncConnect/asyncConnect';
54
55
  import actions from '@plone/volto/reducers/actions/actions';
55
56
  import addons from '@plone/volto/reducers/addons/addons';
@@ -1,2 +1 @@
1
1
  export default function client(): void;
2
- export const history: import("history").History<unknown>;
package/package-why.json DELETED
@@ -1,34 +0,0 @@
1
- {
2
- "scripts": {
3
- "postinstall": "Description for `npm run postinstall` command",
4
- "analyze": "Description for `npm run analyze` command",
5
- "start": "Description for `npm run start` command",
6
- "build": "Description for `npm run build` command",
7
- "build:clean": "Description for `npm run build:clean` command",
8
- "dist:clean": "Description for `npm run dist:clean` command",
9
- "build:dist": "Description for `npm run build:dist` command",
10
- "test": "Description for `npm run test` command",
11
- "test:ci": "Description for `npm run test:ci` command",
12
- "test:husky": "Description for `npm run test:husky` command",
13
- "test:debug": "Description for `npm run test:debug` command",
14
- "start:prod": "Description for `npm run start:prod` command",
15
- "start:dist": "Description for `npm run start:dist` command",
16
- "prettier": "Description for `npm run prettier` command",
17
- "prettier:fix": "Description for `npm run prettier:fix` command",
18
- "stylelint": "Description for `npm run stylelint` command",
19
- "stylelint:overrides": "Description for `npm run stylelint:overrides` command",
20
- "stylelint:fix": "Description for `npm run stylelint:fix` command",
21
- "lint": "Description for `npm run lint` command",
22
- "lint:fix": "Description for `npm run lint:fix` command",
23
- "i18n": "Description for `npm run i18n` command",
24
- "i18n:ci": "Description for `npm run i18n:ci` command",
25
- "stylelint:patches": "Description for `npm run stylelint:patches` command",
26
- "patches": "Description for `npm run patches` command",
27
- "dry-release": "Description for `npm run dry-release` command",
28
- "release": "Description for `npm run release` command",
29
- "release-major-alpha": "Description for `npm run release-major-alpha` command",
30
- "release-alpha": "Description for `npm run release-alpha` command",
31
- "storybook": "Description for `npm run storybook` command",
32
- "build-storybook": "Description for `npm run build-storybook` command"
33
- }
34
- }
@@ -1,79 +0,0 @@
1
- /**
2
- * Language selector component.
3
- * @module components/LanguageSelector/LanguageSelector
4
- */
5
-
6
- import React from 'react';
7
- import PropTypes from 'prop-types';
8
- import { Link } from 'react-router-dom';
9
-
10
- import { useSelector } from 'react-redux';
11
- import cx from 'classnames';
12
- import find from 'lodash/find';
13
- import map from 'lodash/map';
14
-
15
- import Helmet from '@plone/volto/helpers/Helmet/Helmet';
16
- import langmap from '@plone/volto/helpers/LanguageMap/LanguageMap';
17
- import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
18
- import { toReactIntlLang } from '@plone/volto/helpers/Utils/Utils';
19
-
20
- import { defineMessages, useIntl } from 'react-intl';
21
-
22
- const messages = defineMessages({
23
- switchLanguageTo: {
24
- id: 'Switch to',
25
- defaultMessage: 'Switch to',
26
- },
27
- });
28
-
29
- const LanguageSelector = (props) => {
30
- const intl = useIntl();
31
- const currentLang = useSelector((state) => state.intl.locale);
32
- const translations = useSelector(
33
- (state) => state.content.data?.['@components']?.translations?.items,
34
- );
35
- const isMultilingual = useSelector(
36
- (state) => state.site.data.features?.multilingual,
37
- );
38
- const availableLanguages = useSelector(
39
- (state) => state.site.data?.['plone.available_languages'],
40
- );
41
-
42
- return isMultilingual ? (
43
- <div className="language-selector">
44
- {map(availableLanguages, (lang) => {
45
- const translation = find(translations, { language: lang });
46
- return (
47
- <Link
48
- aria-label={`${intl.formatMessage(
49
- messages.switchLanguageTo,
50
- )} ${langmap[lang].nativeName.toLowerCase()}`}
51
- className={cx({ selected: toReactIntlLang(lang) === currentLang })}
52
- to={translation ? flattenToAppURL(translation['@id']) : `/${lang}`}
53
- title={langmap[lang].nativeName}
54
- onClick={() => {
55
- props.onClickAction();
56
- }}
57
- key={`language-selector-${lang}`}
58
- >
59
- {langmap[lang].nativeName}
60
- </Link>
61
- );
62
- })}
63
- </div>
64
- ) : (
65
- <Helmet>
66
- <html lang={toReactIntlLang(currentLang)} />
67
- </Helmet>
68
- );
69
- };
70
-
71
- LanguageSelector.propTypes = {
72
- onClickAction: PropTypes.func,
73
- };
74
-
75
- LanguageSelector.defaultProps = {
76
- onClickAction: () => {},
77
- };
78
-
79
- export default LanguageSelector;