@frontify/guideline-blocks-settings 0.27.0 → 0.28.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 (214) hide show
  1. package/.eslintrc.js +1 -1
  2. package/CHANGELOG.md +15 -0
  3. package/README.md +24 -5
  4. package/package.json +46 -10
  5. package/postcss.config.js +8 -0
  6. package/setupTests.ts +13 -0
  7. package/src/components/Attachments/AttachmentItem.tsx +257 -0
  8. package/src/components/Attachments/Attachments.spec.ct.tsx +151 -0
  9. package/src/components/Attachments/Attachments.tsx +221 -0
  10. package/src/components/Attachments/index.ts +4 -0
  11. package/src/components/Attachments/types.ts +30 -0
  12. package/src/components/BlockInjectButton/BlockInjectButton.spec.ct.tsx +48 -0
  13. package/src/components/BlockInjectButton/BlockInjectButton.tsx +212 -0
  14. package/src/components/BlockInjectButton/index.ts +4 -0
  15. package/src/components/BlockInjectButton/types.ts +18 -0
  16. package/src/components/BlockItemWrapper/BlockItemWrapper.spec.ct.tsx +146 -0
  17. package/src/components/BlockItemWrapper/BlockItemWrapper.tsx +76 -0
  18. package/src/components/BlockItemWrapper/Toolbar.tsx +128 -0
  19. package/src/components/BlockItemWrapper/constants.ts +4 -0
  20. package/src/components/BlockItemWrapper/index.ts +5 -0
  21. package/src/components/BlockItemWrapper/types.ts +46 -0
  22. package/src/components/DownloadButton/DownloadButton.spec.ct.tsx +20 -0
  23. package/src/components/DownloadButton/DownloadButton.tsx +36 -0
  24. package/src/components/DownloadButton/index.ts +3 -0
  25. package/src/components/DownloadButton/types.ts +5 -0
  26. package/src/components/RichTextEditor/RichTextEditor.spec.ct.tsx +204 -0
  27. package/src/components/RichTextEditor/RichTextEditor.tsx +62 -0
  28. package/src/components/RichTextEditor/SerializedText.tsx +25 -0
  29. package/src/components/RichTextEditor/constants.ts +3 -0
  30. package/src/components/RichTextEditor/index.ts +6 -0
  31. package/src/components/RichTextEditor/pluginPresets/defaultPluginsWithLinkChooser.tsx +53 -0
  32. package/src/components/RichTextEditor/pluginPresets/index.ts +3 -0
  33. package/src/components/RichTextEditor/plugins/ButtonPlugin/ButtonMarkupElement/ButtonMarkupElementNode.tsx +74 -0
  34. package/src/components/RichTextEditor/plugins/ButtonPlugin/ButtonMarkupElement/index.ts +11 -0
  35. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/ButtonButton.tsx +20 -0
  36. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/ButtonToolbarButton.tsx +56 -0
  37. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/CustomFloatingButton.tsx +19 -0
  38. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/EditButtonModal/EditModal.tsx +42 -0
  39. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/FloatingButton.tsx +37 -0
  40. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/FloatingButtonEditButton.tsx +22 -0
  41. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/FloatingButtonUrlInput.tsx +30 -0
  42. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/InsertButtonModal/InsertButtonModal.tsx +81 -0
  43. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/InsertButtonModal/types.ts +13 -0
  44. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/InsertButtonModal/useInsertModal.ts +143 -0
  45. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/UnlinkButton.tsx +31 -0
  46. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/floatingButtonStore.ts +46 -0
  47. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/index.ts +12 -0
  48. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/useFloatingButtonEdit.ts +113 -0
  49. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/useFloatingButtonEnter.ts +21 -0
  50. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/useFloatingButtonEscape.ts +30 -0
  51. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/useFloatingButtonInsert.ts +71 -0
  52. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/FloatingButton/useVirtualFloatingButton.ts +22 -0
  53. package/src/components/RichTextEditor/plugins/ButtonPlugin/components/index.ts +3 -0
  54. package/src/components/RichTextEditor/plugins/ButtonPlugin/createButtonPlugin.ts +116 -0
  55. package/src/components/RichTextEditor/plugins/ButtonPlugin/index.ts +7 -0
  56. package/src/components/RichTextEditor/plugins/ButtonPlugin/transforms/index.ts +8 -0
  57. package/src/components/RichTextEditor/plugins/ButtonPlugin/transforms/insertButton.ts +17 -0
  58. package/src/components/RichTextEditor/plugins/ButtonPlugin/transforms/submitFloatingButton.ts +40 -0
  59. package/src/components/RichTextEditor/plugins/ButtonPlugin/transforms/unwrapButton.ts +68 -0
  60. package/src/components/RichTextEditor/plugins/ButtonPlugin/transforms/upsertButton.ts +198 -0
  61. package/src/components/RichTextEditor/plugins/ButtonPlugin/transforms/upsertButtonText.ts +40 -0
  62. package/src/components/RichTextEditor/plugins/ButtonPlugin/transforms/wrapButton.ts +30 -0
  63. package/src/components/RichTextEditor/plugins/ButtonPlugin/types.ts +13 -0
  64. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/createButtonNode.ts +28 -0
  65. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/getButtonStyle.ts +14 -0
  66. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/getUrl.ts +18 -0
  67. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/index.ts +8 -0
  68. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/styles.ts +77 -0
  69. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/triggerFloatingButton.ts +23 -0
  70. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/triggerFloatingButtonEdit.ts +30 -0
  71. package/src/components/RichTextEditor/plugins/ButtonPlugin/utils/triggerFloatingButtonInsert.ts +45 -0
  72. package/src/components/RichTextEditor/plugins/ButtonPlugin/withButton.ts +106 -0
  73. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/CustomFloatingLink.tsx +26 -0
  74. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/EditLinkModal/EditModal.tsx +43 -0
  75. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/EditLinkModal/index.ts +4 -0
  76. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/EditLinkModal/useFloatingLinkEdit.ts +113 -0
  77. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/FloatingLink.tsx +45 -0
  78. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/InsertLinkModal/InsertLinkModal.tsx +5 -0
  79. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/InsertLinkModal/InsertModal.tsx +105 -0
  80. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/InsertLinkModal/index.ts +4 -0
  81. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/InsertLinkModal/types.ts +16 -0
  82. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/InsertLinkModal/useFloatingLinkInsert.ts +73 -0
  83. package/src/components/RichTextEditor/plugins/LinkPlugin/FloatingLink/InsertLinkModal/useInsertModal.ts +136 -0
  84. package/src/components/RichTextEditor/plugins/LinkPlugin/LinkButton.tsx +38 -0
  85. package/src/components/RichTextEditor/plugins/LinkPlugin/LinkMarkupElement/LinkMarkupElementNode.tsx +36 -0
  86. package/src/components/RichTextEditor/plugins/LinkPlugin/LinkMarkupElement/index.ts +11 -0
  87. package/src/components/RichTextEditor/plugins/LinkPlugin/id.ts +3 -0
  88. package/src/components/RichTextEditor/plugins/LinkPlugin/index.ts +48 -0
  89. package/src/components/RichTextEditor/plugins/LinkPlugin/types.ts +12 -0
  90. package/src/components/RichTextEditor/plugins/LinkPlugin/utils/getUrl.ts +30 -0
  91. package/src/components/RichTextEditor/plugins/LinkPlugin/utils/index.ts +4 -0
  92. package/src/components/RichTextEditor/plugins/LinkPlugin/utils/relativeUrlRegex.spec.ts +35 -0
  93. package/src/components/RichTextEditor/plugins/LinkPlugin/utils/relativeUrlRegex.ts +3 -0
  94. package/src/components/RichTextEditor/plugins/LinkPlugin/utils/url.spec.ts +75 -0
  95. package/src/components/RichTextEditor/plugins/LinkPlugin/utils/url.ts +21 -0
  96. package/src/components/RichTextEditor/plugins/TextStylePlugins/custom1Plugin.tsx +61 -0
  97. package/src/components/RichTextEditor/plugins/TextStylePlugins/custom2Plugin.tsx +61 -0
  98. package/src/components/RichTextEditor/plugins/TextStylePlugins/custom3Plugin.tsx +62 -0
  99. package/src/components/RichTextEditor/plugins/TextStylePlugins/heading1Plugin.tsx +61 -0
  100. package/src/components/RichTextEditor/plugins/TextStylePlugins/heading2Plugin.tsx +58 -0
  101. package/src/components/RichTextEditor/plugins/TextStylePlugins/heading3Plugin.tsx +58 -0
  102. package/src/components/RichTextEditor/plugins/TextStylePlugins/heading4Plugin.tsx +59 -0
  103. package/src/components/RichTextEditor/plugins/TextStylePlugins/helpers.tsx +44 -0
  104. package/src/components/RichTextEditor/plugins/TextStylePlugins/imageCaptionPlugin.tsx +61 -0
  105. package/src/components/RichTextEditor/plugins/TextStylePlugins/imageTitlePlugin.tsx +61 -0
  106. package/src/components/RichTextEditor/plugins/TextStylePlugins/index.ts +15 -0
  107. package/src/components/RichTextEditor/plugins/TextStylePlugins/paragraphPlugin.tsx +58 -0
  108. package/src/components/RichTextEditor/plugins/TextStylePlugins/quotePlugin.tsx +62 -0
  109. package/src/components/RichTextEditor/plugins/index.ts +6 -0
  110. package/src/components/RichTextEditor/plugins/shared/LinkSelector/DocumentLink.tsx +80 -0
  111. package/src/components/RichTextEditor/plugins/shared/LinkSelector/DocumentLinks.tsx +97 -0
  112. package/src/components/RichTextEditor/plugins/shared/LinkSelector/LinkSelector.spec.ct.tsx +138 -0
  113. package/src/components/RichTextEditor/plugins/shared/LinkSelector/LinkSelector.tsx +80 -0
  114. package/src/components/RichTextEditor/plugins/shared/LinkSelector/PageLink.tsx +83 -0
  115. package/src/components/RichTextEditor/plugins/shared/LinkSelector/PageLinks.tsx +68 -0
  116. package/src/components/RichTextEditor/plugins/shared/LinkSelector/SectionLink.tsx +37 -0
  117. package/src/components/RichTextEditor/plugins/shared/LinkSelector/index.ts +3 -0
  118. package/src/components/RichTextEditor/plugins/styles.ts +179 -0
  119. package/src/components/RichTextEditor/serializer/index.ts +3 -0
  120. package/src/components/RichTextEditor/serializer/nodes/button.ts +25 -0
  121. package/src/components/RichTextEditor/serializer/nodes/checkItemNode.ts +29 -0
  122. package/src/components/RichTextEditor/serializer/nodes/default.ts +52 -0
  123. package/src/components/RichTextEditor/serializer/nodes/link.ts +25 -0
  124. package/src/components/RichTextEditor/serializer/nodes/mentionHtmlNode.ts +17 -0
  125. package/src/components/RichTextEditor/serializer/serializeNodesToHtmlRecursive.ts +134 -0
  126. package/src/components/RichTextEditor/serializer/serializeToHtml.ts +49 -0
  127. package/src/components/RichTextEditor/serializer/utlis/reactCssPropsToCss.ts +21 -0
  128. package/src/components/RichTextEditor/serializer/utlis/serializeLeafToHtml.ts +32 -0
  129. package/src/components/RichTextEditor/types.ts +23 -0
  130. package/src/components/index.ts +7 -0
  131. package/src/helpers/addHttps.spec.ts +42 -0
  132. package/src/helpers/addHttps.ts +15 -0
  133. package/src/helpers/convertToRichTextValue.spec.ts +32 -0
  134. package/src/helpers/convertToRichTextValue.ts +6 -0
  135. package/src/helpers/customCoordinatesGetterFactory.spec.ts +69 -0
  136. package/src/helpers/customCoordinatesGetterFactory.ts +39 -0
  137. package/src/helpers/hasRichTextValue.spec.ts +63 -0
  138. package/src/helpers/hasRichTextValue.ts +29 -0
  139. package/src/helpers/index.ts +8 -0
  140. package/src/helpers/isDownloadable.spec.ts +47 -0
  141. package/src/helpers/isDownloadable.ts +7 -0
  142. package/src/helpers/mapColorPalettes.spec.ts +146 -0
  143. package/src/helpers/mapColorPalettes.ts +22 -0
  144. package/src/hooks/index.ts +4 -0
  145. package/src/hooks/useAttachments.spec.ts +79 -0
  146. package/src/hooks/useAttachments.ts +46 -0
  147. package/src/hooks/useDndSensors.spec.ts +40 -0
  148. package/src/hooks/useDndSensors.ts +23 -0
  149. package/src/index.ts +8 -0
  150. package/src/settings/background.spec.ts +173 -0
  151. package/src/settings/background.ts +49 -0
  152. package/src/settings/border.spec.ts +76 -0
  153. package/src/settings/border.ts +90 -0
  154. package/src/settings/borderRadius.spec.ts +30 -0
  155. package/src/settings/borderRadius.ts +73 -0
  156. package/src/settings/borderRadiusExtended.spec.ts +52 -0
  157. package/src/settings/borderRadiusExtended.ts +84 -0
  158. package/src/settings/defaultValues.ts +21 -0
  159. package/src/settings/gutter.spec.ts +60 -0
  160. package/src/settings/gutter.ts +75 -0
  161. package/src/settings/index.ts +14 -0
  162. package/src/settings/margin.spec.ts +42 -0
  163. package/src/settings/margin.ts +72 -0
  164. package/src/settings/marginExtended.spec.ts +45 -0
  165. package/src/settings/marginExtended.ts +91 -0
  166. package/src/settings/padding.spec.ts +42 -0
  167. package/src/settings/padding.ts +73 -0
  168. package/src/settings/paddingExtended.spec.ts +45 -0
  169. package/src/settings/paddingExtended.ts +91 -0
  170. package/src/settings/security.spec.ts +87 -0
  171. package/src/settings/security.ts +61 -0
  172. package/src/settings/securityDownloadable.spec.ts +46 -0
  173. package/src/settings/securityDownloadable.ts +33 -0
  174. package/src/settings/securityGlobalControl.ts +42 -0
  175. package/src/settings/types.ts +128 -0
  176. package/src/utilities/color/getReadableColor.spec.ts +32 -0
  177. package/src/utilities/color/getReadableColor.ts +34 -0
  178. package/src/utilities/color/index.ts +10 -0
  179. package/src/utilities/color/isDark.spec.ts +33 -0
  180. package/src/utilities/color/isDark.ts +29 -0
  181. package/src/utilities/color/setAlpha.spec.ts +28 -0
  182. package/src/utilities/color/setAlpha.ts +14 -0
  183. package/src/utilities/color/toColorObject.spec.ts +19 -0
  184. package/src/utilities/color/toColorObject.ts +16 -0
  185. package/src/utilities/color/toHex8String.spec.ts +17 -0
  186. package/src/utilities/color/toHex8String.ts +14 -0
  187. package/src/utilities/color/toHexString.spec.ts +17 -0
  188. package/src/utilities/color/toHexString.ts +10 -0
  189. package/src/utilities/color/toRgbaString.spec.ts +12 -0
  190. package/src/utilities/color/toRgbaString.ts +14 -0
  191. package/src/utilities/color/toShortRgba.spec.ts +16 -0
  192. package/src/utilities/color/toShortRgba.ts +35 -0
  193. package/src/utilities/index.ts +5 -0
  194. package/src/utilities/moveItemInArray.spec.ts +17 -0
  195. package/src/utilities/moveItemInArray.ts +21 -0
  196. package/src/utilities/react/getBackgroundColorStyles.spec.ts +18 -0
  197. package/src/utilities/react/getBackgroundColorStyles.ts +11 -0
  198. package/src/utilities/react/getBorderStyles.spec.ts +39 -0
  199. package/src/utilities/react/getBorderStyles.ts +21 -0
  200. package/src/utilities/react/getRadiusStyles.spec.ts +25 -0
  201. package/src/utilities/react/getRadiusStyles.ts +8 -0
  202. package/src/utilities/react/index.ts +6 -0
  203. package/src/utilities/react/joinClassNames.spec.ts +18 -0
  204. package/src/utilities/react/joinClassNames.ts +10 -0
  205. package/tailwind.config.js +27 -0
  206. package/tsconfig.json +3 -1
  207. package/vite.config.ts +11 -1
  208. package/dist/index.cjs.js +0 -2
  209. package/dist/index.cjs.js.map +0 -1
  210. package/dist/index.d.ts +0 -147
  211. package/dist/index.es.js +0 -9
  212. package/dist/index.es.js.map +0 -1
  213. package/dist/index.umd.js +0 -2
  214. package/dist/index.umd.js.map +0 -1
@@ -0,0 +1,80 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { AppBridgeBlock } from '@frontify/app-bridge';
4
+ import { useEffect, useState } from 'react';
5
+ import { PageLinks } from './PageLinks';
6
+ import { IconColorFan16, merge } from '@frontify/fondue';
7
+ import { InitiallyExpandedItems } from '../../LinkPlugin/FloatingLink/InsertLinkModal/types';
8
+
9
+ type DocumentLinkProps = {
10
+ document: {
11
+ id: number;
12
+ title: string;
13
+ permanentLink: string;
14
+ };
15
+ appBridge: AppBridgeBlock;
16
+ selectedUrl: string;
17
+ onSelectUrl: (url: string) => void;
18
+ itemsToExpandInitially: InitiallyExpandedItems;
19
+ };
20
+
21
+ export const DocumentLink = ({
22
+ document,
23
+ appBridge,
24
+ selectedUrl,
25
+ onSelectUrl,
26
+ itemsToExpandInitially,
27
+ }: DocumentLinkProps) => {
28
+ const [isExpanded, setIsExpanded] = useState(document.id === itemsToExpandInitially.documentId);
29
+ const isActive = document.permanentLink === selectedUrl;
30
+
31
+ useEffect(() => {
32
+ if (document.id === itemsToExpandInitially.documentId) {
33
+ setIsExpanded(true);
34
+ }
35
+ }, [itemsToExpandInitially, document.id]);
36
+
37
+ return (
38
+ <>
39
+ <button
40
+ data-test-id="internal-link-selector-document-link"
41
+ className={merge([
42
+ 'tw-flex tw-flex-1 tw-space-x-2 tw-items-center tw-py-2 tw-px-2.5 tw-leading-5 tw-cursor-pointer tw-w-full',
43
+ isActive
44
+ ? 'tw-bg-box-selected-strong tw-text-box-selected-strong-inverse hover:tw-bg-box-selected-strong-hover:hover hover:tw-text-box-selected-strong-inverse-hover:hover'
45
+ : 'hover:tw-bg-box-neutral-hover hover:tw-text-box-neutral-inverse-hover',
46
+ ])}
47
+ onClick={() => onSelectUrl(document.permanentLink)}
48
+ onFocus={() => onSelectUrl(document.permanentLink)}
49
+ >
50
+ <div
51
+ role="button"
52
+ tabIndex={0}
53
+ data-test-id="tree-item-toggle"
54
+ className="tw-flex tw-items-center tw-justify-center tw-p-1.5 tw-cursor-pointer"
55
+ onClick={() => setIsExpanded(!isExpanded)}
56
+ onFocus={() => setIsExpanded(!isExpanded)}
57
+ >
58
+ <div
59
+ className={merge([
60
+ 'tw-transition-transform tw-w-0 tw-h-0 tw-font-normal tw-border-t-4 tw-border-t-transparent tw-border-b-4 tw-border-b-transparent tw-border-l-4 tw-border-l-x-strong',
61
+ isExpanded ? 'tw-rotate-90' : '',
62
+ ])}
63
+ ></div>
64
+ </div>
65
+ <IconColorFan16 />
66
+ <span className="tw-text-s">{document.title}</span>
67
+ <span className="tw-flex-auto tw-font-sans tw-text-xs tw-text-right">Document</span>
68
+ </button>
69
+ {isExpanded && (
70
+ <PageLinks
71
+ appBridge={appBridge}
72
+ documentId={document.id}
73
+ selectedUrl={selectedUrl}
74
+ onSelectUrl={onSelectUrl}
75
+ itemsToExpandInitially={itemsToExpandInitially}
76
+ />
77
+ )}
78
+ </>
79
+ );
80
+ };
@@ -0,0 +1,97 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { AppBridgeBlock, Document } from '@frontify/app-bridge';
4
+ import { LoadingCircle } from '@frontify/fondue';
5
+ import { ReactElement, useEffect, useState } from 'react';
6
+ import { InitiallyExpandedItems } from '../../LinkPlugin/FloatingLink/InsertLinkModal/types';
7
+ import { DocumentLink } from './DocumentLink';
8
+
9
+ type DocumentLinksProps = {
10
+ appBridge: AppBridgeBlock;
11
+ selectedUrl: string;
12
+ onSelectUrl: (url: string) => void;
13
+ };
14
+
15
+ export const DocumentLinks = ({ appBridge, selectedUrl, onSelectUrl }: DocumentLinksProps): ReactElement => {
16
+ const [isLoading, setIsLoading] = useState(true);
17
+ const [documents, setDocuments] = useState<Document[]>([]);
18
+ const [itemsToExpandInitially, setItemsToExpandInitially] = useState<InitiallyExpandedItems>({
19
+ documentId: undefined,
20
+ pageId: undefined,
21
+ });
22
+
23
+ const documentArray = [...documents.values()];
24
+
25
+ useEffect(() => {
26
+ if (selectedUrl && documentArray.length > 0) {
27
+ findLocationOfSelectedUrl().then((items) => {
28
+ setItemsToExpandInitially(items);
29
+ });
30
+ }
31
+ // eslint-disable-next-line react-hooks/exhaustive-deps
32
+ }, [documentArray.length]);
33
+
34
+ useEffect(() => {
35
+ appBridge
36
+ .getAllDocuments()
37
+ .then((_documents) => {
38
+ setDocuments(_documents);
39
+ })
40
+ .finally(() => {
41
+ setIsLoading(false);
42
+ });
43
+ }, []);
44
+
45
+ const findLocationOfSelectedUrl = async () => {
46
+ const itemsToExpand: InitiallyExpandedItems = {
47
+ documentId: undefined,
48
+ pageId: undefined,
49
+ };
50
+ const selectedUrlIsDocument = documentArray.find((document) => document.permanentLink === selectedUrl);
51
+ if (selectedUrlIsDocument) {
52
+ return itemsToExpand;
53
+ }
54
+ for (const document of documentArray) {
55
+ const pages = await appBridge.getDocumentPagesByDocumentId(document.id);
56
+ appBridge.getAllDocuments();
57
+ const pagesArray = [...pages.values()];
58
+ const selectedUrlIsPage = !!pagesArray.find((page) => page.permanentLink === selectedUrl);
59
+ if (selectedUrlIsPage) {
60
+ itemsToExpand.documentId = document.id;
61
+ return itemsToExpand;
62
+ }
63
+ for (const page of pagesArray) {
64
+ const sections = await appBridge.getDocumentSectionsByDocumentPageId(page.id);
65
+ const sectionsArray = [...sections.values()];
66
+ const selectedUrlIsSection = !!sectionsArray.find((section) => section.permanentLink === selectedUrl);
67
+ if (selectedUrlIsSection) {
68
+ itemsToExpand.documentId = document.id;
69
+ itemsToExpand.pageId = page.id;
70
+ return itemsToExpand;
71
+ }
72
+ }
73
+ }
74
+ return itemsToExpand;
75
+ };
76
+
77
+ return isLoading ? (
78
+ <div className="tw-flex tw-justify-center tw-p-4">
79
+ <LoadingCircle />
80
+ </div>
81
+ ) : (
82
+ <>
83
+ {documentArray.map((document) => {
84
+ return (
85
+ <DocumentLink
86
+ key={document.id}
87
+ document={document}
88
+ appBridge={appBridge}
89
+ selectedUrl={selectedUrl}
90
+ onSelectUrl={onSelectUrl}
91
+ itemsToExpandInitially={itemsToExpandInitially}
92
+ />
93
+ );
94
+ })}
95
+ </>
96
+ );
97
+ };
@@ -0,0 +1,138 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import {
4
+ DocumentApiDummy,
5
+ DocumentPageApiDummy,
6
+ DocumentSectionApiDummy,
7
+ getAppBridgeBlockStub,
8
+ withAppBridgeBlockStubs,
9
+ } from '@frontify/app-bridge';
10
+ import { mount } from 'cypress/react18';
11
+ import { LinkSelector } from './LinkSelector';
12
+ import { SinonStub } from 'cypress/types/sinon';
13
+
14
+ const LinkSelectorSelector = '[data-test-id="internal-link-selector"]';
15
+ const LinkSelectorButtonSelector = '[data-test-id="internal-link-selector"] > button';
16
+ const LinkSelectorModalSelector = '[data-test-id="modal-body"]';
17
+ const DocumentLinkSelector = '[data-test-id="internal-link-selector-document-link"]';
18
+ const DocumentTreeItemToggleSelector = '[data-test-id="tree-item-toggle"]';
19
+ const PageLinkSelector = '[data-test-id="internal-link-selector-page-link"]';
20
+ const SectionLinkSelector = '[data-test-id="internal-link-selector-section-link"]';
21
+
22
+ const apiDocuments = [
23
+ { ...DocumentApiDummy.with(1), permanentLink: '/1' },
24
+ { ...DocumentApiDummy.with(2), permanentLink: '/2' },
25
+ ];
26
+ const apiPages = [
27
+ { ...DocumentPageApiDummy.with(1), permanentLink: '/3' },
28
+ { ...DocumentPageApiDummy.with(2), permanentLink: '/4' },
29
+ { ...DocumentPageApiDummy.with(3), permanentLink: '/5' },
30
+ ];
31
+ const apiSections = [
32
+ { ...DocumentSectionApiDummy.with(1), permanentLink: '/6' },
33
+ { ...DocumentSectionApiDummy.with(2), permanentLink: '/7' },
34
+ { ...DocumentSectionApiDummy.with(3), permanentLink: '/8' },
35
+ { ...DocumentSectionApiDummy.with(4), permanentLink: '/9' },
36
+ ];
37
+
38
+ describe('Link Selector', () => {
39
+ it('renders the link selector button', () => {
40
+ const [LinkSelectorWithStubs] = withAppBridgeBlockStubs(LinkSelector, {});
41
+ mount(<LinkSelectorWithStubs url="" onUrlChange={cy.stub()} />);
42
+ cy.get(LinkSelectorSelector).should('exist');
43
+ });
44
+
45
+ it('opens the modal on button click', () => {
46
+ const appBridge = getAppBridgeBlockStub({
47
+ blockId: 1,
48
+ });
49
+ const apiDocuments = [DocumentApiDummy.with(1)];
50
+ (appBridge.getDocumentGroups as SinonStub) = cy.stub().returns([]);
51
+ (appBridge.getAllDocuments as SinonStub) = cy.stub().returns(Promise.resolve(apiDocuments));
52
+
53
+ mount(<LinkSelector appBridge={appBridge} url="" onUrlChange={cy.stub()} />);
54
+ cy.get(LinkSelectorButtonSelector).click();
55
+ cy.get(LinkSelectorModalSelector).should('exist');
56
+ });
57
+
58
+ it('renders two documents initially', () => {
59
+ const appBridge = getAppBridgeBlockStub({
60
+ blockId: 1,
61
+ });
62
+ (appBridge.getDocumentGroups as SinonStub) = cy.stub().returns([]);
63
+ (appBridge.getAllDocuments as SinonStub) = cy.stub().returns(Promise.resolve(apiDocuments));
64
+
65
+ mount(<LinkSelector appBridge={appBridge} url="" onUrlChange={cy.stub()} />);
66
+ cy.get(LinkSelectorButtonSelector).click();
67
+ cy.get(DocumentLinkSelector).should('have.length', 2);
68
+ });
69
+
70
+ it('renders three pages on document expand', () => {
71
+ const appBridge = getAppBridgeBlockStub({
72
+ blockId: 1,
73
+ });
74
+
75
+ (appBridge.getDocumentGroups as SinonStub) = cy.stub().returns([]);
76
+ (appBridge.getAllDocuments as SinonStub) = cy.stub().returns(Promise.resolve(apiDocuments));
77
+ (appBridge.getDocumentPagesByDocumentId as SinonStub) = cy.stub().returns(Promise.resolve(apiPages));
78
+ (appBridge.getDocumentSectionsByDocumentPageId as SinonStub) = cy.stub().returns(Promise.resolve(apiSections));
79
+
80
+ mount(<LinkSelector appBridge={appBridge} url="" onUrlChange={cy.stub()} />);
81
+ cy.get(LinkSelectorButtonSelector).click();
82
+ cy.get(DocumentLinkSelector).eq(0).find(DocumentTreeItemToggleSelector).click();
83
+ cy.get(PageLinkSelector).should('have.length', 3);
84
+ });
85
+
86
+ it('renders four sections on page expand', () => {
87
+ const appBridge = getAppBridgeBlockStub({
88
+ blockId: 1,
89
+ });
90
+ (appBridge.getDocumentGroups as SinonStub) = cy.stub().returns([]);
91
+ (appBridge.getAllDocuments as SinonStub) = cy.stub().returns(Promise.resolve(apiDocuments));
92
+ (appBridge.getDocumentPagesByDocumentId as SinonStub) = cy.stub().returns(Promise.resolve(apiPages));
93
+ (appBridge.getDocumentSectionsByDocumentPageId as SinonStub) = cy.stub().returns(Promise.resolve(apiSections));
94
+
95
+ mount(<LinkSelector appBridge={appBridge} url="" onUrlChange={cy.stub()} />);
96
+ cy.get(LinkSelectorButtonSelector).click();
97
+ cy.get(DocumentLinkSelector).eq(0).find(DocumentTreeItemToggleSelector).click();
98
+ cy.get(PageLinkSelector).eq(0).find('button').click();
99
+ cy.get(SectionLinkSelector).should('have.length', 4);
100
+ });
101
+
102
+ it('renders the selected section immediately if it is preselected', () => {
103
+ const appBridge = getAppBridgeBlockStub({
104
+ blockId: 1,
105
+ });
106
+ (appBridge.getDocumentGroups as SinonStub) = cy.stub().returns([]);
107
+ (appBridge.getAllDocuments as SinonStub) = cy.stub().returns(Promise.resolve(apiDocuments));
108
+ (appBridge.getDocumentPagesByDocumentId as SinonStub) = cy.stub().returns(Promise.resolve(apiPages));
109
+ (appBridge.getDocumentSectionsByDocumentPageId as SinonStub) = cy.stub().returns(Promise.resolve(apiSections));
110
+
111
+ mount(<LinkSelector appBridge={appBridge} url="/7" onUrlChange={cy.stub()} />);
112
+ cy.get(LinkSelectorButtonSelector).click();
113
+ cy.get(SectionLinkSelector).should('have.length', 4);
114
+ });
115
+
116
+ it('renders the all section and they are shown on focus and stores if you press enter', () => {
117
+ const appBridge = getAppBridgeBlockStub({
118
+ blockId: 1,
119
+ });
120
+ (appBridge.getDocumentGroups as SinonStub) = cy.stub().returns([]);
121
+ (appBridge.getAllDocuments as SinonStub) = cy.stub().returns(Promise.resolve(apiDocuments));
122
+ (appBridge.getDocumentPagesByDocumentId as SinonStub) = cy.stub().returns(Promise.resolve(apiPages));
123
+ (appBridge.getDocumentSectionsByDocumentPageId as SinonStub) = cy.stub().returns(Promise.resolve(apiSections));
124
+
125
+ mount(<LinkSelector appBridge={appBridge} url="" onUrlChange={cy.stub().as('urlChange')} />);
126
+ cy.get(LinkSelectorButtonSelector).click();
127
+ cy.get(DocumentLinkSelector).should('have.length', 2);
128
+ cy.realPress('Tab');
129
+ cy.realPress('Tab');
130
+ cy.get(PageLinkSelector).should('have.length', 3);
131
+ cy.realPress('Tab');
132
+ cy.realPress('Tab');
133
+ cy.get(SectionLinkSelector).should('have.length', 4);
134
+ cy.realPress('Tab');
135
+ cy.realPress('Enter');
136
+ cy.get('@urlChange').should('be.calledWith', '/6');
137
+ });
138
+ });
@@ -0,0 +1,80 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { AppBridgeBlock } from '@frontify/app-bridge';
4
+ import { Button, ButtonEmphasis, ButtonSize, ButtonStyle, ButtonType, IconLink, Modal } from '@frontify/fondue';
5
+ import { useOverlayTriggerState } from '@react-stately/overlays';
6
+ import { KeyboardEvent, ReactElement, useEffect, useState } from 'react';
7
+ import { DocumentLinks } from './DocumentLinks';
8
+
9
+ type LinkSelectorProps = {
10
+ appBridge: AppBridgeBlock;
11
+ url: string;
12
+ onUrlChange: (value: string) => void;
13
+ };
14
+
15
+ export const LinkSelector = ({ appBridge, url, onUrlChange }: LinkSelectorProps): ReactElement => {
16
+ const { open: openLinkTree, isOpen: isLinkTreeOpen, close: closeLinkTree } = useOverlayTriggerState({});
17
+ const [selectedUrl, setSelectedUrl] = useState<string>(url);
18
+
19
+ const onSelectUrl = (url: string) => {
20
+ setSelectedUrl(url);
21
+ };
22
+
23
+ const onPressEnter = (event: KeyboardEvent<HTMLInputElement>) => {
24
+ if (event.key === 'Enter') {
25
+ saveLink();
26
+ }
27
+ };
28
+
29
+ useEffect(() => {
30
+ if (url && !selectedUrl) {
31
+ setSelectedUrl(url);
32
+ }
33
+ }, [url, selectedUrl]);
34
+
35
+ const saveLink = () => {
36
+ onUrlChange(selectedUrl);
37
+ closeLinkTree();
38
+ };
39
+
40
+ return (
41
+ <div data-test-id="internal-link-selector" onKeyDown={onPressEnter}>
42
+ <Button
43
+ icon={<IconLink />}
44
+ size={ButtonSize.Medium}
45
+ type={ButtonType.Button}
46
+ style={ButtonStyle.Default}
47
+ emphasis={ButtonEmphasis.Default}
48
+ onClick={() => openLinkTree()}
49
+ >
50
+ Internal link
51
+ </Button>
52
+ <Modal zIndex={1001} onClose={() => closeLinkTree()} isOpen={isLinkTreeOpen} isDismissable>
53
+ <Modal.Header title="Select internal link" />
54
+ <Modal.Body>
55
+ <DocumentLinks appBridge={appBridge} selectedUrl={selectedUrl} onSelectUrl={onSelectUrl} />
56
+ </Modal.Body>
57
+ <Modal.Footer
58
+ buttons={[
59
+ {
60
+ children: 'Cancel',
61
+ onClick: () => closeLinkTree(),
62
+ style: ButtonStyle.Default,
63
+ emphasis: ButtonEmphasis.Default,
64
+ },
65
+ {
66
+ children: 'Choose',
67
+ onClick: (event) => {
68
+ event?.preventDefault();
69
+ saveLink();
70
+ },
71
+ style: ButtonStyle.Default,
72
+ emphasis: ButtonEmphasis.Strong,
73
+ disabled: !selectedUrl,
74
+ },
75
+ ]}
76
+ />
77
+ </Modal>
78
+ </div>
79
+ );
80
+ };
@@ -0,0 +1,83 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { AppBridgeBlock, useDocumentSection } from '@frontify/app-bridge';
4
+ import { merge } from '@frontify/fondue';
5
+ import { useEffect, useState } from 'react';
6
+ import { InitiallyExpandedItems } from '../../LinkPlugin/FloatingLink/InsertLinkModal/types';
7
+ import { SectionLink } from './SectionLink';
8
+
9
+ type DocumentLinkProps = {
10
+ page: {
11
+ id: number;
12
+ title: string;
13
+ permanentLink: string;
14
+ };
15
+ selectedUrl: string;
16
+ onSelectUrl: (url: string) => void;
17
+ appBridge: AppBridgeBlock;
18
+ itemsToExpandInitially: InitiallyExpandedItems;
19
+ };
20
+
21
+ export const PageLink = ({ page, selectedUrl, onSelectUrl, itemsToExpandInitially, appBridge }: DocumentLinkProps) => {
22
+ const [isExpanded, setIsExpanded] = useState(page.id === itemsToExpandInitially.documentId);
23
+ const isActive = page.permanentLink === selectedUrl;
24
+ const { documentSections } = useDocumentSection(appBridge, page.id);
25
+ const sectionsArray = [...documentSections.values()];
26
+ const hasSections = sectionsArray.length > 0;
27
+
28
+ useEffect(() => {
29
+ if (page.id === itemsToExpandInitially.pageId) {
30
+ setIsExpanded(true);
31
+ }
32
+ }, [itemsToExpandInitially, page.id]);
33
+
34
+ return (
35
+ <>
36
+ <div
37
+ tabIndex={0}
38
+ data-test-id="internal-link-selector-page-link"
39
+ className={merge([
40
+ 'tw-py-2 tw-px-2.5 tw-leading-5 tw-cursor-pointer',
41
+ hasSections ? 'tw-pl-7' : 'tw-pl-12',
42
+ isActive
43
+ ? 'tw-bg-box-selected-strong tw-text-box-selected-strong-inverse hover:tw-bg-box-selected-strong-hover:hover hover:tw-text-box-selected-strong-inverse-hover:hover'
44
+ : 'hover:tw-bg-box-neutral-hover hover:tw-text-box-neutral-inverse-hover',
45
+ ])}
46
+ onClick={() => onSelectUrl(page.permanentLink)}
47
+ onFocus={() => onSelectUrl(page.permanentLink)}
48
+ >
49
+ <div key={page.id} className="tw-flex tw-flex-1 tw-space-x-1 tw-items-center tw-h-6">
50
+ {hasSections && (
51
+ <button
52
+ data-test-id="tree-item-toggle"
53
+ className="tw-flex tw-items-center tw-justify-center tw-p-1.5 tw-cursor-pointer"
54
+ onClick={() => setIsExpanded(!isExpanded)}
55
+ onFocus={() => setIsExpanded(!isExpanded)}
56
+ >
57
+ <div
58
+ className={merge([
59
+ 'tw-transition-transform tw-w-0 tw-h-0 tw-font-normal tw-border-t-4 tw-border-t-transparent tw-border-b-4 tw-border-b-transparent tw-border-l-4 tw-border-l-x-strong',
60
+ isExpanded ? 'tw-rotate-90' : '',
61
+ ])}
62
+ ></div>
63
+ </button>
64
+ )}
65
+ <span className="tw-text-s">{page.title}</span>
66
+ <span className="tw-flex-auto tw-font-sans tw-text-xs tw-text-right">Page</span>
67
+ </div>
68
+ </div>
69
+ {isExpanded &&
70
+ sectionsArray.length > 0 &&
71
+ sectionsArray.map((section) => {
72
+ return (
73
+ <SectionLink
74
+ key={section.id}
75
+ section={section}
76
+ selectedUrl={selectedUrl}
77
+ onSelectUrl={onSelectUrl}
78
+ />
79
+ );
80
+ })}
81
+ </>
82
+ );
83
+ };
@@ -0,0 +1,68 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { AppBridgeBlock, DocumentPage } from '@frontify/app-bridge';
4
+ import { LoadingCircle } from '@frontify/fondue';
5
+ import { ReactElement, useEffect, useState } from 'react';
6
+ import { InitiallyExpandedItems } from '../../LinkPlugin/FloatingLink/InsertLinkModal/types';
7
+ import { PageLink } from './PageLink';
8
+
9
+ type PageLinksProps = {
10
+ appBridge: AppBridgeBlock;
11
+ documentId: number;
12
+ selectedUrl: string;
13
+ onSelectUrl: (url: string) => void;
14
+ itemsToExpandInitially: InitiallyExpandedItems;
15
+ };
16
+
17
+ export const PageLinks = ({
18
+ appBridge,
19
+ documentId,
20
+ selectedUrl,
21
+ onSelectUrl,
22
+ itemsToExpandInitially,
23
+ }: PageLinksProps): ReactElement => {
24
+ const [pages, setPages] = useState<DocumentPage[]>([]);
25
+ const [isLoading, setIsLoading] = useState(true);
26
+ const pagesArray = [...pages.values()];
27
+ const hasPages = !isLoading && pagesArray.length > 0;
28
+
29
+ useEffect(() => {
30
+ appBridge
31
+ .getDocumentPagesByDocumentId(documentId)
32
+ .then((_pages) => {
33
+ setPages(_pages);
34
+ })
35
+ .finally(() => {
36
+ setIsLoading(false);
37
+ });
38
+ }, []);
39
+
40
+ if (isLoading) {
41
+ return (
42
+ <div className="tw-flex tw-justify-center tw-p-4">
43
+ <LoadingCircle />
44
+ </div>
45
+ );
46
+ }
47
+
48
+ return hasPages ? (
49
+ <>
50
+ {pagesArray.map((page) => {
51
+ return (
52
+ <PageLink
53
+ key={page.id}
54
+ page={page}
55
+ appBridge={appBridge}
56
+ selectedUrl={selectedUrl}
57
+ onSelectUrl={onSelectUrl}
58
+ itemsToExpandInitially={itemsToExpandInitially}
59
+ />
60
+ );
61
+ })}
62
+ </>
63
+ ) : (
64
+ <div className="tw-py-2 tw-px-2.5 tw-pl-7 tw-leading-5 tw-text-s tw-text-text-weak">
65
+ This document does not contain any pages.
66
+ </div>
67
+ );
68
+ };
@@ -0,0 +1,37 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ import { IconDocumentText16, merge } from '@frontify/fondue';
4
+
5
+ type SectionLinkProps = {
6
+ section: {
7
+ id: number;
8
+ title: string;
9
+ permanentLink: string;
10
+ };
11
+ selectedUrl: string;
12
+ onSelectUrl: (url: string) => void;
13
+ };
14
+
15
+ export const SectionLink = ({ section, selectedUrl, onSelectUrl }: SectionLinkProps) => {
16
+ const isActive = section.permanentLink === selectedUrl;
17
+
18
+ return (
19
+ <button
20
+ data-test-id="internal-link-selector-section-link"
21
+ className={merge([
22
+ 'tw-py-2 tw-px-2.5 tw-pl-14 tw-leading-5 tw-cursor-pointer tw-w-full',
23
+ isActive
24
+ ? 'tw-bg-box-selected-strong tw-text-box-selected-strong-inverse hover:tw-bg-box-selected-strong-hover:hover hover:tw-text-box-selected-strong-inverse-hover:hover'
25
+ : 'hover:tw-bg-box-neutral-hover hover:tw-text-box-neutral-inverse-hover',
26
+ ])}
27
+ onClick={() => onSelectUrl(section.permanentLink)}
28
+ onFocus={() => onSelectUrl(section.permanentLink)}
29
+ >
30
+ <div className="tw-flex tw-flex-1 tw-space-x-2 tw-items-center tw-h-6">
31
+ <IconDocumentText16 />
32
+ <span className="tw-text-s">{section.title}</span>
33
+ <span className="tw-flex-auto tw-font-sans tw-text-xs tw-text-right">Section</span>
34
+ </div>
35
+ </button>
36
+ );
37
+ };
@@ -0,0 +1,3 @@
1
+ /* (c) Copyright Frontify Ltd., all rights reserved. */
2
+
3
+ export * from './LinkSelector';