@plone/volto 17.4.0 → 17.6.0

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 (51) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +0 -1
  3. package/locales/ca/LC_MESSAGES/volto.po +15 -10
  4. package/locales/ca.json +1 -1
  5. package/locales/de/LC_MESSAGES/volto.po +15 -10
  6. package/locales/de.json +1 -1
  7. package/locales/en/LC_MESSAGES/volto.po +15 -10
  8. package/locales/en.json +1 -1
  9. package/locales/es/LC_MESSAGES/volto.po +15 -10
  10. package/locales/es.json +1 -1
  11. package/locales/eu/LC_MESSAGES/volto.po +15 -10
  12. package/locales/eu.json +1 -1
  13. package/locales/fi/LC_MESSAGES/volto.po +15 -10
  14. package/locales/fi.json +1 -1
  15. package/locales/fr/LC_MESSAGES/volto.po +15 -10
  16. package/locales/fr.json +1 -1
  17. package/locales/it/LC_MESSAGES/volto.po +15 -10
  18. package/locales/it.json +1 -1
  19. package/locales/ja/LC_MESSAGES/volto.po +15 -10
  20. package/locales/ja.json +1 -1
  21. package/locales/nl/LC_MESSAGES/volto.po +15 -10
  22. package/locales/nl.json +1 -1
  23. package/locales/pt/LC_MESSAGES/volto.po +15 -10
  24. package/locales/pt.json +1 -1
  25. package/locales/pt_BR/LC_MESSAGES/volto.po +15 -10
  26. package/locales/pt_BR.json +1 -1
  27. package/locales/ro/LC_MESSAGES/volto.po +15 -10
  28. package/locales/ro.json +1 -1
  29. package/locales/volto.pot +16 -11
  30. package/locales/zh_CN/LC_MESSAGES/volto.po +15 -10
  31. package/locales/zh_CN.json +1 -1
  32. package/package.json +4 -4
  33. package/packages/volto-slate/package.json +5 -5
  34. package/packages/volto-slate/src/blocks/Text/keyboard/joinBlocks.js +6 -3
  35. package/packages/volto-slate/src/editor/SlateEditor.jsx +3 -1
  36. package/packages/volto-slate/src/editor/plugins/Link/extensions.js +25 -20
  37. package/packages/volto-slate/src/slate-react.js +26 -0
  38. package/packages/volto-slate/src/utils/selection.js +7 -4
  39. package/razzle.config.js +3 -0
  40. package/src/components/manage/BlockChooser/BlockChooserSearch.jsx +5 -2
  41. package/src/components/manage/Blocks/Description/Edit.jsx +1 -1
  42. package/src/components/manage/Blocks/Title/Edit.jsx +1 -1
  43. package/src/components/manage/Contents/Contents.jsx +240 -164
  44. package/src/components/manage/TextLineEdit/TextLineEdit.jsx +1 -1
  45. package/src/components/theme/ContentMetadataTags/ContentMetadataTags.jsx +39 -46
  46. package/src/components/theme/Sitemap/Sitemap.jsx +40 -50
  47. package/src/helpers/Api/APIResourceWithAuth.js +1 -1
  48. package/theme/themes/pastanaga/elements/input.overrides +13 -2
  49. package/theme/themes/pastanaga/elements/input.variables +0 -3
  50. package/theme/themes/pastanaga/extras/blocks.less +17 -8
  51. package/theme/themes/pastanaga/extras/contents.less +1 -1
@@ -411,7 +411,8 @@ class Contents extends Component {
411
411
  showWorkflow: false,
412
412
  itemsToDelete: [],
413
413
  containedItemsToDelete: [],
414
- brokenReferences: [],
414
+ brokenReferences: 0,
415
+ breaches: [],
415
416
  showAllItemsToDelete: true,
416
417
  items: this.props.items,
417
418
  filter: '',
@@ -450,20 +451,37 @@ class Contents extends Component {
450
451
  const linkintegrityInfo = await this.props.linkIntegrityCheck(
451
452
  map(this.state.itemsToDelete, (item) => this.getFieldById(item, 'UID')),
452
453
  );
453
- let containedItems = 0;
454
- let brokenReferencesCount = 0;
454
+ const containedItems = linkintegrityInfo
455
+ .map((result) => result.items_total ?? 0)
456
+ .reduce((acc, value) => acc + value, 0);
457
+ const breaches = linkintegrityInfo.flatMap((result) =>
458
+ result.breaches.map((source) => ({
459
+ source: source,
460
+ target: result,
461
+ })),
462
+ );
463
+ const source_by_uid = breaches.reduce(
464
+ (acc, value) => acc.set(value.source.uid, value.source),
465
+ new Map(),
466
+ );
467
+ const by_source = breaches.reduce((acc, value) => {
468
+ if (acc.get(value.source.uid) === undefined) {
469
+ acc.set(value.source.uid, new Set());
470
+ }
471
+ acc.get(value.source.uid).add(value.target);
472
+ return acc;
473
+ }, new Map());
455
474
 
456
- linkintegrityInfo.forEach((item) => {
457
- containedItems += item.items_total ?? 0;
458
- brokenReferencesCount += item.breaches.length;
459
- });
460
475
  this.setState({
461
476
  containedItemsToDelete: containedItems,
462
- brokenReferences: brokenReferencesCount,
463
- brokenLinksList:
464
- linkintegrityInfo.length === 1
465
- ? linkintegrityInfo[0]['@id'] + '/links-to-item'
466
- : null,
477
+ brokenReferences: by_source.size,
478
+ linksAndReferencesViewLink: linkintegrityInfo.length
479
+ ? linkintegrityInfo[0]['@id'] + '/links-to-item'
480
+ : null,
481
+ breaches: Array.from(by_source, (entry) => ({
482
+ source: source_by_uid.get(entry[0]),
483
+ targets: Array.from(entry[1]),
484
+ })),
467
485
  showAllItemsToDelete:
468
486
  this.state.itemsToDelete.length < this.deleteItemsToShowThreshold,
469
487
  });
@@ -1188,7 +1206,11 @@ class Contents extends Component {
1188
1206
  <article id="content">
1189
1207
  <Confirm
1190
1208
  open={this.state.showDelete}
1191
- confirmButton="Delete"
1209
+ confirmButton={
1210
+ this.state.brokenReferences === 0
1211
+ ? 'Delete'
1212
+ : 'Delete item and break links'
1213
+ }
1192
1214
  header={
1193
1215
  this.state.itemsToDelete.length === 1
1194
1216
  ? this.props.intl.formatMessage(
@@ -1200,114 +1222,14 @@ class Contents extends Component {
1200
1222
  }
1201
1223
  content={
1202
1224
  <div className="content">
1203
- <p>
1204
- {this.state.itemsToDelete.length > 1 ? (
1205
- this.state.containedItemsToDelete > 0 ? (
1206
- <>
1207
- <FormattedMessage
1208
- id="Some items are also a folder.
1209
- By deleting them you will delete {containedItemsToDelete} {variation} inside the folders."
1210
- defaultMessage="Some items are also a folder.
1211
- By deleting them you will delete {containedItemsToDelete} {variation} inside the folders."
1212
- values={{
1213
- containedItemsToDelete: (
1214
- <span>
1215
- {this.state.containedItemsToDelete}
1216
- </span>
1217
- ),
1218
- variation: (
1219
- <span>
1220
- {this.state.containedItemsToDelete ===
1221
- 1 ? (
1222
- <FormattedMessage
1223
- id="item"
1224
- defaultMessage="item"
1225
- />
1226
- ) : (
1227
- <FormattedMessage
1228
- id="items"
1229
- defaultMessage="items"
1230
- />
1231
- )}
1232
- </span>
1233
- ),
1234
- }}
1235
- />
1236
- {this.state.brokenReferences > 0 && (
1237
- <>
1238
- <br />
1239
- <FormattedMessage
1240
- id="Some items are referenced by other contents. By deleting them {brokenReferences} {variation} will be broken."
1241
- defaultMessage="Some items are referenced by other contents. By deleting them {brokenReferences} {variation} will be broken."
1242
- values={{
1243
- brokenReferences: (
1244
- <span>
1245
- {this.state.brokenReferences}
1246
- </span>
1247
- ),
1248
- variation: (
1249
- <span>
1250
- {this.state.brokenReferences ===
1251
- 1 ? (
1252
- <FormattedMessage
1253
- id="reference"
1254
- defaultMessage="reference"
1255
- />
1256
- ) : (
1257
- <FormattedMessage
1258
- id="references"
1259
- defaultMessage="references"
1260
- />
1261
- )}
1262
- </span>
1263
- ),
1264
- }}
1265
- />
1266
- </>
1267
- )}
1268
- </>
1269
- ) : (
1270
- <>
1271
- {this.state.brokenReferences > 0 && (
1272
- <>
1273
- <FormattedMessage
1274
- id="Some items are referenced by other contents. By deleting them {brokenReferences} {variation} will be broken."
1275
- defaultMessage="Some items are referenced by other contents. By deleting them {brokenReferences} {variation} will be broken."
1276
- values={{
1277
- brokenReferences: (
1278
- <span>
1279
- {this.state.brokenReferences}
1280
- </span>
1281
- ),
1282
- variation: (
1283
- <span>
1284
- {this.state.brokenReferences ===
1285
- 1 ? (
1286
- <FormattedMessage
1287
- id="reference"
1288
- defaultMessage="reference"
1289
- />
1290
- ) : (
1291
- <FormattedMessage
1292
- id="references"
1293
- defaultMessage="references"
1294
- />
1295
- )}
1296
- </span>
1297
- ),
1298
- }}
1299
- />
1300
- </>
1301
- )}
1302
- </>
1303
- )
1304
- ) : this.state.containedItemsToDelete > 0 ? (
1225
+ {this.state.itemsToDelete.length > 1 ? (
1226
+ this.state.containedItemsToDelete > 0 ? (
1305
1227
  <>
1306
1228
  <FormattedMessage
1307
- id="This item is also a folder.
1308
- By deleting it you will delete {containedItemsToDelete} {variation} inside the folder."
1309
- defaultMessage="This item is also a folder.
1310
- By deleting it you will delete {containedItemsToDelete} {variation} inside the folder."
1229
+ id="Some items are also a folder.
1230
+ By deleting them you will delete {containedItemsToDelete} {variation} inside the folders."
1231
+ defaultMessage="Some items are also a folder.
1232
+ By deleting them you will delete {containedItemsToDelete} {variation} inside the folders."
1311
1233
  values={{
1312
1234
  containedItemsToDelete: (
1313
1235
  <span>
@@ -1336,8 +1258,41 @@ class Contents extends Component {
1336
1258
  <>
1337
1259
  <br />
1338
1260
  <FormattedMessage
1339
- id="This item is referenced by other items. By deleting it {brokenReferences} {variation} will be broken."
1340
- defaultMessage="This item is referenced by other items. By deleting it {brokenReferences} {variation} will be broken."
1261
+ id="Some items are referenced by other contents. By deleting them {brokenReferences} {variation} will be broken."
1262
+ defaultMessage="Some items are referenced by other contents. By deleting them {brokenReferences} {variation} will be broken."
1263
+ values={{
1264
+ brokenReferences: (
1265
+ <span>
1266
+ {this.state.brokenReferences}
1267
+ </span>
1268
+ ),
1269
+ variation: (
1270
+ <span>
1271
+ {this.state.brokenReferences === 1 ? (
1272
+ <FormattedMessage
1273
+ id="reference"
1274
+ defaultMessage="reference"
1275
+ />
1276
+ ) : (
1277
+ <FormattedMessage
1278
+ id="references"
1279
+ defaultMessage="references"
1280
+ />
1281
+ )}
1282
+ </span>
1283
+ ),
1284
+ }}
1285
+ />
1286
+ </>
1287
+ )}
1288
+ </>
1289
+ ) : (
1290
+ <>
1291
+ {this.state.brokenReferences > 0 && (
1292
+ <>
1293
+ <FormattedMessage
1294
+ id="Some items are referenced by other contents. By deleting them {brokenReferences} {variation} will be broken."
1295
+ defaultMessage="Some items are referenced by other contents. By deleting them {brokenReferences} {variation} will be broken."
1341
1296
  values={{
1342
1297
  brokenReferences: (
1343
1298
  <span>
@@ -1361,62 +1316,183 @@ class Contents extends Component {
1361
1316
  ),
1362
1317
  }}
1363
1318
  />
1364
- <div className="broken-links-list-link-wrapper">
1319
+ </>
1320
+ )}
1321
+ </>
1322
+ )
1323
+ ) : this.state.containedItemsToDelete > 0 ? (
1324
+ <>
1325
+ <FormattedMessage
1326
+ id="This item is also a folder.
1327
+ By deleting it you will delete {containedItemsToDelete} {variation} inside the folder."
1328
+ defaultMessage="This item is also a folder.
1329
+ By deleting it you will delete {containedItemsToDelete} {variation} inside the folder."
1330
+ values={{
1331
+ containedItemsToDelete: (
1332
+ <span>
1333
+ {this.state.containedItemsToDelete}
1334
+ </span>
1335
+ ),
1336
+ variation: (
1337
+ <span>
1338
+ {this.state.containedItemsToDelete === 1 ? (
1339
+ <FormattedMessage
1340
+ id="item"
1341
+ defaultMessage="item"
1342
+ />
1343
+ ) : (
1344
+ <FormattedMessage
1345
+ id="items"
1346
+ defaultMessage="items"
1347
+ />
1348
+ )}
1349
+ </span>
1350
+ ),
1351
+ }}
1352
+ />
1353
+ {this.state.brokenReferences > 0 && (
1354
+ <>
1355
+ <br />
1356
+ <FormattedMessage
1357
+ id="Deleting this item breaks {brokenReferences} {variation}."
1358
+ defaultMessage="Deleting this item breaks {brokenReferences} {variation}."
1359
+ values={{
1360
+ brokenReferences: (
1361
+ <span>{this.state.brokenReferences}</span>
1362
+ ),
1363
+ variation: (
1364
+ <span>
1365
+ {this.state.brokenReferences === 1 ? (
1366
+ <FormattedMessage
1367
+ id="reference"
1368
+ defaultMessage="reference"
1369
+ />
1370
+ ) : (
1371
+ <FormattedMessage
1372
+ id="references"
1373
+ defaultMessage="references"
1374
+ />
1375
+ )}
1376
+ </span>
1377
+ ),
1378
+ }}
1379
+ />
1380
+ <div className="broken-links-list">
1381
+ <FormattedMessage id="These items will have broken links" />
1382
+ <ul>
1383
+ {this.state.breaches.map((breach) => (
1384
+ <li key={breach.source['@id']}>
1385
+ <Link
1386
+ to={flattenToAppURL(
1387
+ breach.source['@id'],
1388
+ )}
1389
+ title="Navigate to this item"
1390
+ >
1391
+ {breach.source.title}
1392
+ </Link>{' '}
1393
+ refers to{' '}
1394
+ {breach.targets
1395
+ .map((target) => (
1396
+ <Link
1397
+ to={flattenToAppURL(
1398
+ target['@id'],
1399
+ )}
1400
+ title="Navigate to this item"
1401
+ >
1402
+ {target.title}
1403
+ </Link>
1404
+ ))
1405
+ .reduce((result, item) => (
1406
+ <>
1407
+ {result}, {item}
1408
+ </>
1409
+ ))}
1410
+ </li>
1411
+ ))}
1412
+ </ul>
1413
+ {this.state.linksAndReferencesViewLink && (
1365
1414
  <Link
1366
1415
  to={flattenToAppURL(
1367
- this.state.brokenLinksList,
1416
+ this.state.linksAndReferencesViewLink,
1368
1417
  )}
1369
1418
  >
1370
1419
  <FormattedMessage
1371
- id="View broken links list"
1372
- defaultMessage="View broken links list"
1420
+ id="View links and references to this item"
1421
+ defaultMessage="View links and references to this item"
1373
1422
  />
1374
1423
  </Link>
1375
- </div>
1376
- </>
1377
- )}
1378
- </>
1379
- ) : this.state.brokenReferences > 0 ? (
1380
- <>
1381
- <FormattedMessage
1382
- id="This item is referenced by other items. By deleting it {brokenReferences} {variation} will be broken."
1383
- defaultMessage="This item is referenced by other items. By deleting it {brokenReferences} {variation} will be broken."
1384
- values={{
1385
- brokenReferences: (
1386
- <span>{this.state.brokenReferences}</span>
1387
- ),
1388
- variation: (
1389
- <span>
1390
- {this.state.brokenReferences === 1 ? (
1391
- <FormattedMessage
1392
- id="reference"
1393
- defaultMessage="reference"
1394
- />
1395
- ) : (
1396
- <FormattedMessage
1397
- id="references"
1398
- defaultMessage="references"
1399
- />
1400
- )}
1401
- </span>
1402
- ),
1403
- }}
1404
- />
1405
- <div className="broken-links-list-link-wrapper">
1424
+ )}
1425
+ </div>
1426
+ </>
1427
+ )}
1428
+ </>
1429
+ ) : this.state.brokenReferences > 0 ? (
1430
+ <>
1431
+ <FormattedMessage
1432
+ id="Deleting this item breaks {brokenReferences} {variation}."
1433
+ defaultMessage="Deleting this item breaks {brokenReferences} {variation}."
1434
+ values={{
1435
+ brokenReferences: (
1436
+ <span>{this.state.brokenReferences}</span>
1437
+ ),
1438
+ variation: (
1439
+ <span>
1440
+ {this.state.brokenReferences === 1 ? (
1441
+ <FormattedMessage
1442
+ id="reference"
1443
+ defaultMessage="reference"
1444
+ />
1445
+ ) : (
1446
+ <FormattedMessage id="references" />
1447
+ )}
1448
+ </span>
1449
+ ),
1450
+ }}
1451
+ />
1452
+ <div className="broken-links-list">
1453
+ <FormattedMessage id="These items will have broken links" />
1454
+ <ul>
1455
+ {this.state.breaches.map((breach) => (
1456
+ <li key={breach.source['@id']}>
1457
+ <Link
1458
+ to={flattenToAppURL(breach.source['@id'])}
1459
+ title="Navigate to this item"
1460
+ >
1461
+ {breach.source.title}
1462
+ </Link>{' '}
1463
+ refers to{' '}
1464
+ {breach.targets
1465
+ .map((target) => (
1466
+ <Link
1467
+ to={flattenToAppURL(target['@id'])}
1468
+ title="Navigate to this item"
1469
+ >
1470
+ {target.title}
1471
+ </Link>
1472
+ ))
1473
+ .reduce((result, item) => (
1474
+ <>
1475
+ {result}, {item}
1476
+ </>
1477
+ ))}
1478
+ </li>
1479
+ ))}
1480
+ </ul>
1481
+ {this.state.linksAndReferencesViewLink && (
1406
1482
  <Link
1407
1483
  to={flattenToAppURL(
1408
- this.state.brokenLinksList,
1484
+ this.state.linksAndReferencesViewLink,
1409
1485
  )}
1410
1486
  >
1411
1487
  <FormattedMessage
1412
- id="View broken links list"
1413
- defaultMessage="View broken links list"
1488
+ id="View links and references to this item"
1489
+ defaultMessage="View links and references to this item"
1414
1490
  />
1415
1491
  </Link>
1416
- </div>
1417
- </>
1418
- ) : null}
1419
- </p>
1492
+ )}
1493
+ </div>
1494
+ </>
1495
+ ) : null}
1420
1496
  </div>
1421
1497
  }
1422
1498
  onCancel={this.onDeleteCancel}
@@ -2219,7 +2295,7 @@ export default compose(
2219
2295
  asyncConnect([
2220
2296
  {
2221
2297
  key: 'actions',
2222
- // Dispatch async/await to make the operation syncronous, otherwise it returns
2298
+ // Dispatch async/await to make the operation synchronous, otherwise it returns
2223
2299
  // before the promise is resolved
2224
2300
  promise: async ({ location, store: { dispatch } }) =>
2225
2301
  await dispatch(listActions(getBaseUrl(location.pathname))),
@@ -174,7 +174,7 @@ export const TextLineEdit = (props) => {
174
174
  return <div />;
175
175
  }
176
176
  return (
177
- <Slate editor={editor} onChange={handleChange} value={initialValue}>
177
+ <Slate editor={editor} onChange={handleChange} initialValue={initialValue}>
178
178
  <Editable
179
179
  readOnly={!editable}
180
180
  onKeyDown={handleKeyDown}
@@ -1,13 +1,5 @@
1
- import React, { useEffect } from 'react';
2
- import {
3
- toPublicURL,
4
- Helmet,
5
- hasApiExpander,
6
- getBaseUrl,
7
- } from '@plone/volto/helpers';
8
- import { getNavroot } from '@plone/volto/actions';
1
+ import { toPublicURL, Helmet } from '@plone/volto/helpers';
9
2
  import config from '@plone/volto/registry';
10
- import { useDispatch, useSelector } from 'react-redux';
11
3
 
12
4
  const ContentMetadataTags = (props) => {
13
5
  const {
@@ -21,21 +13,23 @@ const ContentMetadataTags = (props) => {
21
13
  description,
22
14
  } = props.content;
23
15
 
24
- const dispatch = useDispatch();
25
- const pathname = useSelector((state) => state.router.location.pathname);
26
- const navroot = useSelector((state) => state.navroot?.data?.navroot);
27
- const site = useSelector((state) => state.site?.data);
28
-
29
- useEffect(() => {
30
- if (pathname && !hasApiExpander('navroot', getBaseUrl(pathname))) {
31
- dispatch(getNavroot(getBaseUrl(pathname)));
32
- }
33
- }, [dispatch, pathname]);
34
-
35
16
  const getContentImageInfo = () => {
36
17
  const { contentMetadataTagsImageField } = config.settings;
37
- const image = props.content[contentMetadataTagsImageField];
18
+ const image_field = props.content[contentMetadataTagsImageField];
19
+ const preview_image = props.content.preview_image;
20
+ const preview_image_link = props.content.preview_image_link;
38
21
  const { opengraph_image } = props.content;
22
+ let image = undefined;
23
+
24
+ if (opengraph_image !== undefined && opengraph_image) {
25
+ image = opengraph_image;
26
+ } else if (preview_image_link !== undefined && preview_image_link) {
27
+ image = preview_image_link[contentMetadataTagsImageField];
28
+ } else if (preview_image !== undefined && preview_image) {
29
+ image = preview_image;
30
+ } else if (image_field !== undefined && image_field) {
31
+ image = image_field;
32
+ }
39
33
 
40
34
  const contentImageInfo = {
41
35
  contentHasImage: false,
@@ -43,10 +37,7 @@ const ContentMetadataTags = (props) => {
43
37
  height: null,
44
38
  width: null,
45
39
  };
46
- contentImageInfo.contentHasImage =
47
- opengraph_image?.scales?.large?.download ||
48
- image?.scales?.large?.download ||
49
- false;
40
+ contentImageInfo.contentHasImage = image?.scales?.large?.download || false;
50
41
 
51
42
  if (contentImageInfo.contentHasImage && opengraph_image?.scales?.large) {
52
43
  contentImageInfo.url = opengraph_image.scales.large.download;
@@ -63,35 +54,16 @@ const ContentMetadataTags = (props) => {
63
54
 
64
55
  const contentImageInfo = getContentImageInfo();
65
56
 
66
- const getTitle = () => {
67
- const includeSiteTitle =
68
- config?.settings?.siteTitleFormat?.includeSiteTitle || false;
69
- const titleAndSiteTitleSeparator =
70
- config?.settings?.titleAndSiteTitleSeparator || '-';
71
- const navRootTitle = navroot?.title;
72
- const siteRootTitle = site?.['plone.site_title'];
73
- const titlePart = navRootTitle || siteRootTitle;
74
-
75
- if (includeSiteTitle && titlePart && titlePart !== title) {
76
- return seo_title || `${title} ${titleAndSiteTitleSeparator} ${titlePart}`;
77
- } else {
78
- return seo_title || title;
79
- }
80
- };
81
-
82
57
  return (
83
58
  <>
84
59
  <Helmet>
85
- <title>{getTitle()?.replace(/\u00AD/g, '')}</title>
86
- <link
87
- rel="canonical"
88
- href={seo_canonical_url || toPublicURL(props.content['@id'])}
89
- />
60
+ <title>{(seo_title || title)?.replace(/\u00AD/g, '')}</title>
90
61
  <meta name="description" content={seo_description || description} />
91
62
  <meta
92
63
  property="og:title"
93
64
  content={opengraph_title || seo_title || title}
94
65
  />
66
+ <meta property="og:type" content={'website'} />
95
67
  <meta
96
68
  property="og:url"
97
69
  content={seo_canonical_url || toPublicURL(props.content['@id'])}
@@ -103,6 +75,12 @@ const ContentMetadataTags = (props) => {
103
75
  content={toPublicURL(contentImageInfo.url)}
104
76
  />
105
77
  )}
78
+ {contentImageInfo.contentHasImage && (
79
+ <meta
80
+ property="twitter:image"
81
+ content={toPublicURL(contentImageInfo.url)}
82
+ />
83
+ )}
106
84
  {contentImageInfo.contentHasImage && (
107
85
  <meta property="og:image:width" content={contentImageInfo.width} />
108
86
  )}
@@ -116,6 +94,21 @@ const ContentMetadataTags = (props) => {
116
94
  />
117
95
  )}
118
96
  <meta name="twitter:card" content="summary_large_image" />
97
+ <meta
98
+ property="twitter:url"
99
+ content={seo_canonical_url || toPublicURL(props.content['@id'])}
100
+ />
101
+ {/* TODO: Improve SEO backend metadata providers by adding the twitter handler */}
102
+ {/* <meta property="twitter:site" content={'@my_twitter_handler'} /> */}
103
+ <meta
104
+ property="twitter:title"
105
+ content={opengraph_title || seo_title || title}
106
+ />
107
+ <meta
108
+ property="twitter:description"
109
+ content={seo_description || description}
110
+ />
111
+ <meta property="twitter:domain" content={config.settings.publicURL} />
119
112
  </Helmet>
120
113
  </>
121
114
  );