@plone/volto 18.0.0-alpha.33 → 18.0.0-alpha.35

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 (75) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/locales/ca/LC_MESSAGES/volto.po +5 -0
  3. package/locales/ca.json +1 -1
  4. package/locales/de/LC_MESSAGES/volto.po +5 -0
  5. package/locales/de.json +1 -1
  6. package/locales/en/LC_MESSAGES/volto.po +5 -0
  7. package/locales/en.json +1 -1
  8. package/locales/es/LC_MESSAGES/volto.po +5 -0
  9. package/locales/es.json +1 -1
  10. package/locales/eu/LC_MESSAGES/volto.po +5 -0
  11. package/locales/eu.json +1 -1
  12. package/locales/fi/LC_MESSAGES/volto.po +5 -0
  13. package/locales/fi.json +1 -1
  14. package/locales/fr/LC_MESSAGES/volto.po +5 -0
  15. package/locales/fr.json +1 -1
  16. package/locales/hi/LC_MESSAGES/volto.po +5 -0
  17. package/locales/hi.json +1 -1
  18. package/locales/it/LC_MESSAGES/volto.po +5 -0
  19. package/locales/it.json +1 -1
  20. package/locales/ja/LC_MESSAGES/volto.po +5 -0
  21. package/locales/ja.json +1 -1
  22. package/locales/nl/LC_MESSAGES/volto.po +5 -0
  23. package/locales/nl.json +1 -1
  24. package/locales/pt/LC_MESSAGES/volto.po +5 -0
  25. package/locales/pt.json +1 -1
  26. package/locales/pt_BR/LC_MESSAGES/volto.po +5 -0
  27. package/locales/pt_BR.json +1 -1
  28. package/locales/ro/LC_MESSAGES/volto.po +5 -0
  29. package/locales/ro.json +1 -1
  30. package/locales/volto.pot +5 -0
  31. package/locales/zh_CN/LC_MESSAGES/volto.po +5 -0
  32. package/locales/zh_CN.json +1 -1
  33. package/package.json +8 -5
  34. package/razzle.config.js +20 -5
  35. package/src/actions/form/form.js +18 -2
  36. package/src/actions/index.js +1 -1
  37. package/src/components/manage/Blocks/Block/BlocksForm.jsx +157 -81
  38. package/src/components/manage/Blocks/Block/BlocksForm.test.jsx +8 -0
  39. package/src/components/manage/Blocks/Block/Edit.jsx +37 -4
  40. package/src/components/manage/Blocks/Block/Order/Item.jsx +122 -0
  41. package/src/components/manage/Blocks/Block/Order/Order.jsx +367 -0
  42. package/src/components/manage/Blocks/Block/Order/SortableItem.jsx +58 -0
  43. package/src/components/manage/Blocks/Block/Order/utilities.js +113 -0
  44. package/src/components/manage/Blocks/Container/Edit.jsx +1 -0
  45. package/src/components/manage/Blocks/Grid/Edit.jsx +6 -4
  46. package/src/components/manage/Blocks/Image/schema.js +2 -0
  47. package/src/components/manage/Controlpanels/Relations/RelationsListing.jsx +1 -1
  48. package/src/components/manage/Controlpanels/Relations/RelationsMatrix.jsx +1 -1
  49. package/src/components/manage/Form/Form.jsx +159 -151
  50. package/src/components/manage/Sidebar/Sidebar.jsx +28 -1
  51. package/src/components/manage/Widgets/InternalUrlWidget.jsx +10 -14
  52. package/src/components/theme/FormattedDate/FormattedDate.jsx +13 -1
  53. package/src/components/theme/Icon/Icon.jsx +4 -4
  54. package/src/config/Loadables.jsx +18 -0
  55. package/src/config/index.js +0 -1
  56. package/src/constants/ActionTypes.js +1 -0
  57. package/src/helpers/Blocks/Blocks.js +198 -4
  58. package/src/helpers/Blocks/Blocks.test.js +191 -0
  59. package/src/helpers/index.js +5 -0
  60. package/src/reducers/form/form.js +18 -1
  61. package/src/reducers/form/form.test.js +15 -1
  62. package/theme/themes/pastanaga/extras/blocks.less +7 -6
  63. package/theme/themes/pastanaga/extras/sidebar.less +145 -0
  64. package/types/actions/form/form.d.ts +8 -1
  65. package/types/actions/index.d.ts +1 -1
  66. package/types/components/manage/Blocks/Block/Order/Item.d.ts +2 -0
  67. package/types/components/manage/Blocks/Block/Order/Order.d.ts +13 -0
  68. package/types/components/manage/Blocks/Block/Order/SortableItem.d.ts +9 -0
  69. package/types/components/manage/Blocks/Block/Order/utilities.d.ts +9 -0
  70. package/types/components/manage/Blocks/Image/schema.d.ts +2 -0
  71. package/types/components/theme/Icon/Icon.d.ts +8 -8
  72. package/types/config/Loadables.d.ts +15 -162
  73. package/types/constants/ActionTypes.d.ts +1 -0
  74. package/types/helpers/Blocks/Blocks.d.ts +19 -0
  75. package/types/helpers/index.d.ts +3 -3
@@ -7,7 +7,11 @@ import { omit, without, endsWith, find, isObject, keys, merge } from 'lodash';
7
7
  import move from 'lodash-move';
8
8
  import { v4 as uuid } from 'uuid';
9
9
  import config from '@plone/volto/registry';
10
- import { applySchemaEnhancer } from '@plone/volto/helpers';
10
+ import {
11
+ applySchemaEnhancer,
12
+ insertInArray,
13
+ removeFromArray,
14
+ } from '@plone/volto/helpers';
11
15
 
12
16
  /**
13
17
  * Get blocks field.
@@ -689,6 +693,19 @@ export const getPreviousNextBlock = ({ content, block }) => {
689
693
  return [previousBlock, nextBlock];
690
694
  };
691
695
 
696
+ /**
697
+ * Check if a block is a container block
698
+ * check blocks from data as well since some add-ons use that
699
+ * such as @eeacms/volto-tabs-block
700
+ */
701
+ export function isBlockContainer(block) {
702
+ return (
703
+ block &&
704
+ (hasBlocksData(block) ||
705
+ (block.hasOwnProperty('data') && hasBlocksData(block.data)))
706
+ );
707
+ }
708
+
692
709
  /**
693
710
  * Given a `block` object and a list of block types, return a list of block ids matching the types
694
711
  *
@@ -697,8 +714,6 @@ export const getPreviousNextBlock = ({ content, block }) => {
697
714
  * @return {Array} An array of block ids
698
715
  */
699
716
  export function findBlocks(blocks, types, result = []) {
700
- const containerBlockTypes = config.settings.containerBlockTypes;
701
-
702
717
  Object.keys(blocks).forEach((blockId) => {
703
718
  const block = blocks[blockId];
704
719
  // check blocks from data as well since some add-ons use that
@@ -706,7 +721,7 @@ export function findBlocks(blocks, types, result = []) {
706
721
  const child_blocks = block.blocks || block.data?.blocks;
707
722
  if (types.includes(block['@type'])) {
708
723
  result.push(blockId);
709
- } else if (containerBlockTypes.includes(block['@type']) || child_blocks) {
724
+ } else if (isBlockContainer(block)) {
710
725
  findBlocks(child_blocks, types, result);
711
726
  }
712
727
  });
@@ -714,6 +729,185 @@ export function findBlocks(blocks, types, result = []) {
714
729
  return result;
715
730
  }
716
731
 
732
+ /**
733
+ * Build a block's hierarchy that the order tab can understand and uses
734
+ */
735
+ export const getBlocksHierarchy = (properties) => {
736
+ const blocksFieldName = getBlocksFieldname(properties);
737
+ const blocksLayoutFieldname = getBlocksLayoutFieldname(properties);
738
+ return properties[blocksLayoutFieldname]?.items?.map((n) => ({
739
+ id: n,
740
+ title: properties[blocksFieldName][n]?.['@type'],
741
+ data: properties[blocksFieldName][n],
742
+ children: isBlockContainer(properties[blocksFieldName][n])
743
+ ? getBlocksHierarchy(properties[blocksFieldName][n])
744
+ : [],
745
+ }));
746
+ };
747
+
748
+ /**
749
+ * Move block to different location index within blocks_layout
750
+ * @function moveBlock
751
+ * @param {Object} formData Form data
752
+ * @param {number} source index within form blocks_layout items
753
+ * @param {number} destination index within form blocks_layout items
754
+ * @return {Object} New form data
755
+ */
756
+ export function moveBlockEnhanced(formData, { source, destination }) {
757
+ const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
758
+ const blocksFieldName = getBlocksFieldname(formData);
759
+
760
+ // If either one of source and destination are present
761
+ // (Moves intra-container or container <-> main container)
762
+ if (source.parent || destination.parent) {
763
+ // Move from a container to the main container
764
+ if (source.parent && !destination.parent) {
765
+ let clonedFormData = { ...formData };
766
+
767
+ clonedFormData[blocksFieldName][source.id] =
768
+ formData[blocksFieldName][source.parent][blocksFieldName][source.id];
769
+
770
+ clonedFormData[blocksLayoutFieldname].items = insertInArray(
771
+ formData[blocksLayoutFieldname].items,
772
+ source.id,
773
+ destination.position,
774
+ );
775
+
776
+ // Remove the source block from the source parent
777
+ const sourceContainer = findContainer(clonedFormData, {
778
+ containerId: source.parent,
779
+ });
780
+ delete sourceContainer[blocksFieldName][source.id];
781
+ sourceContainer[blocksLayoutFieldname].items = removeFromArray(
782
+ sourceContainer[blocksLayoutFieldname].items,
783
+ source.position,
784
+ );
785
+
786
+ return clonedFormData;
787
+ }
788
+
789
+ // Move from the main container to an inner container
790
+ if (!source.parent && destination.parent) {
791
+ let clonedFormData = { ...formData };
792
+
793
+ const destinationContainer = findContainer(clonedFormData, {
794
+ containerId: destination.parent,
795
+ });
796
+ destinationContainer[blocksFieldName][source.id] =
797
+ clonedFormData[blocksFieldName][source.id];
798
+ destinationContainer[blocksLayoutFieldname].items = insertInArray(
799
+ destinationContainer[blocksLayoutFieldname].items,
800
+ source.id,
801
+ destination.position,
802
+ );
803
+
804
+ // Remove the source block from the source parent
805
+ delete clonedFormData[blocksFieldName][source.id];
806
+ clonedFormData[blocksLayoutFieldname].items = removeFromArray(
807
+ clonedFormData[blocksLayoutFieldname].items,
808
+ source.position,
809
+ );
810
+
811
+ return clonedFormData;
812
+ }
813
+
814
+ // Move within the same container (except moves within the main container)
815
+ if (source.parent === destination.parent) {
816
+ let clonedFormData = { ...formData };
817
+
818
+ const destinationContainer = findContainer(clonedFormData, {
819
+ containerId: destination.parent,
820
+ });
821
+
822
+ destinationContainer[blocksLayoutFieldname].items = move(
823
+ destinationContainer[blocksLayoutFieldname].items,
824
+ source.position,
825
+ destination.position,
826
+ );
827
+ return clonedFormData;
828
+ }
829
+
830
+ // Move between containers
831
+ if (source.parent !== destination.parent) {
832
+ let clonedFormData = { ...formData };
833
+
834
+ const destinationContainer = findContainer(clonedFormData, {
835
+ containerId: destination.parent,
836
+ });
837
+ destinationContainer[blocksFieldName][source.id] =
838
+ formData[blocksFieldName][source.parent][blocksFieldName][source.id];
839
+
840
+ destinationContainer[blocksLayoutFieldname].items = insertInArray(
841
+ destinationContainer[blocksLayoutFieldname].items,
842
+ source.id,
843
+ destination.position,
844
+ );
845
+
846
+ // Remove the source block from the source parent
847
+ const sourceContainer = findContainer(clonedFormData, {
848
+ containerId: source.parent,
849
+ });
850
+ delete sourceContainer[blocksFieldName][source.id];
851
+ sourceContainer[blocksLayoutFieldname].items = removeFromArray(
852
+ sourceContainer[blocksLayoutFieldname].items,
853
+ source.position,
854
+ );
855
+
856
+ return clonedFormData;
857
+ }
858
+ }
859
+
860
+ // Default catch all, no source/destination parent specified
861
+ // Move within the main container
862
+ return {
863
+ ...formData,
864
+ [blocksLayoutFieldname]: {
865
+ items: move(
866
+ formData[blocksLayoutFieldname].items,
867
+ source.position,
868
+ destination.position,
869
+ ),
870
+ },
871
+ };
872
+ }
873
+
874
+ /**
875
+ * Finds the container with the specified containerId in the given formData.
876
+ *
877
+ * @param {object} formData - The form data object.
878
+ * @param {object} options - The options object.
879
+ * @param {string} options.containerId - The ID of the container to find.
880
+ * @returns {object|undefined} - The container object if found, otherwise undefined.
881
+ */
882
+ export const findContainer = (formData, { containerId }) => {
883
+ if (
884
+ formData.blocks[containerId] &&
885
+ Object.keys(formData.blocks[containerId]).includes('blocks') &&
886
+ Object.keys(formData.blocks[containerId]).includes('blocks_layout')
887
+ ) {
888
+ return formData.blocks[containerId];
889
+ }
890
+
891
+ let container;
892
+ Object.keys(formData.blocks).every((blockId) => {
893
+ const block = formData.blocks[blockId];
894
+ if (
895
+ formData.blocks[blockId] &&
896
+ Object.keys(formData.blocks[blockId]).includes('blocks') &&
897
+ Object.keys(formData.blocks[blockId]).includes('blocks_layout')
898
+ ) {
899
+ container = findContainer(block, { containerId });
900
+ }
901
+ if (container) {
902
+ return false;
903
+ } else {
904
+ return true;
905
+ }
906
+ });
907
+
908
+ return container;
909
+ };
910
+
717
911
  const _dummyIntl = {
718
912
  formatMessage() {},
719
913
  };
@@ -22,6 +22,8 @@ import {
22
22
  getPreviousNextBlock,
23
23
  blocksFormGenerator,
24
24
  findBlocks,
25
+ findContainer,
26
+ isBlockContainer,
25
27
  } from './Blocks';
26
28
 
27
29
  import config from '@plone/volto/registry';
@@ -1507,3 +1509,192 @@ describe('findBlocks', () => {
1507
1509
  expect(findBlocks(blocks, types)).toStrictEqual(['3', '4', '8', '9']);
1508
1510
  });
1509
1511
  });
1512
+
1513
+ describe('findContainer', () => {
1514
+ const blocksData = { blocks: {}, blocks_layout: { items: [] } };
1515
+
1516
+ it('Get a container in the first level (main block container)', () => {
1517
+ const formData = {
1518
+ title: 'Example',
1519
+ blocks: {
1520
+ 1: { title: 'title', '@type': 'title' },
1521
+ 2: { title: 'an image', '@type': 'image' },
1522
+ 3: { title: 'description', '@type': 'description' },
1523
+ 4: { title: 'a container', '@type': 'container', ...blocksData },
1524
+ },
1525
+ blocks_layout: {
1526
+ items: ['1', '2', '3', '4'],
1527
+ },
1528
+ };
1529
+
1530
+ expect(findContainer(formData, { containerId: '4' })).toStrictEqual({
1531
+ title: 'a container',
1532
+ '@type': 'container',
1533
+ ...blocksData,
1534
+ });
1535
+ });
1536
+
1537
+ it('Get a container in the second level', () => {
1538
+ const formData = {
1539
+ title: 'Example',
1540
+ blocks: {
1541
+ 1: { title: 'title', '@type': 'title' },
1542
+ 2: { title: 'an image', '@type': 'image' },
1543
+ 3: { title: 'description', '@type': 'description' },
1544
+ 4: {
1545
+ title: 'a container',
1546
+ '@type': 'container',
1547
+ blocks: {
1548
+ 1: { title: 'title', '@type': 'title' },
1549
+ 2: { title: 'an image', '@type': 'image' },
1550
+ 'second-level': {
1551
+ title: 'a container',
1552
+ '@type': 'container',
1553
+ blocks: {},
1554
+ blocks_layout: { items: [] },
1555
+ },
1556
+ },
1557
+ blocks_layout: { items: [1, 2, 'second-level'] },
1558
+ },
1559
+ },
1560
+ blocks_layout: {
1561
+ items: ['1', '2', '3', '4'],
1562
+ },
1563
+ };
1564
+
1565
+ expect(
1566
+ findContainer(formData, { containerId: 'second-level' }),
1567
+ ).toStrictEqual({
1568
+ title: 'a container',
1569
+ '@type': 'container',
1570
+ blocks: {},
1571
+ blocks_layout: { items: [] },
1572
+ });
1573
+ });
1574
+
1575
+ it('Get a container in the third level', () => {
1576
+ const formData = {
1577
+ title: 'Example',
1578
+ blocks: {
1579
+ 1: { title: 'title', '@type': 'title' },
1580
+ 2: { title: 'an image', '@type': 'image' },
1581
+ 3: { title: 'description', '@type': 'description' },
1582
+ 4: {
1583
+ title: 'a container',
1584
+ '@type': 'container',
1585
+ blocks: {
1586
+ 1: { title: 'title', '@type': 'title' },
1587
+ 2: { title: 'an image', '@type': 'image' },
1588
+ 'second-level': {
1589
+ title: 'a second level container',
1590
+ '@type': 'container',
1591
+ blocks: {
1592
+ 'third-level': {
1593
+ title: 'a third level container',
1594
+ '@type': 'container',
1595
+ blocks: {},
1596
+ blocks_layout: { items: [] },
1597
+ },
1598
+ },
1599
+ blocks_layout: { items: ['third-level'] },
1600
+ },
1601
+ },
1602
+ blocks_layout: { items: [1, 2, 'second-level'] },
1603
+ },
1604
+ },
1605
+ blocks_layout: {
1606
+ items: ['1', '2', '3', '4'],
1607
+ },
1608
+ };
1609
+
1610
+ expect(
1611
+ findContainer(formData, { containerId: 'third-level' }),
1612
+ ).toStrictEqual({
1613
+ title: 'a third level container',
1614
+ '@type': 'container',
1615
+ blocks: {},
1616
+ blocks_layout: { items: [] },
1617
+ });
1618
+ });
1619
+
1620
+ describe('findContainer then modify it', () => {
1621
+ const blocksData = { blocks: {}, blocks_layout: { items: [] } };
1622
+
1623
+ it('Get and modifies a container in the first level (main block container)', () => {
1624
+ const formData = {
1625
+ title: 'Example',
1626
+ blocks: {
1627
+ 1: { title: 'title', '@type': 'title' },
1628
+ 2: { title: 'an image', '@type': 'image' },
1629
+ 3: { title: 'description', '@type': 'description' },
1630
+ 4: { title: 'a container', '@type': 'container', ...blocksData },
1631
+ },
1632
+ blocks_layout: {
1633
+ items: ['1', '2', '3', '4'],
1634
+ },
1635
+ };
1636
+
1637
+ const container = findContainer(formData, { containerId: '4' });
1638
+ container.title = 'Modified the title of the container';
1639
+ expect(findContainer(formData, { containerId: '4' })).toStrictEqual({
1640
+ title: 'Modified the title of the container',
1641
+ '@type': 'container',
1642
+ ...blocksData,
1643
+ });
1644
+ });
1645
+ });
1646
+
1647
+ describe('isBlockContainer', () => {
1648
+ const blocksData = { blocks: {}, blocks_layout: { items: [] } };
1649
+
1650
+ it('basic test', () => {
1651
+ const formData = {
1652
+ title: 'Example',
1653
+ blocks: {
1654
+ 1: { title: 'title', '@type': 'title' },
1655
+ 2: { title: 'an image', '@type': 'image' },
1656
+ 3: { title: 'description', '@type': 'description' },
1657
+ 4: { title: 'a container', '@type': 'container', ...blocksData },
1658
+ },
1659
+ blocks_layout: {
1660
+ items: ['1', '2', '3', '4'],
1661
+ },
1662
+ };
1663
+
1664
+ const container = isBlockContainer(formData);
1665
+ expect(container).toBeTruthy();
1666
+ });
1667
+
1668
+ it('in data key (EEA add-ons)', () => {
1669
+ const formData = {
1670
+ title: 'Example',
1671
+ data: {
1672
+ blocks: {
1673
+ 1: { title: 'title', '@type': 'title' },
1674
+ 2: { title: 'an image', '@type': 'image' },
1675
+ 3: { title: 'description', '@type': 'description' },
1676
+ 4: { title: 'a container', '@type': 'container', ...blocksData },
1677
+ },
1678
+ blocks_layout: {
1679
+ items: ['1', '2', '3', '4'],
1680
+ },
1681
+ },
1682
+ };
1683
+
1684
+ const container = isBlockContainer(formData);
1685
+ expect(container).toBeTruthy();
1686
+ });
1687
+
1688
+ it('not a container', () => {
1689
+ const formData = {
1690
+ title: 'Example',
1691
+ styles: {
1692
+ color: 'red',
1693
+ },
1694
+ };
1695
+
1696
+ const container = isBlockContainer(formData);
1697
+ expect(container).toBeFalsy();
1698
+ });
1699
+ });
1700
+ });
@@ -30,6 +30,7 @@ export {
30
30
  removeProtocol,
31
31
  URLUtils,
32
32
  flattenScales,
33
+ getFieldURL,
33
34
  } from '@plone/volto/helpers/Url/Url';
34
35
  export { generateRobots } from '@plone/volto/helpers/Robots/Robots';
35
36
  export {
@@ -61,6 +62,8 @@ export {
61
62
  buildStyleObjectFromData,
62
63
  getPreviousNextBlock,
63
64
  findBlocks,
65
+ getBlocksHierarchy,
66
+ moveBlockEnhanced,
64
67
  } from '@plone/volto/helpers/Blocks/Blocks';
65
68
  export {
66
69
  getSimpleDefaultBlocks,
@@ -98,6 +101,8 @@ export {
98
101
  hasApiExpander,
99
102
  replaceItemOfArray,
100
103
  cloneDeepSchema,
104
+ insertInArray,
105
+ removeFromArray,
101
106
  arrayRange,
102
107
  reorderArray,
103
108
  isInteractiveElement,
@@ -4,10 +4,19 @@
4
4
  * @module reducers/form/form
5
5
  */
6
6
 
7
- import { SET_FORM_DATA } from '@plone/volto/constants/ActionTypes';
7
+ import {
8
+ SET_FORM_DATA,
9
+ SET_UI_STATE,
10
+ } from '@plone/volto/constants/ActionTypes';
8
11
 
9
12
  const initialState = {
10
13
  global: {},
14
+ ui: {
15
+ selected: null,
16
+ multiSelected: [],
17
+ gridSelected: null,
18
+ hovered: null,
19
+ },
11
20
  };
12
21
 
13
22
  /**
@@ -23,6 +32,14 @@ export default function form(state = initialState, action = {}) {
23
32
  ...state,
24
33
  global: action.data,
25
34
  };
35
+ case SET_UI_STATE:
36
+ return {
37
+ ...state,
38
+ ui: {
39
+ ...state.ui,
40
+ ...action.ui,
41
+ },
42
+ };
26
43
  default:
27
44
  return state;
28
45
  }
@@ -3,7 +3,15 @@ import { SET_FORM_DATA } from '@plone/volto/constants/ActionTypes';
3
3
 
4
4
  describe('Form reducer', () => {
5
5
  it('should return the initial state', () => {
6
- expect(form()).toEqual({ global: {} });
6
+ expect(form()).toEqual({
7
+ global: {},
8
+ ui: {
9
+ gridSelected: null,
10
+ hovered: null,
11
+ multiSelected: [],
12
+ selected: null,
13
+ },
14
+ });
7
15
  });
8
16
 
9
17
  it('should handle SET_FORM_DATA', () => {
@@ -14,6 +22,12 @@ describe('Form reducer', () => {
14
22
  }),
15
23
  ).toEqual({
16
24
  global: { foo: 'bar' },
25
+ ui: {
26
+ gridSelected: null,
27
+ hovered: null,
28
+ multiSelected: [],
29
+ selected: null,
30
+ },
17
31
  });
18
32
  });
19
33
  });
@@ -36,20 +36,21 @@
36
36
  display: inline-block;
37
37
  }
38
38
 
39
- .block .block.selected::before,
40
- .block .block.selected:hover::before {
41
- border-width: 1px;
42
- border-color: rgba(120, 192, 215, 0.75);
43
- }
44
-
45
39
  [data-slate-editor='true'] {
46
40
  outline: none;
47
41
  }
48
42
 
43
+ .block .block.hovered::before,
49
44
  .block .block:hover::before {
50
45
  border-color: rgba(120, 192, 215, 0.375);
51
46
  }
52
47
 
48
+ .block .block.selected::before,
49
+ .block .block.selected:hover::before {
50
+ border-width: 1px;
51
+ border-color: rgba(120, 192, 215, 0.75);
52
+ }
53
+
53
54
  .block .block.multiSelected::before {
54
55
  z-index: 1;
55
56
  background-color: rgba(120, 192, 215, 0.375);
@@ -504,3 +504,148 @@
504
504
  .formtabs {
505
505
  flex-wrap: wrap;
506
506
  }
507
+
508
+ // Order
509
+ .tree-item-wrapper {
510
+ box-sizing: border-box;
511
+ padding-left: var(--spacing);
512
+ margin-bottom: -1px;
513
+ list-style: none;
514
+
515
+ .text {
516
+ overflow: hidden;
517
+ flex-grow: 1;
518
+ padding-left: 0.5rem;
519
+ text-overflow: ellipsis;
520
+ white-space: nowrap;
521
+ }
522
+
523
+ &.disable-interaction {
524
+ pointer-events: none;
525
+ }
526
+
527
+ &.disable-selection,
528
+ &.clone {
529
+ .text {
530
+ -webkit-user-select: none;
531
+ user-select: none;
532
+ }
533
+ }
534
+
535
+ &.clone {
536
+ display: inline-block;
537
+ width: 375px;
538
+ height: 43px;
539
+ padding: 0;
540
+ pointer-events: none;
541
+
542
+ .tree-item {
543
+ box-shadow: 0px 15px 15px 0 rgba(34, 33, 81, 0.1);
544
+ }
545
+ }
546
+
547
+ &.ghost {
548
+ &.indicator {
549
+ position: relative;
550
+ z-index: 1;
551
+ margin-bottom: -1px;
552
+ opacity: 1;
553
+
554
+ .tree-item {
555
+ position: relative;
556
+ height: 8px;
557
+ padding: 0;
558
+ border-color: #2389ff;
559
+ background-color: #56a1f8;
560
+
561
+ &:before {
562
+ position: absolute;
563
+ top: -4px;
564
+ left: -8px;
565
+ display: block;
566
+ width: 12px;
567
+ height: 12px;
568
+ border: 1px solid #2389ff;
569
+ border-radius: 50%;
570
+ background-color: #ffffff;
571
+ content: '';
572
+ }
573
+
574
+ > * {
575
+ height: 0;
576
+ /* Items are hidden using height and opacity to retain focus */
577
+ opacity: 0;
578
+ }
579
+ }
580
+ }
581
+
582
+ &:not(.indicator) {
583
+ opacity: 0.5;
584
+ }
585
+
586
+ .tree-item > * {
587
+ background-color: transparent;
588
+ box-shadow: none;
589
+ }
590
+ }
591
+
592
+ .tree-item {
593
+ position: relative;
594
+ display: flex;
595
+ box-sizing: border-box;
596
+ align-items: center;
597
+ padding: var(--vertical-padding) 10px;
598
+ border: 1px solid #edf1f2;
599
+ background-color: #fff;
600
+ color: #222;
601
+ cursor: pointer;
602
+ --vertical-padding: 10px;
603
+
604
+ .delete {
605
+ visibility: hidden;
606
+ }
607
+
608
+ &.depth-0 {
609
+ border-left: solid 1px white;
610
+ }
611
+
612
+ &.hovered {
613
+ z-index: 99;
614
+ border: solid 1px #69b6fa;
615
+ }
616
+
617
+ &.selected {
618
+ z-index: 99;
619
+ border: solid 1px #2996da;
620
+
621
+ .delete {
622
+ visibility: visible;
623
+ }
624
+ }
625
+
626
+ &.multiSelected {
627
+ z-index: 99;
628
+ border: solid 1px #2996da;
629
+ background-color: rgba(120, 192, 215, 0.375);
630
+ }
631
+
632
+ .action {
633
+ border: 0;
634
+ margin-left: 8px;
635
+ background-color: transparent;
636
+ color: rgba(0, 0, 0, 0.6);
637
+
638
+ .icon {
639
+ vertical-align: middle;
640
+ }
641
+
642
+ &.delete {
643
+ cursor: pointer;
644
+ }
645
+
646
+ &.drag {
647
+ cursor: grab;
648
+ }
649
+ }
650
+ }
651
+ }