@intlayer/design-system 9.3.2 → 9.4.0-canary.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.
- package/README.md +1 -1
- package/dist/esm/api/hooks/analytics.mjs +6 -5
- package/dist/esm/api/hooks/analytics.mjs.map +1 -1
- package/dist/esm/api/hooks/auth.mjs +2 -0
- package/dist/esm/api/hooks/auth.mjs.map +1 -1
- package/dist/esm/api/index.mjs +2 -2
- package/dist/esm/api/useAuth/useOAuth2.mjs +1 -1
- package/dist/esm/api/useAuth/useSession.mjs +1 -1
- package/dist/esm/components/Accordion/Accordion.mjs +2 -2
- package/dist/esm/components/Accordion/Accordion.mjs.map +1 -1
- package/dist/esm/components/Browser/Browser.mjs +1 -1
- package/dist/esm/components/Browser/Browser.mjs.map +1 -1
- package/dist/esm/components/ContentEditor/ContentEditorTextArea.mjs +1 -1
- package/dist/esm/components/CopyButton/index.mjs +2 -1
- package/dist/esm/components/CopyButton/index.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs +4 -5
- package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs +6 -10
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/DictionaryCreationForm.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs +6 -6
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs +3 -7
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs +2 -2
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs +1 -1
- package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs +19 -12
- package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs.map +1 -1
- package/dist/esm/components/Form/elements/OTPElement.mjs +4 -4
- package/dist/esm/components/Form/elements/OTPElement.mjs.map +1 -1
- package/dist/esm/components/HTMLRender/HTMLRender.mjs +2 -2
- package/dist/esm/components/HTMLRender/HTMLRender.mjs.map +1 -1
- package/dist/esm/components/IDE/Code.mjs +3 -3
- package/dist/esm/components/IDE/Code.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockClient.mjs +16 -4
- package/dist/esm/components/IDE/CodeBlockClient.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockHighlight.mjs +14 -5
- package/dist/esm/components/IDE/CodeBlockHighlight.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockServer.mjs +5 -3
- package/dist/esm/components/IDE/CodeBlockServer.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockShiki.mjs +18 -18
- package/dist/esm/components/IDE/CodeBlockShiki.mjs.map +1 -1
- package/dist/esm/components/IDE/IDE.mjs +2 -5
- package/dist/esm/components/IDE/IDE.mjs.map +1 -1
- package/dist/esm/components/IDE/MarkDownRender.mjs +1 -2
- package/dist/esm/components/IDE/MarkDownRender.mjs.map +1 -1
- package/dist/esm/components/IDE/index.mjs +2 -2
- package/dist/esm/components/IDE/shikiThemes.mjs +18 -0
- package/dist/esm/components/IDE/shikiThemes.mjs.map +1 -0
- package/dist/esm/components/Link/Link.mjs +10 -5
- package/dist/esm/components/Link/Link.mjs.map +1 -1
- package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs +105 -4
- package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs.map +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs +16 -16
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs.map +1 -1
- package/dist/esm/components/MaxHeightSmoother/index.mjs +1 -1
- package/dist/esm/components/MaxHeightSmoother/index.mjs.map +1 -1
- package/dist/esm/components/MaxWidthSmoother/index.mjs +1 -1
- package/dist/esm/components/MaxWidthSmoother/index.mjs.map +1 -1
- package/dist/esm/components/Modal/Modal.mjs +2 -2
- package/dist/esm/components/Navbar/MobileNavbar.mjs +1 -1
- package/dist/esm/components/Pagination/Pagination.mjs +4 -4
- package/dist/esm/components/Pagination/Pagination.mjs.map +1 -1
- package/dist/esm/components/Pattern/GridDistortionPattern.mjs +103 -17
- package/dist/esm/components/Pattern/GridDistortionPattern.mjs.map +1 -1
- package/dist/esm/components/Popover/dynamic.mjs +73 -28
- package/dist/esm/components/Popover/dynamic.mjs.map +1 -1
- package/dist/esm/components/Popover/index.mjs +2 -2
- package/dist/esm/components/Popover/static.mjs +47 -18
- package/dist/esm/components/Popover/static.mjs.map +1 -1
- package/dist/esm/components/RightDrawer/RightDrawer.mjs +5 -4
- package/dist/esm/components/RightDrawer/RightDrawer.mjs.map +1 -1
- package/dist/esm/components/SwitchSelector/useSwitchSelector.mjs +2 -2
- package/dist/esm/components/SwitchSelector/useSwitchSelector.mjs.map +1 -1
- package/dist/esm/components/Tab/Tab.mjs +38 -17
- package/dist/esm/components/Tab/Tab.mjs.map +1 -1
- package/dist/esm/components/TabSelector/TabSelector.mjs +2 -2
- package/dist/esm/components/TabSelector/TabSelector.mjs.map +1 -1
- package/dist/esm/components/TextArea/AutoSizeTextArea.mjs +3 -2
- package/dist/esm/components/TextArea/AutoSizeTextArea.mjs.map +1 -1
- package/dist/esm/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs +10 -18
- package/dist/esm/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs.map +1 -1
- package/dist/esm/components/ThemeSwitcherDropDown/MobileThemeSwitcher.mjs +14 -13
- package/dist/esm/components/ThemeSwitcherDropDown/MobileThemeSwitcher.mjs.map +1 -1
- package/dist/esm/components/index.mjs +3 -3
- package/dist/esm/hooks/index.mjs +9 -9
- package/dist/esm/hooks/useDevice.mjs +16 -6
- package/dist/esm/hooks/useDevice.mjs.map +1 -1
- package/dist/esm/hooks/useItemSelector.mjs +110 -112
- package/dist/esm/hooks/useItemSelector.mjs.map +1 -1
- package/dist/esm/hooks/useScrollY.mjs +64 -45
- package/dist/esm/hooks/useScrollY.mjs.map +1 -1
- package/dist/esm/hooks/useStickToBottom.mjs +69 -0
- package/dist/esm/hooks/useStickToBottom.mjs.map +1 -0
- package/dist/esm/libs/auth.mjs +16 -3
- package/dist/esm/libs/auth.mjs.map +1 -1
- package/dist/esm/providers/ThemeProvider.mjs +143 -0
- package/dist/esm/providers/ThemeProvider.mjs.map +1 -0
- package/dist/esm/providers/index.mjs +2 -1
- package/dist/esm/routes.mjs +2 -1
- package/dist/esm/routes.mjs.map +1 -1
- package/dist/esm/structured-data/buildCreativeWorkJsonLd.mjs +1 -1
- package/dist/esm/structured-data/buildCreativeWorkJsonLd.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +3 -1
- package/dist/esm/utils/observeScrollExtent.mjs +39 -0
- package/dist/esm/utils/observeScrollExtent.mjs.map +1 -0
- package/dist/esm/utils/scheduleFrameTask.mjs +44 -0
- package/dist/esm/utils/scheduleFrameTask.mjs.map +1 -0
- package/dist/types/api/hooks/analytics.d.ts +5 -3
- package/dist/types/api/hooks/analytics.d.ts.map +1 -1
- package/dist/types/api/hooks/auth.d.ts.map +1 -1
- package/dist/types/components/Browser/Browser.d.ts +7 -1
- package/dist/types/components/Browser/Browser.d.ts.map +1 -1
- package/dist/types/components/CopyButton/index.d.ts +9 -3
- package/dist/types/components/CopyButton/index.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts +0 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts +0 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.d.ts +0 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.d.ts.map +1 -1
- package/dist/types/components/ExpandCollapse/ExpandCollapse.d.ts.map +1 -1
- package/dist/types/components/HTMLRender/HTMLRender.d.ts +1 -2
- package/dist/types/components/HTMLRender/HTMLRender.d.ts.map +1 -1
- package/dist/types/components/IDE/Code.d.ts +6 -1
- package/dist/types/components/IDE/Code.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockClient.d.ts +18 -2
- package/dist/types/components/IDE/CodeBlockClient.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockHighlight.d.ts +3 -2
- package/dist/types/components/IDE/CodeBlockHighlight.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockServer.d.ts +0 -1
- package/dist/types/components/IDE/CodeBlockServer.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockShiki.d.ts +0 -1
- package/dist/types/components/IDE/CodeBlockShiki.d.ts.map +1 -1
- package/dist/types/components/IDE/IDE.d.ts +0 -1
- package/dist/types/components/IDE/IDE.d.ts.map +1 -1
- package/dist/types/components/IDE/MarkDownRender.d.ts +0 -1
- package/dist/types/components/IDE/MarkDownRender.d.ts.map +1 -1
- package/dist/types/components/IDE/index.d.ts +2 -2
- package/dist/types/components/IDE/shikiThemes.d.ts +18 -0
- package/dist/types/components/IDE/shikiThemes.d.ts.map +1 -0
- package/dist/types/components/MarkDownRender/MarkDownIframe.d.ts.map +1 -1
- package/dist/types/components/MarkDownRender/MarkDownRender.d.ts +1 -2
- package/dist/types/components/MarkDownRender/MarkDownRender.d.ts.map +1 -1
- package/dist/types/components/Pattern/GridDistortionPattern.d.ts.map +1 -1
- package/dist/types/components/Popover/dynamic.d.ts.map +1 -1
- package/dist/types/components/Popover/index.d.ts +2 -2
- package/dist/types/components/Popover/static.d.ts +6 -1
- package/dist/types/components/Popover/static.d.ts.map +1 -1
- package/dist/types/components/Tab/Tab.d.ts +2 -2
- package/dist/types/components/Tab/Tab.d.ts.map +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.d.ts +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.d.ts.map +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/MobileThemeSwitcher.d.ts +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/MobileThemeSwitcher.d.ts.map +1 -1
- package/dist/types/components/ThemeSwitcherDropDown/types.d.ts +3 -1
- package/dist/types/components/ThemeSwitcherDropDown/types.d.ts.map +1 -1
- package/dist/types/components/index.d.ts +3 -3
- package/dist/types/hooks/index.d.ts +2 -2
- package/dist/types/hooks/useItemSelector.d.ts +2 -0
- package/dist/types/hooks/useItemSelector.d.ts.map +1 -1
- package/dist/types/hooks/useScrollY.d.ts +10 -0
- package/dist/types/hooks/useScrollY.d.ts.map +1 -1
- package/dist/types/hooks/useStickToBottom.d.ts +41 -0
- package/dist/types/hooks/useStickToBottom.d.ts.map +1 -0
- package/dist/types/libs/auth.d.ts +3 -0
- package/dist/types/libs/auth.d.ts.map +1 -1
- package/dist/types/providers/ThemeProvider.d.ts +41 -0
- package/dist/types/providers/ThemeProvider.d.ts.map +1 -0
- package/dist/types/providers/index.d.ts +2 -1
- package/dist/types/routes.d.ts +2 -1
- package/dist/types/routes.d.ts.map +1 -1
- package/dist/types/structured-data/buildCreativeWorkJsonLd.d.ts +9 -3
- package/dist/types/structured-data/buildCreativeWorkJsonLd.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts +3 -1
- package/dist/types/utils/observeScrollExtent.d.ts +22 -0
- package/dist/types/utils/observeScrollExtent.d.ts.map +1 -0
- package/dist/types/utils/scheduleFrameTask.d.ts +25 -0
- package/dist/types/utils/scheduleFrameTask.d.ts.map +1 -0
- package/package.json +46 -41
- package/tailwind.css +27 -1
- package/dist/esm/hooks/useIsDarkMode.mjs +0 -17
- package/dist/esm/hooks/useIsDarkMode.mjs.map +0 -1
- package/dist/types/hooks/useIsDarkMode.d.ts +0 -5
- package/dist/types/hooks/useIsDarkMode.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -278,4 +278,4 @@ Contribute on [GitHub](https://github.com/aymericzip/intlayer), [GitLab](https:/
|
|
|
278
278
|
|
|
279
279
|
If you like Intlayer, give us a ⭐ on GitHub. It helps others discover the project! [See why GitHub Stars matter](https://github.com/aymericzip/intlayer/blob/main/CONTRIBUTING.md#why-github-stars-matter-).
|
|
280
280
|
|
|
281
|
-
[](https://star-history.dera.page/#aymericzip/intlayer&Date)
|
|
@@ -6,21 +6,22 @@ import { useAppQuery } from "./utils.mjs";
|
|
|
6
6
|
//#region src/api/hooks/analytics.ts
|
|
7
7
|
/**
|
|
8
8
|
* Fetches the audience report for the active project: distinct visitors
|
|
9
|
-
* (today / 7d / window), page views, the
|
|
9
|
+
* (today / 7d / window), page views, the evolution series, and locale +
|
|
10
10
|
* country breakdowns.
|
|
11
11
|
*
|
|
12
|
-
* @param
|
|
12
|
+
* @param range - Rolling window, either a named range (`1h`, `24h`, `7d`,
|
|
13
|
+
* `30d`, `90d`, `6mo`, `1y`, `3y`) or a number of days (default 30).
|
|
13
14
|
* @param options - Extra react-query options.
|
|
14
15
|
*/
|
|
15
|
-
const useGetAnalyticsAudience = (
|
|
16
|
+
const useGetAnalyticsAudience = (range = 30, options) => {
|
|
16
17
|
const analyticsAPI = useAnalyticsAPI();
|
|
17
18
|
return useAppQuery({
|
|
18
19
|
queryKey: [
|
|
19
20
|
"analytics",
|
|
20
21
|
"audience",
|
|
21
|
-
|
|
22
|
+
range
|
|
22
23
|
],
|
|
23
|
-
queryFn: ({ signal }) => analyticsAPI.getAudience(
|
|
24
|
+
queryFn: ({ signal }) => analyticsAPI.getAudience(range, { signal }),
|
|
24
25
|
requireUser: true,
|
|
25
26
|
requireOrganization: true,
|
|
26
27
|
requireProject: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.mjs","names":[],"sources":["../../../../src/api/hooks/analytics.ts"],"sourcesContent":["'use client';\n\nimport type { UseQueryOptions } from '@tanstack/react-query';\nimport { useAnalyticsAPI } from '../useIntlayerAPI';\nimport { useAppQuery } from './utils';\n\n/**\n * Fetches the audience report for the active project: distinct visitors\n * (today / 7d / window), page views, the
|
|
1
|
+
{"version":3,"file":"analytics.mjs","names":[],"sources":["../../../../src/api/hooks/analytics.ts"],"sourcesContent":["'use client';\n\nimport type { AudienceRange } from '@intlayer/backend';\nimport type { UseQueryOptions } from '@tanstack/react-query';\nimport { useAnalyticsAPI } from '../useIntlayerAPI';\nimport { useAppQuery } from './utils';\n\n/**\n * Fetches the audience report for the active project: distinct visitors\n * (today / 7d / window), page views, the evolution series, and locale +\n * country breakdowns.\n *\n * @param range - Rolling window, either a named range (`1h`, `24h`, `7d`,\n * `30d`, `90d`, `6mo`, `1y`, `3y`) or a number of days (default 30).\n * @param options - Extra react-query options.\n */\nexport const useGetAnalyticsAudience = (\n range: AudienceRange | number = 30,\n options?: Partial<UseQueryOptions>\n) => {\n const analyticsAPI = useAnalyticsAPI();\n\n return useAppQuery({\n queryKey: ['analytics', 'audience', range],\n queryFn: ({ signal }) => analyticsAPI.getAudience(range, { signal }),\n requireUser: true,\n requireOrganization: true,\n requireProject: true,\n ...options,\n });\n};\n\n/**\n * Fetches per-content exposure totals — \"which content is actually shown\".\n *\n * @param options - Extra react-query options.\n */\nexport const useGetContentStats = (options?: Partial<UseQueryOptions>) => {\n const analyticsAPI = useAnalyticsAPI();\n\n return useAppQuery({\n queryKey: ['analytics', 'content-stats'],\n queryFn: ({ signal }) => analyticsAPI.getContentStats({ signal }),\n requireUser: true,\n requireOrganization: true,\n requireProject: true,\n ...options,\n });\n};\n\n/**\n * Fetches A/B experiment results for the active project.\n *\n * @param experimentKey - The experiment to evaluate.\n * @param options - Extra react-query options.\n */\nexport const useGetExperimentResults = (\n experimentKey: string,\n options?: Partial<UseQueryOptions>\n) => {\n const analyticsAPI = useAnalyticsAPI();\n\n return useAppQuery({\n queryKey: ['analytics', 'experiment', experimentKey],\n queryFn: ({ signal }) =>\n analyticsAPI.getExperimentResults(experimentKey, { signal }),\n requireUser: true,\n requireOrganization: true,\n requireProject: true,\n enabled: Boolean(experimentKey),\n ...options,\n });\n};\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,MAAa,2BACX,QAAgC,IAChC,YACG;CACH,MAAM,eAAe,gBAAgB;CAErC,OAAO,YAAY;EACjB,UAAU;GAAC;GAAa;GAAY;EAAK;EACzC,UAAU,EAAE,aAAa,aAAa,YAAY,OAAO,EAAE,OAAO,CAAC;EACnE,aAAa;EACb,qBAAqB;EACrB,gBAAgB;EAChB,GAAG;CACL,CAAC;AACH;;;;;;AAOA,MAAa,sBAAsB,YAAuC;CACxE,MAAM,eAAe,gBAAgB;CAErC,OAAO,YAAY;EACjB,UAAU,CAAC,aAAa,eAAe;EACvC,UAAU,EAAE,aAAa,aAAa,gBAAgB,EAAE,OAAO,CAAC;EAChE,aAAa;EACb,qBAAqB;EACrB,gBAAgB;EAChB,GAAG;CACL,CAAC;AACH;;;;;;;AAQA,MAAa,2BACX,eACA,YACG;CACH,MAAM,eAAe,gBAAgB;CAErC,OAAO,YAAY;EACjB,UAAU;GAAC;GAAa;GAAc;EAAa;EACnD,UAAU,EAAE,aACV,aAAa,qBAAqB,eAAe,EAAE,OAAO,CAAC;EAC7D,aAAa;EACb,qBAAqB;EACrB,gBAAgB;EAChB,SAAS,QAAQ,aAAa;EAC9B,GAAG;CACL,CAAC;AACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.mjs","names":[],"sources":["../../../../src/api/hooks/auth.ts"],"sourcesContent":["'use client';\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport type { AuthAPI } from '../../libs/auth';\nimport { useIntlayerAuth } from '../useIntlayerAPI';\n\nexport const useLogin = () => {\n const intlayerAuth = useIntlayerAuth();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationKey: ['login'],\n mutationFn: (args: Parameters<AuthAPI['signInEmail']>[0]) =>\n intlayerAuth.signInEmail(args),\n onSuccess: (data) => {\n const session = queryClient.getQueryData(['session']);\n\n queryClient.setQueryData(['session'], {\n ...session,\n user: data.data.user,\n });\n },\n });\n};\n\nexport const useListAccounts = () => {\n const intlayerAuth = useIntlayerAuth();\n return useQuery({\n queryKey: ['listAccounts'],\n queryFn: () => intlayerAuth.listAccounts(),\n });\n};\n\nexport const useUnlinkAccount = () => {\n const intlayerAuth = useIntlayerAuth();\n const queryClient = useQueryClient();\n return useMutation({\n mutationKey: ['unlinkAccount'],\n mutationFn: (args: Parameters<AuthAPI['unlinkAccount']>[0]) =>\n intlayerAuth.unlinkAccount(args),\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['listAccounts'] });\n },\n });\n};\n\nexport const useLinkSocial = () => {\n const intlayerAuth = useIntlayerAuth();\n const queryClient = useQueryClient();\n return useMutation({\n mutationKey: ['linkSocial'],\n mutationFn: (args: Parameters<AuthAPI['linkSocial']>[0]) =>\n intlayerAuth.linkSocial(args),\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['listAccounts'] });\n },\n });\n};\n\nexport const useGetVerifyEmailStatus = () => {\n const intlayerAuth = useIntlayerAuth();\n\n return useMutation({\n mutationKey: ['getVerifyEmailStatus'],\n mutationFn: (args: Parameters<AuthAPI['verifyEmailSession']>) =>\n intlayerAuth.verifyEmailSession(...args),\n });\n};\n\nexport const useRegister = () => {\n const intlayerAuth = useIntlayerAuth();\n\n return useMutation({\n mutationKey: ['register'],\n mutationFn: (args: Parameters<AuthAPI['signUpEmail']>[0]) =>\n intlayerAuth.signUpEmail(args),\n });\n};\n\nconst LOGOUT_QUERY_KEYS: string[][] = [\n ['session'],\n ['users'],\n ['organizations'],\n ['projects'],\n ['dictionaries'],\n ['tags'],\n];\n\nexport const useLogout = () => {\n const intlayerAuth = useIntlayerAuth();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationKey: ['logout'],\n mutationFn: () => intlayerAuth.signOut(),\n // removeQueries instead of resetQueries: evicts cached data without\n // triggering a re-fetch. A re-fetch (resetQueries) can race with the\n // signOut response and resurrect a session from better-auth's\n // short-lived cookie cache, causing an infinite redirect loop between\n // the dashboard and the login page.\n onSettled: () => {\n LOGOUT_QUERY_KEYS.forEach((queryKey) => {\n queryClient.removeQueries({ queryKey });\n });\n },\n });\n};\n\nexport const useChangePassword = () => {\n const intlayerAuth = useIntlayerAuth();\n\n return useMutation({\n mutationKey: ['changePassword'],\n mutationFn: (args: Parameters<AuthAPI['changePasswordSession']>) =>\n intlayerAuth.changePasswordSession(...args),\n });\n};\n\nexport const useAskResetPassword = () => {\n const intlayerAuth = useIntlayerAuth();\n\n return useMutation({\n mutationKey: ['askResetPassword'],\n mutationFn: (args: Parameters<AuthAPI['requestPasswordResetSession']>[0]) =>\n intlayerAuth.requestPasswordResetSession(args),\n });\n};\n\nexport const useResetPassword = () => {\n const intlayerAuth = useIntlayerAuth();\n\n return useMutation({\n mutationKey: ['resetPassword'],\n mutationFn: (args: Parameters<AuthAPI['resetPassword']>[0]) =>\n intlayerAuth.resetPassword(args),\n });\n};\n\nexport const useVerifyEmail = () => {\n const intlayerAuth = useIntlayerAuth();\n\n return useMutation({\n mutationKey: ['verifyEmail'],\n mutationFn: (args: Parameters<AuthAPI['verifyEmailSession']>[0]) =>\n intlayerAuth.verifyEmailSession(args),\n });\n};\n\nexport const useGetUserByAccount = () => {\n const intlayerAuth = useIntlayerAuth();\n\n return useMutation({\n mutationKey: ['user'],\n mutationFn: (args: Parameters<AuthAPI['accountInfo']>[0]) =>\n intlayerAuth.accountInfo(args),\n });\n};\n\nexport const useEnableTwoFactor = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['enableTwoFactor'],\n mutationFn: (args: Parameters<AuthAPI['enableTwoFactor']>[0]) =>\n intlayerAuth.enableTwoFactor(args),\n });\n};\n\nexport const useDisableTwoFactor = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['disableTwoFactor'],\n mutationFn: (args: Parameters<AuthAPI['disableTwoFactor']>[0]) =>\n intlayerAuth.disableTwoFactor(args),\n });\n};\n\nexport const useVerifyTotp = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['verifyTotp'],\n mutationFn: (args: Parameters<AuthAPI['verifyTotp']>[0]) =>\n intlayerAuth.verifyTotp(args),\n });\n};\n\nexport const useVerifyBackupCode = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['verifyBackupCode'],\n mutationFn: (args: Parameters<AuthAPI['verifyBackupCode']>[0]) =>\n intlayerAuth.verifyBackupCode(args),\n });\n};\n\nexport const useAddPasskey = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['addPasskey'],\n mutationFn: (args: Parameters<AuthAPI['addPasskey']>[0]) =>\n intlayerAuth.addPasskey(args),\n });\n};\n\nexport const useSignInPasskey = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['signInPasskey'],\n mutationFn: (args?: Parameters<AuthAPI['signInPasskey']>[0]) =>\n intlayerAuth.signInPasskey(args),\n });\n};\n\nexport const useDeletePasskey = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['deletePasskey'],\n mutationFn: (args: Parameters<AuthAPI['deletePasskey']>[0]) =>\n intlayerAuth.deletePasskey(args),\n });\n};\n\nexport const useListPasskeys = () => {\n const intlayerAuth = useIntlayerAuth();\n return useQuery({\n queryKey: ['listPasskeys'],\n queryFn: () => intlayerAuth.listPasskeys(),\n });\n};\n\nexport const useSignInMagicLink = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['signInMagicLink'],\n mutationFn: (args?: any) => intlayerAuth.signInMagicLink(args),\n });\n};\n\nexport const useRegisterSSO = () => {\n const intlayerAuth = useIntlayerAuth();\n const queryClient = useQueryClient();\n return useMutation({\n mutationKey: ['registerSSO'],\n mutationFn: (args: Parameters<AuthAPI['registerSSO']>[0]) =>\n intlayerAuth.registerSSO(args),\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['ssoProviders'] });\n },\n });\n};\n\nexport const useSignInSSO = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['signInSSO'],\n mutationFn: (args: Parameters<AuthAPI['signInSSO']>[0]) =>\n intlayerAuth.signInSSO(args),\n });\n};\n\nexport const useListSSOProviders = () => {\n const intlayerAuth = useIntlayerAuth();\n return useQuery({\n queryKey: ['ssoProviders'],\n queryFn: () => intlayerAuth.listSSOProviders(),\n });\n};\n\nexport const useDeleteSSOProvider = () => {\n const intlayerAuth = useIntlayerAuth();\n const queryClient = useQueryClient();\n return useMutation({\n mutationKey: ['deleteSSOProvider'],\n mutationFn: (args: Parameters<AuthAPI['deleteSSOProvider']>[0]) =>\n intlayerAuth.deleteSSOProvider(args),\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['ssoProviders'] });\n },\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth.mjs","names":[],"sources":["../../../../src/api/hooks/auth.ts"],"sourcesContent":["'use client';\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport type { AuthAPI } from '../../libs/auth';\nimport { useIntlayerAuth } from '../useIntlayerAPI';\n\nexport const useLogin = () => {\n const intlayerAuth = useIntlayerAuth();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationKey: ['login'],\n mutationFn: (args: Parameters<AuthAPI['signInEmail']>[0]) =>\n intlayerAuth.signInEmail(args),\n onSuccess: (data) => {\n const session = queryClient.getQueryData(['session']);\n\n queryClient.setQueryData(['session'], {\n ...session,\n user: data.data.user,\n });\n queryClient.invalidateQueries({ queryKey: ['session'] });\n queryClient.invalidateQueries({ queryKey: ['deviceSessions'] });\n },\n });\n};\n\nexport const useListAccounts = () => {\n const intlayerAuth = useIntlayerAuth();\n return useQuery({\n queryKey: ['listAccounts'],\n queryFn: () => intlayerAuth.listAccounts(),\n });\n};\n\nexport const useUnlinkAccount = () => {\n const intlayerAuth = useIntlayerAuth();\n const queryClient = useQueryClient();\n return useMutation({\n mutationKey: ['unlinkAccount'],\n mutationFn: (args: Parameters<AuthAPI['unlinkAccount']>[0]) =>\n intlayerAuth.unlinkAccount(args),\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['listAccounts'] });\n },\n });\n};\n\nexport const useLinkSocial = () => {\n const intlayerAuth = useIntlayerAuth();\n const queryClient = useQueryClient();\n return useMutation({\n mutationKey: ['linkSocial'],\n mutationFn: (args: Parameters<AuthAPI['linkSocial']>[0]) =>\n intlayerAuth.linkSocial(args),\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['listAccounts'] });\n },\n });\n};\n\nexport const useGetVerifyEmailStatus = () => {\n const intlayerAuth = useIntlayerAuth();\n\n return useMutation({\n mutationKey: ['getVerifyEmailStatus'],\n mutationFn: (args: Parameters<AuthAPI['verifyEmailSession']>) =>\n intlayerAuth.verifyEmailSession(...args),\n });\n};\n\nexport const useRegister = () => {\n const intlayerAuth = useIntlayerAuth();\n\n return useMutation({\n mutationKey: ['register'],\n mutationFn: (args: Parameters<AuthAPI['signUpEmail']>[0]) =>\n intlayerAuth.signUpEmail(args),\n });\n};\n\nconst LOGOUT_QUERY_KEYS: string[][] = [\n ['session'],\n ['users'],\n ['organizations'],\n ['projects'],\n ['dictionaries'],\n ['tags'],\n];\n\nexport const useLogout = () => {\n const intlayerAuth = useIntlayerAuth();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationKey: ['logout'],\n mutationFn: () => intlayerAuth.signOut(),\n // removeQueries instead of resetQueries: evicts cached data without\n // triggering a re-fetch. A re-fetch (resetQueries) can race with the\n // signOut response and resurrect a session from better-auth's\n // short-lived cookie cache, causing an infinite redirect loop between\n // the dashboard and the login page.\n onSettled: () => {\n LOGOUT_QUERY_KEYS.forEach((queryKey) => {\n queryClient.removeQueries({ queryKey });\n });\n },\n });\n};\n\nexport const useChangePassword = () => {\n const intlayerAuth = useIntlayerAuth();\n\n return useMutation({\n mutationKey: ['changePassword'],\n mutationFn: (args: Parameters<AuthAPI['changePasswordSession']>) =>\n intlayerAuth.changePasswordSession(...args),\n });\n};\n\nexport const useAskResetPassword = () => {\n const intlayerAuth = useIntlayerAuth();\n\n return useMutation({\n mutationKey: ['askResetPassword'],\n mutationFn: (args: Parameters<AuthAPI['requestPasswordResetSession']>[0]) =>\n intlayerAuth.requestPasswordResetSession(args),\n });\n};\n\nexport const useResetPassword = () => {\n const intlayerAuth = useIntlayerAuth();\n\n return useMutation({\n mutationKey: ['resetPassword'],\n mutationFn: (args: Parameters<AuthAPI['resetPassword']>[0]) =>\n intlayerAuth.resetPassword(args),\n });\n};\n\nexport const useVerifyEmail = () => {\n const intlayerAuth = useIntlayerAuth();\n\n return useMutation({\n mutationKey: ['verifyEmail'],\n mutationFn: (args: Parameters<AuthAPI['verifyEmailSession']>[0]) =>\n intlayerAuth.verifyEmailSession(args),\n });\n};\n\nexport const useGetUserByAccount = () => {\n const intlayerAuth = useIntlayerAuth();\n\n return useMutation({\n mutationKey: ['user'],\n mutationFn: (args: Parameters<AuthAPI['accountInfo']>[0]) =>\n intlayerAuth.accountInfo(args),\n });\n};\n\nexport const useEnableTwoFactor = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['enableTwoFactor'],\n mutationFn: (args: Parameters<AuthAPI['enableTwoFactor']>[0]) =>\n intlayerAuth.enableTwoFactor(args),\n });\n};\n\nexport const useDisableTwoFactor = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['disableTwoFactor'],\n mutationFn: (args: Parameters<AuthAPI['disableTwoFactor']>[0]) =>\n intlayerAuth.disableTwoFactor(args),\n });\n};\n\nexport const useVerifyTotp = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['verifyTotp'],\n mutationFn: (args: Parameters<AuthAPI['verifyTotp']>[0]) =>\n intlayerAuth.verifyTotp(args),\n });\n};\n\nexport const useVerifyBackupCode = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['verifyBackupCode'],\n mutationFn: (args: Parameters<AuthAPI['verifyBackupCode']>[0]) =>\n intlayerAuth.verifyBackupCode(args),\n });\n};\n\nexport const useAddPasskey = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['addPasskey'],\n mutationFn: (args: Parameters<AuthAPI['addPasskey']>[0]) =>\n intlayerAuth.addPasskey(args),\n });\n};\n\nexport const useSignInPasskey = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['signInPasskey'],\n mutationFn: (args?: Parameters<AuthAPI['signInPasskey']>[0]) =>\n intlayerAuth.signInPasskey(args),\n });\n};\n\nexport const useDeletePasskey = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['deletePasskey'],\n mutationFn: (args: Parameters<AuthAPI['deletePasskey']>[0]) =>\n intlayerAuth.deletePasskey(args),\n });\n};\n\nexport const useListPasskeys = () => {\n const intlayerAuth = useIntlayerAuth();\n return useQuery({\n queryKey: ['listPasskeys'],\n queryFn: () => intlayerAuth.listPasskeys(),\n });\n};\n\nexport const useSignInMagicLink = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['signInMagicLink'],\n mutationFn: (args?: any) => intlayerAuth.signInMagicLink(args),\n });\n};\n\nexport const useRegisterSSO = () => {\n const intlayerAuth = useIntlayerAuth();\n const queryClient = useQueryClient();\n return useMutation({\n mutationKey: ['registerSSO'],\n mutationFn: (args: Parameters<AuthAPI['registerSSO']>[0]) =>\n intlayerAuth.registerSSO(args),\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['ssoProviders'] });\n },\n });\n};\n\nexport const useSignInSSO = () => {\n const intlayerAuth = useIntlayerAuth();\n return useMutation({\n mutationKey: ['signInSSO'],\n mutationFn: (args: Parameters<AuthAPI['signInSSO']>[0]) =>\n intlayerAuth.signInSSO(args),\n });\n};\n\nexport const useListSSOProviders = () => {\n const intlayerAuth = useIntlayerAuth();\n return useQuery({\n queryKey: ['ssoProviders'],\n queryFn: () => intlayerAuth.listSSOProviders(),\n });\n};\n\nexport const useDeleteSSOProvider = () => {\n const intlayerAuth = useIntlayerAuth();\n const queryClient = useQueryClient();\n return useMutation({\n mutationKey: ['deleteSSOProvider'],\n mutationFn: (args: Parameters<AuthAPI['deleteSSOProvider']>[0]) =>\n intlayerAuth.deleteSSOProvider(args),\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: ['ssoProviders'] });\n },\n });\n};\n"],"mappings":"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"}
|
package/dist/esm/api/index.mjs
CHANGED
|
@@ -2,11 +2,12 @@ import { useOAuth2 } from "./useAuth/useOAuth2.mjs";
|
|
|
2
2
|
import { useSession } from "./useAuth/useSession.mjs";
|
|
3
3
|
import { useAuth } from "./useAuth/useAuth.mjs";
|
|
4
4
|
import { useAiAPI, useAnalyticsAPI, useAssetAPI, useAuditAPI, useBitbucketAPI, useDictionaryAPI, useEditorAPI, useEnvironmentAPI, useGithubAPI, useGitlabAPI, useIntlayerAuth, useIntlayerOAuth, useIntlayerOAuthOptions, useNewsletterAPI, useOAuthAPI, useOrganizationAPI, useProjectAPI, useReviewerAPI, useSearchAPI, useShowcaseProjectAPI, useStripeAPI, useTagAPI, useTranslateAPI, useUserAPI } from "./useIntlayerAPI.mjs";
|
|
5
|
+
import { useAddPasskey, useAskResetPassword, useChangePassword, useDeletePasskey, useDeleteSSOProvider, useDisableTwoFactor, useEnableTwoFactor, useGetUserByAccount, useGetVerifyEmailStatus, useLinkSocial, useListAccounts, useListPasskeys, useListSSOProviders, useLogin, useLogout, useRegister, useRegisterSSO, useResetPassword, useSignInMagicLink, useSignInPasskey, useSignInSSO, useUnlinkAccount, useVerifyBackupCode, useVerifyEmail, useVerifyTotp } from "./hooks/auth.mjs";
|
|
6
|
+
import { useUser } from "./useUser/index.mjs";
|
|
5
7
|
import { useAskDocQuestion, useAuditContentDeclaration, useAuditContentDeclarationField, useAuditContentDeclarationMetadata, useAuditTag, useAutocomplete, useChat, useCustomQuery, useGetAIStats, useTranslateJSONDeclaration } from "./hooks/ai.mjs";
|
|
6
8
|
import { useGetAnalyticsAudience, useGetContentStats, useGetExperimentResults } from "./hooks/analytics.mjs";
|
|
7
9
|
import { useDeleteAsset, useGetAssetById, useGetAssets, useUpdateAsset, useUploadAsset } from "./hooks/asset.mjs";
|
|
8
10
|
import { useAuditScan, useGetRecursiveAuditStatus, useStartRecursiveAudit } from "./hooks/audit.mjs";
|
|
9
|
-
import { useAddPasskey, useAskResetPassword, useChangePassword, useDeletePasskey, useDeleteSSOProvider, useDisableTwoFactor, useEnableTwoFactor, useGetUserByAccount, useGetVerifyEmailStatus, useLinkSocial, useListAccounts, useListPasskeys, useListSSOProviders, useLogin, useLogout, useRegister, useRegisterSSO, useResetPassword, useSignInMagicLink, useSignInPasskey, useSignInSSO, useUnlinkAccount, useVerifyBackupCode, useVerifyEmail, useVerifyTotp } from "./hooks/auth.mjs";
|
|
10
11
|
import { useBitbucketAuth, useBitbucketCheckConfig, useBitbucketGetConfigFile, useBitbucketRepos } from "./hooks/bitbucket.mjs";
|
|
11
12
|
import { useAddDictionary, useDeleteDictionary, useGetDictionaries, useGetDictionariesKeys, useGetDictionary, useInfiniteGetDictionaries, usePushDictionaries, useUpdateDictionary } from "./hooks/dictionary.mjs";
|
|
12
13
|
import { useGetDiscussions, useGetDiscussionsData } from "./hooks/discussions.mjs";
|
|
@@ -24,6 +25,5 @@ import { useAcceptAffiliateInvitation, useCancelSubscription, useCreatePortalSes
|
|
|
24
25
|
import { useAddTag, useDeleteTag, useGetTags, useUpdateTag } from "./hooks/tag.mjs";
|
|
25
26
|
import { useFillAllTranslations, usePauseTranslationJob, useResumeTranslationJob, useStopTranslationJob } from "./hooks/translate.mjs";
|
|
26
27
|
import { useCreateUser, useDeleteUser, useGetUserById, useGetUsers, useUpdateUser, useUploadUserAvatar } from "./hooks/user.mjs";
|
|
27
|
-
import { useUser } from "./useUser/index.mjs";
|
|
28
28
|
|
|
29
29
|
export { useAcceptAffiliateInvitation, useAddDictionary, useAddEnvironment, useAddNewAccessKey, useAddOrganization, useAddOrganizationMember, useAddPasskey, useAddProject, useAddTag, useAiAPI, useAnalyticsAPI, useAskDocQuestion, useAskResetPassword, useAssetAPI, useAuditAPI, useAuditContentDeclaration, useAuditContentDeclarationField, useAuditContentDeclarationMetadata, useAuditScan, useAuditTag, useAuth, useAutocomplete, useBitbucketAPI, useBitbucketAuth, useBitbucketCheckConfig, useBitbucketGetConfigFile, useBitbucketRepos, useCancelSubscription, useChangePassword, useChat, useContactReviewer, useCreateMission, useCreatePortalSession, useCreatePromoCode, useCreateUser, useCustomQuery, useDeleteAccessKey, useDeleteAsset, useDeleteDictionary, useDeleteEnvironment, useDeleteOrganization, useDeleteOrganizationById, useDeletePasskey, useDeleteProject, useDeleteProjectById, useDeletePromoCode, useDeleteReviewerProfile, useDeleteSSOProvider, useDeleteShowcaseProject, useDeleteTag, useDeleteUser, useDictionaryAPI, useDisableTwoFactor, useEditorAPI, useEnableTwoFactor, useEnvironmentAPI, useEstimateMission, useFillAllTranslations, useGetAIStats, useGetAdminReviewers, useGetAffiliate, useGetAffiliateAccountSession, useGetAffiliateById, useGetAffiliateInvitation, useGetAffiliateInvitations, useGetAffiliateOnboardingLink, useGetAffiliatePromoCode, useGetAffiliateStats, useGetAffiliates, useGetAnalyticsAudience, useGetAssetById, useGetAssets, useGetCIConfig, useGetChatHistory, useGetContentStats, useGetDictionaries, useGetDictionariesKeys, useGetDictionary, useGetDiscussions, useGetDiscussionsData, useGetEditorDictionaries, useGetExperimentResults, useGetInvoices, useGetMissionById, useGetMyMissions, useGetMyReviewerProfile, useGetNewsletterStatus, useGetOrganizations, useGetOtherShowcaseProjects, useGetPaymentMethod, useGetPricing, useGetProjectInsights, useGetProjects, useGetPromoCodeById, useGetPromoCodes, useGetRecursiveAuditStatus, useGetReviewerById, useGetReviewerMarketplace, useGetReviewerPriceDistribution, useGetReviewerReviews, useGetShowcaseProjectById, useGetShowcaseProjects, useGetSubscription, useGetTags, useGetUserByAccount, useGetUserById, useGetUsers, useGetVerifyEmailStatus, useGithubAPI, useGithubAuth, useGithubCheckConfig, useGithubGetAuthUrl, useGithubGetConfigFile, useGithubRepos, useGithubToken, useGitlabAPI, useGitlabAuth, useGitlabCheckConfig, useGitlabGetConfigFile, useGitlabProjects, useGrantAffiliateAccess, useInfiniteGetDictionaries, useIntlayerAuth, useIntlayerOAuth, useIntlayerOAuthOptions, useLinkSocial, useListAccounts, useListPasskeys, useListSSOProviders, useLogin, useLogout, useMigrateEnvironment, useNewsletterAPI, useOAuth2, useOAuthAPI, useOrganizationAPI, usePauseTranslationJob, useProjectAPI, usePushCIConfig, usePushDictionaries, usePushProjectConfiguration, useRefreshAccessKey, useRegister, useRegisterAsReviewer, useRegisterSSO, useResetPassword, useResetToProductionEnvironment, useResumeTranslationJob, useReviewerAPI, useSearchAPI, useSearchDoc, useSelectEnvironment, useSelectOrganization, useSelectProject, useSendAffiliateInvitation, useSendReviewerMessage, useSession, useShowcaseProjectAPI, useSignInMagicLink, useSignInPasskey, useSignInSSO, useStartRecursiveAudit, useStopTranslationJob, useStripeAPI, useSubmitReview, useSubmitShowcaseProject, useSubscribeToNewsletter, useTagAPI, useToggleShowcaseDownvote, useToggleShowcaseUpvote, useTranslateAPI, useTranslateJSONDeclaration, useTriggerBuild, useTriggerWebhook, useUnlinkAccount, useUnselectOrganization, useUnselectProject, useUnsubscribeFromNewsletter, useUpdateAffiliateStatus, useUpdateAsset, useUpdateDictionary, useUpdateEnvironment, useUpdateMemberAccess, useUpdateMissionStatus, useUpdateOrganization, useUpdateOrganizationMailerConfig, useUpdateOrganizationMembers, useUpdateOrganizationMembersById, useUpdateProject, useUpdateProjectMembers, useUpdatePromoCode, useUpdateReviewerProfile, useUpdateShowcaseProject, useUpdateTag, useUpdateUser, useUploadAsset, useUploadReviewerCoverPicture, useUploadReviewerMainPicture, useUploadUserAvatar, useUser, useUserAPI, useValidateReviewerProfile, useVerifyBackupCode, useVerifyEmail, useVerifyTotp, useWriteDictionary };
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { useIsAuthFetchEnabled } from "../../providers/DeferredAuthProvider.mjs";
|
|
4
4
|
import { useQuery } from "@tanstack/react-query";
|
|
5
|
+
import { editor } from "@intlayer/config/built";
|
|
5
6
|
import { getOAuthAPI } from "@intlayer/api";
|
|
6
7
|
import { useConfiguration } from "@intlayer/editor-react";
|
|
7
|
-
import { editor } from "@intlayer/config/built";
|
|
8
8
|
import { defu } from "defu";
|
|
9
9
|
|
|
10
10
|
//#region src/api/useAuth/useOAuth2.ts
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
import { useIsAuthFetchEnabled } from "../../providers/DeferredAuthProvider.mjs";
|
|
4
4
|
import { getAuthAPI } from "../../libs/auth.mjs";
|
|
5
5
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
6
|
-
import { useConfiguration } from "@intlayer/editor-react";
|
|
7
6
|
import { editor } from "@intlayer/config/built";
|
|
7
|
+
import { useConfiguration } from "@intlayer/editor-react";
|
|
8
8
|
|
|
9
9
|
//#region src/api/useAuth/useSession.ts
|
|
10
10
|
const useSession = (sessionProp, intlayerConfiguration) => {
|
|
@@ -45,7 +45,7 @@ import { ChevronDown } from "lucide-react";
|
|
|
45
45
|
* <p>Controlled content</p>
|
|
46
46
|
* </Accordion>
|
|
47
47
|
*/
|
|
48
|
-
const Accordion = ({ children, header, isOpen, defaultIsOpen = false, onToggle, onClick, disabled = false, contentClassName, headerClassName, contentId, "aria-label": ariaLabel, ...props }) => {
|
|
48
|
+
const Accordion = ({ children, header, isOpen, defaultIsOpen = false, onToggle, onClick, disabled = false, contentClassName, headerClassName, contentId, "aria-label": ariaLabel, className, ...props }) => {
|
|
49
49
|
const isControlled = isOpen !== void 0;
|
|
50
50
|
const [internalIsOpen, setInternalIsOpen] = useState(defaultIsOpen);
|
|
51
51
|
const id = useId();
|
|
@@ -66,7 +66,7 @@ const Accordion = ({ children, header, isOpen, defaultIsOpen = false, onToggle,
|
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
68
|
return /* @__PURE__ */ jsxs("div", {
|
|
69
|
-
className: "w-full",
|
|
69
|
+
className: cn("w-full", className),
|
|
70
70
|
children: [/* @__PURE__ */ jsx(Button, {
|
|
71
71
|
variant: "hoverable",
|
|
72
72
|
color: "text",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Accordion.mjs","names":[],"sources":["../../../../src/components/Accordion/Accordion.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { ChevronDown } from 'lucide-react';\nimport {\n type FC,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n useId,\n useState,\n} from 'react';\nimport { Button, type ButtonProps } from '../Button';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\n\nexport interface AccordionProps\n extends Omit<ButtonProps, 'children' | 'onToggle'> {\n /** The content displayed in the accordion header */\n header: ReactNode;\n /** The collapsible content inside the accordion */\n children: ReactNode;\n /** Controls whether the accordion is open (controlled mode) */\n isOpen?: boolean;\n /** Default open state (uncontrolled mode) */\n defaultIsOpen?: boolean;\n /** Called when the accordion state changes */\n onToggle?: (isOpen: boolean) => void;\n /** Disable the accordion interaction */\n disabled?: boolean;\n /** Custom class for the content container */\n contentClassName?: string;\n /** Custom class for the header container */\n headerClassName?: string;\n /** Accessible label for screen readers */\n 'aria-label'?: string;\n /** ID for the accordion content (for aria-controls) */\n contentId?: string;\n}\n\n/**\n * Accordion component that allows the user to expand and collapse content.\n * It provides a header with a chevron icon that controls the visibility of the content.\n *\n * Features:\n * - Supports both controlled and uncontrolled modes\n * - Accessible with proper ARIA attributes\n * - Keyboard navigation support\n * - Smooth animations for expand/collapse\n * - Customizable styling\n *\n * @param header - The content of the header.\n * @param children - The content to be expanded and collapsed.\n * @param isOpen - Controlled state for whether the content is expanded.\n * @param defaultIsOpen - Default open state for uncontrolled mode.\n * @param onToggle - Callback when the accordion state changes.\n * @param disabled - Whether the accordion is disabled.\n * @param contentClassName - Custom class for the content container.\n * @param headerClassName - Custom class for the header.\n * @param contentId - ID for the content (used for aria-controls).\n *\n * @example\n * // Uncontrolled mode\n * <Accordion header=\"Accordion Header\" defaultIsOpen={true}>\n * <p>Accordion content</p>\n * </Accordion>\n *\n * @example\n * // Controlled mode\n * <Accordion\n * header=\"Controlled Accordion\"\n * isOpen={isOpen}\n * onToggle={setIsOpen}\n * >\n * <p>Controlled content</p>\n * </Accordion>\n */\nexport const Accordion: FC<AccordionProps> = ({\n children,\n header,\n isOpen,\n defaultIsOpen = false,\n onToggle,\n onClick,\n disabled = false,\n contentClassName,\n headerClassName,\n contentId,\n 'aria-label': ariaLabel,\n ...props\n}) => {\n // Determine if we're in controlled or uncontrolled mode\n const isControlled = isOpen !== undefined;\n const [internalIsOpen, setInternalIsOpen] = useState(defaultIsOpen);\n const id = useId();\n\n // Use controlled value if provided, otherwise use internal state\n const isExpandedState = isControlled ? isOpen : internalIsOpen;\n const isHidden = !isExpandedState;\n\n // Generate unique ID for content if not provided\n const generatedContentId = contentId ?? `${id}-accordion-content`;\n\n const handleToggle = (e: MouseEvent<HTMLButtonElement>) => {\n if (disabled) return;\n\n const newIsOpen = !isExpandedState;\n\n // Update internal state if uncontrolled\n if (!isControlled) {\n setInternalIsOpen(newIsOpen);\n }\n\n // Call external handlers\n onToggle?.(newIsOpen);\n onClick?.(e);\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n // Enter and Space should toggle the accordion\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleToggle(e as any);\n }\n };\n\n return (\n <div className
|
|
1
|
+
{"version":3,"file":"Accordion.mjs","names":[],"sources":["../../../../src/components/Accordion/Accordion.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { ChevronDown } from 'lucide-react';\nimport {\n type FC,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n useId,\n useState,\n} from 'react';\nimport { Button, type ButtonProps } from '../Button';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\n\nexport interface AccordionProps\n extends Omit<ButtonProps, 'children' | 'onToggle'> {\n /** The content displayed in the accordion header */\n header: ReactNode;\n /** The collapsible content inside the accordion */\n children: ReactNode;\n /** Controls whether the accordion is open (controlled mode) */\n isOpen?: boolean;\n /** Default open state (uncontrolled mode) */\n defaultIsOpen?: boolean;\n /** Called when the accordion state changes */\n onToggle?: (isOpen: boolean) => void;\n /** Disable the accordion interaction */\n disabled?: boolean;\n /** Custom class for the content container */\n contentClassName?: string;\n /** Custom class for the header container */\n headerClassName?: string;\n /** Accessible label for screen readers */\n 'aria-label'?: string;\n /** ID for the accordion content (for aria-controls) */\n contentId?: string;\n}\n\n/**\n * Accordion component that allows the user to expand and collapse content.\n * It provides a header with a chevron icon that controls the visibility of the content.\n *\n * Features:\n * - Supports both controlled and uncontrolled modes\n * - Accessible with proper ARIA attributes\n * - Keyboard navigation support\n * - Smooth animations for expand/collapse\n * - Customizable styling\n *\n * @param header - The content of the header.\n * @param children - The content to be expanded and collapsed.\n * @param isOpen - Controlled state for whether the content is expanded.\n * @param defaultIsOpen - Default open state for uncontrolled mode.\n * @param onToggle - Callback when the accordion state changes.\n * @param disabled - Whether the accordion is disabled.\n * @param contentClassName - Custom class for the content container.\n * @param headerClassName - Custom class for the header.\n * @param contentId - ID for the content (used for aria-controls).\n *\n * @example\n * // Uncontrolled mode\n * <Accordion header=\"Accordion Header\" defaultIsOpen={true}>\n * <p>Accordion content</p>\n * </Accordion>\n *\n * @example\n * // Controlled mode\n * <Accordion\n * header=\"Controlled Accordion\"\n * isOpen={isOpen}\n * onToggle={setIsOpen}\n * >\n * <p>Controlled content</p>\n * </Accordion>\n */\nexport const Accordion: FC<AccordionProps> = ({\n children,\n header,\n isOpen,\n defaultIsOpen = false,\n onToggle,\n onClick,\n disabled = false,\n contentClassName,\n headerClassName,\n contentId,\n 'aria-label': ariaLabel,\n className,\n ...props\n}) => {\n // Determine if we're in controlled or uncontrolled mode\n const isControlled = isOpen !== undefined;\n const [internalIsOpen, setInternalIsOpen] = useState(defaultIsOpen);\n const id = useId();\n\n // Use controlled value if provided, otherwise use internal state\n const isExpandedState = isControlled ? isOpen : internalIsOpen;\n const isHidden = !isExpandedState;\n\n // Generate unique ID for content if not provided\n const generatedContentId = contentId ?? `${id}-accordion-content`;\n\n const handleToggle = (e: MouseEvent<HTMLButtonElement>) => {\n if (disabled) return;\n\n const newIsOpen = !isExpandedState;\n\n // Update internal state if uncontrolled\n if (!isControlled) {\n setInternalIsOpen(newIsOpen);\n }\n\n // Call external handlers\n onToggle?.(newIsOpen);\n onClick?.(e);\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n // Enter and Space should toggle the accordion\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleToggle(e as any);\n }\n };\n\n return (\n <div className={cn('w-full', className)}>\n <Button\n variant=\"hoverable\"\n color=\"text\"\n onClick={handleToggle}\n onKeyDown={handleKeyDown}\n disabled={disabled}\n isFullWidth\n className={cn(\n 'flex items-center justify-between gap-2',\n headerClassName\n )}\n IconRight={ChevronDown}\n iconClassName={cn(\n 'transform transition-transform duration-500 ease-in-out',\n isExpandedState ? 'rotate-0' : '-rotate-180'\n )}\n aria-expanded={isExpandedState}\n aria-controls={generatedContentId}\n aria-label={ariaLabel}\n role=\"button\"\n {...props}\n >\n {header}\n </Button>\n\n <MaxHeightSmoother\n id={generatedContentId}\n tabIndex={isHidden ? -1 : undefined}\n isHidden={isHidden}\n className={contentClassName}\n role=\"region\"\n aria-labelledby={generatedContentId}\n >\n {children}\n </MaxHeightSmoother>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,MAAa,aAAiC,EAC5C,UACA,QACA,QACA,gBAAgB,OAChB,UACA,SACA,WAAW,OACX,kBACA,iBACA,WACA,cAAc,WACd,WACA,GAAG,YACC;CAEJ,MAAM,eAAe,WAAW;CAChC,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,aAAa;CAClE,MAAM,KAAK,MAAM;CAGjB,MAAM,kBAAkB,eAAe,SAAS;CAChD,MAAM,WAAW,CAAC;CAGlB,MAAM,qBAAqB,aAAa,GAAG,GAAG;CAE9C,MAAM,gBAAgB,MAAqC;EACzD,IAAI,UAAU;EAEd,MAAM,YAAY,CAAC;EAGnB,IAAI,CAAC,cACH,kBAAkB,SAAS;EAI7B,WAAW,SAAS;EACpB,UAAU,CAAC;CACb;CAEA,MAAM,iBAAiB,MAAwC;EAE7D,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;GACtC,EAAE,eAAe;GACjB,aAAa,CAAQ;EACvB;CACF;CAEA,OACE,qBAAC,OAAD;EAAK,WAAW,GAAG,UAAU,SAAS;YAAtC,CACE,oBAAC,QAAD;GACE,SAAQ;GACR,OAAM;GACN,SAAS;GACT,WAAW;GACD;GACV;GACA,WAAW,GACT,2CACA,eACF;GACA,WAAW;GACX,eAAe,GACb,2DACA,kBAAkB,aAAa,aACjC;GACA,iBAAe;GACf,iBAAe;GACf,cAAY;GACZ,MAAK;GACL,GAAI;aAEH;EACK,IAER,oBAAC,mBAAD;GACE,IAAI;GACJ,UAAU,WAAW,KAAK;GAChB;GACV,WAAW;GACX,MAAK;GACL,mBAAiB;GAEhB;EACgB,EAChB;;AAET"}
|
|
@@ -365,7 +365,7 @@ const Browser = ({ initialUrl = "https://example.com", path, className, style, s
|
|
|
365
365
|
src: currentUrl,
|
|
366
366
|
title: content.iframeTitle.value,
|
|
367
367
|
className: "size-full flex-1",
|
|
368
|
-
sandbox,
|
|
368
|
+
sandbox: sandbox ?? void 0,
|
|
369
369
|
loading: "lazy",
|
|
370
370
|
"aria-live": "polite",
|
|
371
371
|
...props
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Browser.mjs","names":[],"sources":["../../../../src/components/Browser/Browser.tsx"],"sourcesContent":["'use client';\n\nimport { Container } from '@components/Container';\nimport { cn } from '@utils/cn';\nimport { ArrowLeft, ArrowRight, RotateCw, ScanSearch } from 'lucide-react';\nimport {\n type CSSProperties,\n type HTMLAttributes,\n type RefObject,\n type SubmitEvent,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { Button } from '../Button';\nimport { ClickOutsideDiv } from '../ClickOutsideDiv';\nimport { DropDown } from '../DropDown';\nimport { Input, inputVariants } from '../Input';\n\nexport type BrowserProps = {\n initialUrl?: string;\n path?: string;\n className?: string;\n style?: CSSProperties;\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n 'aria-label'?: string;\n sandbox?: string;\n ref?: RefObject<HTMLIFrameElement | null>;\n domainRestriction?: string;\n} & HTMLAttributes<HTMLIFrameElement>;\n\nconst UrlPath = ({ url }: { url: string }) => {\n const parts = getUrlPath(url).split('/').filter(Boolean);\n\n if (parts.length === 0) return <span>/</span>;\n\n return parts.flatMap((part, index, array) => [\n <span key={`part-${index}`}>{part}</span>,\n index < array.length - 1 && (\n <span key={`sep-${index}`} className=\"mx-2 text-neutral\">\n /\n </span>\n ),\n ]);\n};\n\nconst getUrlPath = (url: string) => {\n try {\n const { pathname, search, hash } = new URL(url);\n\n return `${pathname}${search}${hash}` || '/';\n } catch {\n return url;\n }\n};\n\nexport const Browser = ({\n initialUrl = 'https://example.com',\n path,\n className,\n style,\n size = 'md',\n 'aria-label': ariaLabel,\n sandbox = 'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads',\n ref,\n domainRestriction,\n ...props\n}: BrowserProps) => {\n // --- State -----------------------------------------------------------------\n const [inputUrl, setInputUrl] = useState(initialUrl);\n const [currentUrl, setCurrentUrl] = useState(initialUrl);\n\n // History Management\n const [history, setHistory] = useState<string[]>([initialUrl]);\n const [currentIndex, setCurrentIndex] = useState(0);\n\n const [error, setError] = useState<string | null>(null);\n const [submitted, setSubmitted] = useState(false);\n const internalIframeRef = useRef<HTMLIFrameElement>(null);\n\n // Sitemap explorer state\n const [sitemapOpen, setSitemapOpen] = useState(false);\n const [sitemapUrls, setSitemapUrls] = useState<string[]>([]);\n const [sitemapSearch, setSitemapSearch] = useState('');\n const [sitemapLoading, setSitemapLoading] = useState(false);\n const [sitemapFetched, setSitemapFetched] = useState(false);\n const [sitemapError, setSitemapError] = useState(false);\n const sitemapInputRef = useRef<HTMLInputElement>(null);\n\n useImperativeHandle(ref, () => internalIframeRef.current!, []);\n const content = useIntlayer('browser');\n\n // --- Effects ---------------------------------------------------------------\n\n // Reset everything if initialUrl changes completely\n useEffect(() => {\n setInputUrl(initialUrl);\n setCurrentUrl(initialUrl);\n setHistory([initialUrl]);\n setCurrentIndex(0);\n setError(null);\n setSubmitted(false);\n setSitemapOpen(false);\n setSitemapUrls([]);\n setSitemapSearch('');\n setSitemapFetched(false);\n setSitemapError(false);\n }, [initialUrl]);\n\n // Sync external path changes with the URL bar and History\n useEffect(() => {\n if (!path) return;\n\n try {\n const baseOrigin = domainRestriction ?? initialUrl;\n const origin = new URL(baseOrigin).origin;\n const fullUrl = `${origin}${path}`;\n\n // Update Input (Always update the visual bar)\n setInputUrl(fullUrl);\n\n // Check internal iframe state to avoid reload if already there\n let isAlreadyAtUrl = false;\n if (internalIframeRef.current?.contentWindow) {\n try {\n const currentIframeHref =\n internalIframeRef.current.contentWindow.location.href;\n if (new URL(currentIframeHref).href === new URL(fullUrl).href) {\n isAlreadyAtUrl = true;\n }\n } catch {\n // Cross-origin access ignored\n }\n }\n\n // Update History Stack regardless of isAlreadyAtUrl so arrow navigation is always correct\n if (history[currentIndex] !== fullUrl) {\n setHistory((prev) => {\n const newHistory = prev.slice(0, currentIndex + 1);\n newHistory.push(fullUrl);\n return newHistory;\n });\n setCurrentIndex((prev) => prev + 1);\n }\n\n // Navigate only if NOT already there to avoid refreshing on internal iframe navigation\n if (!isAlreadyAtUrl) {\n setCurrentUrl(fullUrl);\n }\n\n setError(null);\n } catch {\n // Ignore invalid paths\n }\n }, [path, domainRestriction, initialUrl]); // Removed currentIndex dependency to prevent loops\n\n // Imperatively keep the iframe src in sync with currentUrl.\n // React's attribute reconciliation can be unreliable for cross-origin iframes after\n // internal navigation, so this effect is the source of truth for navigation.\n useEffect(() => {\n const iframe = internalIframeRef.current;\n if (!iframe) return;\n if (iframe.src !== currentUrl) {\n iframe.src = currentUrl;\n }\n }, [currentUrl]);\n\n // --- Navigation Logic ------------------------------------------------------\n\n const handleNavigateTo = (url: string) => {\n try {\n const validated = normalizeUrl(url);\n\n // If we are navigating to the exact same URL, just reload\n if (validated === currentUrl) {\n handleReload();\n return;\n }\n\n setCurrentUrl(validated);\n setInputUrl(validated);\n setError(null);\n\n // Update History: Slice future if we went back, then push new\n const newHistory = history.slice(0, currentIndex + 1);\n newHistory.push(validated);\n setHistory(newHistory);\n setCurrentIndex(newHistory.length - 1);\n } catch (e) {\n if (\n e instanceof Error &&\n e.message === 'URL does not match allowed domain' &&\n domainRestriction\n ) {\n setError(\n content.domainRestrictionError?.value ??\n `Only URLs from ${domainRestriction} are allowed.`\n );\n } else {\n setError(content.errorMessage.value);\n }\n }\n };\n\n const handleBack = () => {\n if (currentIndex > 0) {\n const newIndex = currentIndex - 1;\n const prevUrl = history[newIndex];\n setCurrentIndex(newIndex);\n setCurrentUrl(prevUrl);\n setInputUrl(prevUrl);\n setError(null);\n }\n };\n\n const handleForward = () => {\n if (currentIndex < history.length - 1) {\n const newIndex = currentIndex + 1;\n const nextUrl = history[newIndex];\n setCurrentIndex(newIndex);\n setCurrentUrl(nextUrl);\n setInputUrl(nextUrl);\n setError(null);\n }\n };\n\n const handleSubmit = (e: SubmitEvent<HTMLFormElement>) => {\n e.preventDefault();\n setSubmitted(true);\n handleNavigateTo(inputUrl);\n };\n\n const handleReload = () => {\n const iframe = internalIframeRef.current;\n if (!iframe) return;\n const src = iframe.src;\n iframe.src = '';\n setTimeout(() => {\n if (internalIframeRef.current) internalIframeRef.current.src = src;\n }, 50);\n };\n\n // --- Validation Helpers ----------------------------------------------------\n const isValidHostname = (host: string) => {\n if (host === 'localhost') return true;\n if (/^(\\d{1,3}\\.){3}\\d{1,3}$/.test(host)) return true;\n if (/^[a-f0-9:]+$/i.test(host)) return true;\n if (!/^[a-z0-9.-]+$/i.test(host)) return false;\n if (/^[-.]/.test(host) || /[-.]$/.test(host)) return false;\n if (host.includes('..')) return false;\n if (!host.includes('.')) return false;\n return true;\n };\n\n const getRestrictionOrigin = (): URL | null => {\n if (!domainRestriction) return null;\n try {\n return new URL(domainRestriction);\n } catch {\n return null;\n }\n };\n\n const normalizeUrl = (raw: string) => {\n const trimmed = raw.trim();\n if (!trimmed || /\\s/.test(trimmed)) throw new Error('Invalid');\n\n const restrictionOrigin = getRestrictionOrigin();\n const isRelativePath = trimmed.startsWith('/') && !trimmed.startsWith('//');\n\n if (isRelativePath) {\n if (restrictionOrigin) {\n return new URL(`${restrictionOrigin.origin}${trimmed}`).toString();\n }\n return new URL(`${new URL(currentUrl).origin}${trimmed}`).toString();\n }\n\n const hasProtocol = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed);\n const candidate = hasProtocol ? trimmed : `https://${trimmed}`;\n const url = new URL(candidate);\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new Error('Only http(s) is allowed');\n }\n\n if (!isValidHostname(url.hostname)) throw new Error('Invalid host');\n\n if (restrictionOrigin) {\n const urlMatches =\n url.hostname === restrictionOrigin.hostname &&\n url.protocol === restrictionOrigin.protocol &&\n (restrictionOrigin.port === '' ||\n url.port === restrictionOrigin.port ||\n url.host === restrictionOrigin.host);\n\n if (!urlMatches) throw new Error('URL does not match allowed domain');\n }\n\n return url.toString();\n };\n\n const handleSitemapToggle = async () => {\n const nextOpen = !sitemapOpen;\n setSitemapOpen(nextOpen);\n\n if (nextOpen && !sitemapFetched) {\n setSitemapLoading(true);\n setSitemapError(false);\n setSitemapFetched(true);\n try {\n const { extractUrlFromSitemap } = await import(\n './extractUrlFromSitemap'\n );\n const urls = await extractUrlFromSitemap(currentUrl);\n setSitemapUrls(urls);\n if (urls.length === 0) setSitemapError(false);\n } catch {\n setSitemapError(true);\n } finally {\n setSitemapLoading(false);\n }\n setTimeout(() => sitemapInputRef.current?.focus(), 50);\n }\n };\n\n const filteredSitemapUrls = useMemo(() => {\n const query = sitemapSearch.trim().toLowerCase();\n if (!query) return sitemapUrls;\n return sitemapUrls.filter((url) => url.toLowerCase().includes(query));\n }, [sitemapUrls, sitemapSearch]);\n\n const showError = submitted && !!error;\n const canGoBack = currentIndex > 0;\n const canGoForward = currentIndex < history.length - 1;\n\n return (\n <section\n className={cn(\n 'flex w-full flex-col overflow-hidden rounded-xl bg-background shadow-[0_4px_12px_rgba(0,0,0,0.4),0_0_1px_rgba(0,0,0,0.2)]',\n className\n )}\n style={style}\n aria-label={ariaLabel ?? content.ariaLabel.value}\n >\n {/* Top bar */}\n <div className=\"relative z-10 flex shrink-0 items-center gap-3 rounded-t-xl bg-text/15 px-4 py-2\">\n {/* Navigation Controls */}\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n onClick={handleBack}\n disabled={!canGoBack}\n variant=\"hoverable\"\n size=\"icon-md\"\n label={content.backButtonLabel.value}\n Icon={ArrowLeft}\n />\n <Button\n type=\"button\"\n onClick={handleForward}\n disabled={!canGoForward}\n variant=\"hoverable\"\n size=\"icon-md\"\n label={content.forwardButtonLabel.value}\n Icon={ArrowRight}\n />\n </div>\n\n {/* URL Bar */}\n <form\n onSubmit={handleSubmit}\n noValidate\n className={cn(\n inputVariants(),\n 'flex w-full gap-2 rounded-xl p-0.5! supports-[corner-shape:squircle]:rounded-2xl',\n 'bg-neutral/10 text-text/50 placeholder:text-neutral/80'\n )}\n >\n <label htmlFor=\"browser-url\" className=\"sr-only\">\n {content.urlLabel}\n </label>\n <Input\n id=\"browser-url\"\n type=\"text\"\n inputMode=\"url\"\n spellCheck={false}\n autoCapitalize=\"off\"\n variant=\"invisible\"\n className=\"ml-3 p-0!\"\n size=\"sm\"\n autoCorrect=\"off\"\n value={inputUrl}\n onChange={(e) => {\n setInputUrl(e.target.value);\n if (showError) setError(null);\n }}\n placeholder={content.urlPlaceholder.value}\n aria-label={content.urlLabel.value}\n aria-invalid={showError}\n aria-describedby={showError ? 'browser-url-error' : undefined}\n />\n\n <Button\n type=\"button\"\n onClick={handleReload}\n variant=\"hoverable\"\n size=\"icon-md\"\n className=\"p-1!\"\n label=\"content.reloadButtonTitle.value\"\n Icon={RotateCw}\n />\n\n {/* invisible submit */}\n <button type=\"submit\" className=\"sr-only absolute\" tabIndex={-1} />\n </form>\n\n {/* Sitemap Explorer */}\n <ClickOutsideDiv\n onClickOutSide={() => setSitemapOpen(false)}\n disabled={!sitemapOpen}\n role=\"none\"\n >\n <DropDown identifier=\"sitemap-explorer\">\n <DropDown.Trigger\n identifier=\"sitemap-explorer-trigger\"\n type=\"button\"\n color=\"text\"\n onClick={handleSitemapToggle}\n variant=\"hoverable\"\n size=\"icon-md\"\n label={content.sitemapButtonLabel.value}\n Icon={ScanSearch}\n />\n\n <DropDown.Panel\n identifier=\"sitemap-explorer\"\n isHidden={!sitemapOpen}\n align=\"end\"\n isFocusable\n isOverable\n >\n <Container\n className=\"min-w-28 rounded-md!\"\n roundedSize=\"xl\"\n border\n borderColor=\"neutral\"\n >\n <div className=\"p-2\">\n <Input\n type=\"search\"\n ref={sitemapInputRef}\n aria-label={content.sitemapSearchAriaLabel.value}\n placeholder={content.sitemapSearchPlaceholder.value}\n onChange={(e) => setSitemapSearch(e.target.value)}\n value={sitemapSearch}\n size=\"sm\"\n />\n </div>\n <ul\n className=\"max-h-64 divide-y divide-dotted divide-neutral/30 overflow-y-auto p-1 text-center\"\n aria-label={content.sitemapButtonLabel.value}\n >\n {sitemapLoading ? (\n <li className=\"px-3 py-4 text-center text-neutral text-xs\">\n {content.sitemapLoading}\n </li>\n ) : sitemapError ||\n (!sitemapLoading && filteredSitemapUrls.length === 0) ? (\n <li className=\"px-3 py-4 text-center text-neutral text-xs\">\n {sitemapError\n ? content.sitemapError\n : content.sitemapEmpty}\n </li>\n ) : (\n filteredSitemapUrls.map((url) => (\n <li key={url} className=\"py-0.5\">\n <Button\n variant=\"hoverable\"\n color=\"text\"\n size=\"sm\"\n className=\"w-full text-left\"\n label={url}\n onClick={() => {\n handleNavigateTo(url);\n setSitemapOpen(false);\n }}\n >\n <span className=\"max-w-64 truncate text-left text-base\">\n <UrlPath url={url} />\n </span>\n </Button>\n </li>\n ))\n )}\n </ul>\n </Container>\n </DropDown.Panel>\n </DropDown>\n </ClickOutsideDiv>\n\n {/* Error Message Tooltip */}\n {showError && (\n <div className=\"absolute top-full left-4 z-20 mt-1\">\n <p\n id=\"browser-url-error\"\n role=\"alert\"\n aria-live=\"assertive\"\n className=\"rounded-md bg-red-900/90 px-3 py-1.5 text-red-100 text-xs shadow-md backdrop-blur-sm\"\n >\n {error}\n </p>\n </div>\n )}\n </div>\n\n {/* Iframe */}\n <div className=\"relative z-0 flex min-h-0 w-full flex-1 flex-col overflow-hidden rounded-b-xl bg-background\">\n <iframe\n ref={internalIframeRef}\n src={currentUrl}\n title={content.iframeTitle.value}\n className=\"size-full flex-1\"\n sandbox={sandbox}\n loading=\"lazy\"\n aria-live=\"polite\"\n {...props}\n />\n </div>\n </section>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Browser.mjs","names":[],"sources":["../../../../src/components/Browser/Browser.tsx"],"sourcesContent":["'use client';\n\nimport { Container } from '@components/Container';\nimport { cn } from '@utils/cn';\nimport { ArrowLeft, ArrowRight, RotateCw, ScanSearch } from 'lucide-react';\nimport {\n type CSSProperties,\n type HTMLAttributes,\n type RefObject,\n type SubmitEvent,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { Button } from '../Button';\nimport { ClickOutsideDiv } from '../ClickOutsideDiv';\nimport { DropDown } from '../DropDown';\nimport { Input, inputVariants } from '../Input';\n\nexport type BrowserProps = {\n initialUrl?: string;\n path?: string;\n className?: string;\n style?: CSSProperties;\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n 'aria-label'?: string;\n /**\n * Value of the iframe `sandbox` attribute. Pass `null` to omit the attribute\n * entirely — required for same-origin embeds, where `allow-scripts` plus\n * `allow-same-origin` sandboxes nothing (the frame can reach its parent and\n * strip the attribute) and browsers warn about the combination.\n */\n sandbox?: string | null;\n ref?: RefObject<HTMLIFrameElement | null>;\n domainRestriction?: string;\n} & HTMLAttributes<HTMLIFrameElement>;\n\nconst UrlPath = ({ url }: { url: string }) => {\n const parts = getUrlPath(url).split('/').filter(Boolean);\n\n if (parts.length === 0) return <span>/</span>;\n\n return parts.flatMap((part, index, array) => [\n <span key={`part-${index}`}>{part}</span>,\n index < array.length - 1 && (\n <span key={`sep-${index}`} className=\"mx-2 text-neutral\">\n /\n </span>\n ),\n ]);\n};\n\nconst getUrlPath = (url: string) => {\n try {\n const { pathname, search, hash } = new URL(url);\n\n return `${pathname}${search}${hash}` || '/';\n } catch {\n return url;\n }\n};\n\nexport const Browser = ({\n initialUrl = 'https://example.com',\n path,\n className,\n style,\n size = 'md',\n 'aria-label': ariaLabel,\n sandbox = 'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads',\n ref,\n domainRestriction,\n ...props\n}: BrowserProps) => {\n // --- State -----------------------------------------------------------------\n const [inputUrl, setInputUrl] = useState(initialUrl);\n const [currentUrl, setCurrentUrl] = useState(initialUrl);\n\n // History Management\n const [history, setHistory] = useState<string[]>([initialUrl]);\n const [currentIndex, setCurrentIndex] = useState(0);\n\n const [error, setError] = useState<string | null>(null);\n const [submitted, setSubmitted] = useState(false);\n const internalIframeRef = useRef<HTMLIFrameElement>(null);\n\n // Sitemap explorer state\n const [sitemapOpen, setSitemapOpen] = useState(false);\n const [sitemapUrls, setSitemapUrls] = useState<string[]>([]);\n const [sitemapSearch, setSitemapSearch] = useState('');\n const [sitemapLoading, setSitemapLoading] = useState(false);\n const [sitemapFetched, setSitemapFetched] = useState(false);\n const [sitemapError, setSitemapError] = useState(false);\n const sitemapInputRef = useRef<HTMLInputElement>(null);\n\n useImperativeHandle(ref, () => internalIframeRef.current!, []);\n const content = useIntlayer('browser');\n\n // --- Effects ---------------------------------------------------------------\n\n // Reset everything if initialUrl changes completely\n useEffect(() => {\n setInputUrl(initialUrl);\n setCurrentUrl(initialUrl);\n setHistory([initialUrl]);\n setCurrentIndex(0);\n setError(null);\n setSubmitted(false);\n setSitemapOpen(false);\n setSitemapUrls([]);\n setSitemapSearch('');\n setSitemapFetched(false);\n setSitemapError(false);\n }, [initialUrl]);\n\n // Sync external path changes with the URL bar and History\n useEffect(() => {\n if (!path) return;\n\n try {\n const baseOrigin = domainRestriction ?? initialUrl;\n const origin = new URL(baseOrigin).origin;\n const fullUrl = `${origin}${path}`;\n\n // Update Input (Always update the visual bar)\n setInputUrl(fullUrl);\n\n // Check internal iframe state to avoid reload if already there\n let isAlreadyAtUrl = false;\n if (internalIframeRef.current?.contentWindow) {\n try {\n const currentIframeHref =\n internalIframeRef.current.contentWindow.location.href;\n if (new URL(currentIframeHref).href === new URL(fullUrl).href) {\n isAlreadyAtUrl = true;\n }\n } catch {\n // Cross-origin access ignored\n }\n }\n\n // Update History Stack regardless of isAlreadyAtUrl so arrow navigation is always correct\n if (history[currentIndex] !== fullUrl) {\n setHistory((prev) => {\n const newHistory = prev.slice(0, currentIndex + 1);\n newHistory.push(fullUrl);\n return newHistory;\n });\n setCurrentIndex((prev) => prev + 1);\n }\n\n // Navigate only if NOT already there to avoid refreshing on internal iframe navigation\n if (!isAlreadyAtUrl) {\n setCurrentUrl(fullUrl);\n }\n\n setError(null);\n } catch {\n // Ignore invalid paths\n }\n }, [path, domainRestriction, initialUrl]); // Removed currentIndex dependency to prevent loops\n\n // Imperatively keep the iframe src in sync with currentUrl.\n // React's attribute reconciliation can be unreliable for cross-origin iframes after\n // internal navigation, so this effect is the source of truth for navigation.\n useEffect(() => {\n const iframe = internalIframeRef.current;\n if (!iframe) return;\n if (iframe.src !== currentUrl) {\n iframe.src = currentUrl;\n }\n }, [currentUrl]);\n\n // --- Navigation Logic ------------------------------------------------------\n\n const handleNavigateTo = (url: string) => {\n try {\n const validated = normalizeUrl(url);\n\n // If we are navigating to the exact same URL, just reload\n if (validated === currentUrl) {\n handleReload();\n return;\n }\n\n setCurrentUrl(validated);\n setInputUrl(validated);\n setError(null);\n\n // Update History: Slice future if we went back, then push new\n const newHistory = history.slice(0, currentIndex + 1);\n newHistory.push(validated);\n setHistory(newHistory);\n setCurrentIndex(newHistory.length - 1);\n } catch (e) {\n if (\n e instanceof Error &&\n e.message === 'URL does not match allowed domain' &&\n domainRestriction\n ) {\n setError(\n content.domainRestrictionError?.value ??\n `Only URLs from ${domainRestriction} are allowed.`\n );\n } else {\n setError(content.errorMessage.value);\n }\n }\n };\n\n const handleBack = () => {\n if (currentIndex > 0) {\n const newIndex = currentIndex - 1;\n const prevUrl = history[newIndex];\n setCurrentIndex(newIndex);\n setCurrentUrl(prevUrl);\n setInputUrl(prevUrl);\n setError(null);\n }\n };\n\n const handleForward = () => {\n if (currentIndex < history.length - 1) {\n const newIndex = currentIndex + 1;\n const nextUrl = history[newIndex];\n setCurrentIndex(newIndex);\n setCurrentUrl(nextUrl);\n setInputUrl(nextUrl);\n setError(null);\n }\n };\n\n const handleSubmit = (e: SubmitEvent<HTMLFormElement>) => {\n e.preventDefault();\n setSubmitted(true);\n handleNavigateTo(inputUrl);\n };\n\n const handleReload = () => {\n const iframe = internalIframeRef.current;\n if (!iframe) return;\n const src = iframe.src;\n iframe.src = '';\n setTimeout(() => {\n if (internalIframeRef.current) internalIframeRef.current.src = src;\n }, 50);\n };\n\n // --- Validation Helpers ----------------------------------------------------\n const isValidHostname = (host: string) => {\n if (host === 'localhost') return true;\n if (/^(\\d{1,3}\\.){3}\\d{1,3}$/.test(host)) return true;\n if (/^[a-f0-9:]+$/i.test(host)) return true;\n if (!/^[a-z0-9.-]+$/i.test(host)) return false;\n if (/^[-.]/.test(host) || /[-.]$/.test(host)) return false;\n if (host.includes('..')) return false;\n if (!host.includes('.')) return false;\n return true;\n };\n\n const getRestrictionOrigin = (): URL | null => {\n if (!domainRestriction) return null;\n try {\n return new URL(domainRestriction);\n } catch {\n return null;\n }\n };\n\n const normalizeUrl = (raw: string) => {\n const trimmed = raw.trim();\n if (!trimmed || /\\s/.test(trimmed)) throw new Error('Invalid');\n\n const restrictionOrigin = getRestrictionOrigin();\n const isRelativePath = trimmed.startsWith('/') && !trimmed.startsWith('//');\n\n if (isRelativePath) {\n if (restrictionOrigin) {\n return new URL(`${restrictionOrigin.origin}${trimmed}`).toString();\n }\n return new URL(`${new URL(currentUrl).origin}${trimmed}`).toString();\n }\n\n const hasProtocol = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed);\n const candidate = hasProtocol ? trimmed : `https://${trimmed}`;\n const url = new URL(candidate);\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new Error('Only http(s) is allowed');\n }\n\n if (!isValidHostname(url.hostname)) throw new Error('Invalid host');\n\n if (restrictionOrigin) {\n const urlMatches =\n url.hostname === restrictionOrigin.hostname &&\n url.protocol === restrictionOrigin.protocol &&\n (restrictionOrigin.port === '' ||\n url.port === restrictionOrigin.port ||\n url.host === restrictionOrigin.host);\n\n if (!urlMatches) throw new Error('URL does not match allowed domain');\n }\n\n return url.toString();\n };\n\n const handleSitemapToggle = async () => {\n const nextOpen = !sitemapOpen;\n setSitemapOpen(nextOpen);\n\n if (nextOpen && !sitemapFetched) {\n setSitemapLoading(true);\n setSitemapError(false);\n setSitemapFetched(true);\n try {\n const { extractUrlFromSitemap } = await import(\n './extractUrlFromSitemap'\n );\n const urls = await extractUrlFromSitemap(currentUrl);\n setSitemapUrls(urls);\n if (urls.length === 0) setSitemapError(false);\n } catch {\n setSitemapError(true);\n } finally {\n setSitemapLoading(false);\n }\n setTimeout(() => sitemapInputRef.current?.focus(), 50);\n }\n };\n\n const filteredSitemapUrls = useMemo(() => {\n const query = sitemapSearch.trim().toLowerCase();\n if (!query) return sitemapUrls;\n return sitemapUrls.filter((url) => url.toLowerCase().includes(query));\n }, [sitemapUrls, sitemapSearch]);\n\n const showError = submitted && !!error;\n const canGoBack = currentIndex > 0;\n const canGoForward = currentIndex < history.length - 1;\n\n return (\n <section\n className={cn(\n 'flex w-full flex-col overflow-hidden rounded-xl bg-background shadow-[0_4px_12px_rgba(0,0,0,0.4),0_0_1px_rgba(0,0,0,0.2)]',\n className\n )}\n style={style}\n aria-label={ariaLabel ?? content.ariaLabel.value}\n >\n {/* Top bar */}\n <div className=\"relative z-10 flex shrink-0 items-center gap-3 rounded-t-xl bg-text/15 px-4 py-2\">\n {/* Navigation Controls */}\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n onClick={handleBack}\n disabled={!canGoBack}\n variant=\"hoverable\"\n size=\"icon-md\"\n label={content.backButtonLabel.value}\n Icon={ArrowLeft}\n />\n <Button\n type=\"button\"\n onClick={handleForward}\n disabled={!canGoForward}\n variant=\"hoverable\"\n size=\"icon-md\"\n label={content.forwardButtonLabel.value}\n Icon={ArrowRight}\n />\n </div>\n\n {/* URL Bar */}\n <form\n onSubmit={handleSubmit}\n noValidate\n className={cn(\n inputVariants(),\n 'flex w-full gap-2 rounded-xl p-0.5! supports-[corner-shape:squircle]:rounded-2xl',\n 'bg-neutral/10 text-text/50 placeholder:text-neutral/80'\n )}\n >\n <label htmlFor=\"browser-url\" className=\"sr-only\">\n {content.urlLabel}\n </label>\n <Input\n id=\"browser-url\"\n type=\"text\"\n inputMode=\"url\"\n spellCheck={false}\n autoCapitalize=\"off\"\n variant=\"invisible\"\n className=\"ml-3 p-0!\"\n size=\"sm\"\n autoCorrect=\"off\"\n value={inputUrl}\n onChange={(e) => {\n setInputUrl(e.target.value);\n if (showError) setError(null);\n }}\n placeholder={content.urlPlaceholder.value}\n aria-label={content.urlLabel.value}\n aria-invalid={showError}\n aria-describedby={showError ? 'browser-url-error' : undefined}\n />\n\n <Button\n type=\"button\"\n onClick={handleReload}\n variant=\"hoverable\"\n size=\"icon-md\"\n className=\"p-1!\"\n label=\"content.reloadButtonTitle.value\"\n Icon={RotateCw}\n />\n\n {/* invisible submit */}\n <button type=\"submit\" className=\"sr-only absolute\" tabIndex={-1} />\n </form>\n\n {/* Sitemap Explorer */}\n <ClickOutsideDiv\n onClickOutSide={() => setSitemapOpen(false)}\n disabled={!sitemapOpen}\n role=\"none\"\n >\n <DropDown identifier=\"sitemap-explorer\">\n <DropDown.Trigger\n identifier=\"sitemap-explorer-trigger\"\n type=\"button\"\n color=\"text\"\n onClick={handleSitemapToggle}\n variant=\"hoverable\"\n size=\"icon-md\"\n label={content.sitemapButtonLabel.value}\n Icon={ScanSearch}\n />\n\n <DropDown.Panel\n identifier=\"sitemap-explorer\"\n isHidden={!sitemapOpen}\n align=\"end\"\n isFocusable\n isOverable\n >\n <Container\n className=\"min-w-28 rounded-md!\"\n roundedSize=\"xl\"\n border\n borderColor=\"neutral\"\n >\n <div className=\"p-2\">\n <Input\n type=\"search\"\n ref={sitemapInputRef}\n aria-label={content.sitemapSearchAriaLabel.value}\n placeholder={content.sitemapSearchPlaceholder.value}\n onChange={(e) => setSitemapSearch(e.target.value)}\n value={sitemapSearch}\n size=\"sm\"\n />\n </div>\n <ul\n className=\"max-h-64 divide-y divide-dotted divide-neutral/30 overflow-y-auto p-1 text-center\"\n aria-label={content.sitemapButtonLabel.value}\n >\n {sitemapLoading ? (\n <li className=\"px-3 py-4 text-center text-neutral text-xs\">\n {content.sitemapLoading}\n </li>\n ) : sitemapError ||\n (!sitemapLoading && filteredSitemapUrls.length === 0) ? (\n <li className=\"px-3 py-4 text-center text-neutral text-xs\">\n {sitemapError\n ? content.sitemapError\n : content.sitemapEmpty}\n </li>\n ) : (\n filteredSitemapUrls.map((url) => (\n <li key={url} className=\"py-0.5\">\n <Button\n variant=\"hoverable\"\n color=\"text\"\n size=\"sm\"\n className=\"w-full text-left\"\n label={url}\n onClick={() => {\n handleNavigateTo(url);\n setSitemapOpen(false);\n }}\n >\n <span className=\"max-w-64 truncate text-left text-base\">\n <UrlPath url={url} />\n </span>\n </Button>\n </li>\n ))\n )}\n </ul>\n </Container>\n </DropDown.Panel>\n </DropDown>\n </ClickOutsideDiv>\n\n {/* Error Message Tooltip */}\n {showError && (\n <div className=\"absolute top-full left-4 z-20 mt-1\">\n <p\n id=\"browser-url-error\"\n role=\"alert\"\n aria-live=\"assertive\"\n className=\"rounded-md bg-red-900/90 px-3 py-1.5 text-red-100 text-xs shadow-md backdrop-blur-sm\"\n >\n {error}\n </p>\n </div>\n )}\n </div>\n\n {/* Iframe */}\n <div className=\"relative z-0 flex min-h-0 w-full flex-1 flex-col overflow-hidden rounded-b-xl bg-background\">\n <iframe\n ref={internalIframeRef}\n src={currentUrl}\n title={content.iframeTitle.value}\n className=\"size-full flex-1\"\n sandbox={sandbox ?? undefined}\n loading=\"lazy\"\n aria-live=\"polite\"\n {...props}\n />\n </div>\n </section>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useUser } from "../../api/useUser/index.mjs";
|
|
4
3
|
import { Button } from "../Button/Button.mjs";
|
|
4
|
+
import { useUser } from "../../api/useUser/index.mjs";
|
|
5
5
|
import { AutoCompleteTextarea } from "../TextArea/AutocompleteTextArea.mjs";
|
|
6
6
|
import { useEffect, useState } from "react";
|
|
7
7
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -82,7 +82,8 @@ const CopyButton = ({ content, ...props }) => {
|
|
|
82
82
|
const handleCopy = async () => {
|
|
83
83
|
try {
|
|
84
84
|
setError(false);
|
|
85
|
-
await
|
|
85
|
+
const text = typeof content === "function" ? await content() : content;
|
|
86
|
+
await navigator.clipboard.writeText(text);
|
|
86
87
|
setCopied(true);
|
|
87
88
|
} catch (error) {
|
|
88
89
|
console.error("Failed to copy text: ", error);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/components/CopyButton/index.tsx"],"sourcesContent":["'use client';\n\nimport { CopyCheckIcon, CopyIcon } from 'lucide-react';\nimport { type FC, useEffect, useState } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { Button, type ButtonProps } from '../Button';\n\n/**\n * Props for the CopyButton component\n */\ntype CopyButtonProps = {\n /**\n * The text content to copy to the clipboard\n * @example\n * ```tsx\n * <CopyButton content=\"Hello World!\" />\n * ```\n */\n content: string;\n} & Partial<ButtonProps>;\n\n/**\n * CopyButton - A specialized button component for copying text to the clipboard\n *\n * This component provides a user-friendly way to copy text content to the system clipboard\n * with visual feedback and accessibility features. It uses the modern Clipboard API with\n * fallback error handling, and provides clear visual indication of successful copy operations.\n *\n * ## Key Features\n * - **Clipboard Integration**: Uses modern Clipboard API for reliable text copying\n * - **Visual Feedback**: Icon changes from copy to check mark on successful copy\n * - **Auto-Reset**: Automatically reverts to copy icon after 1 second\n * - **Error Handling**: Graceful error handling with console logging\n * - **Accessibility**: Full keyboard navigation and screen reader support\n * - **Internationalization**: Multi-language support via Intlayer\n *\n * ## Use Cases\n * - Code snippet copying in documentation\n * - Sharing URLs or links\n * - Copying configuration values\n * - Form data duplication\n * - API key or token copying\n * - Text content sharing in interfaces\n *\n * ## Accessibility\n * - Uses semantic button element with proper ARIA labeling\n * - Keyboard accessible (Tab, Enter, Space)\n * - Screen reader announces copy actions\n * - Focus management with visible indicators\n * - Proper error state handling for assistive technologies\n *\n * ## Browser Compatibility\n * - Requires modern browsers with Clipboard API support\n * - Falls back gracefully with error logging for unsupported browsers\n * - Works in secure contexts (HTTPS) as required by Clipboard API\n *\n * @example\n * Basic usage:\n * ```tsx\n * <CopyButton content=\"Text to copy\" />\n * ```\n *\n * @example\n * With custom styling and label:\n * ```tsx\n * <CopyButton\n * content=\"https://example.com/api/endpoint\"\n * label=\"Copy API endpoint\"\n * variant=\"outline\"\n * color=\"primary\"\n * size=\"icon-md\"\n * />\n * ```\n *\n * @example\n * In a code block context:\n * ```tsx\n * <div className=\"relative\">\n * <pre className=\"bg-gray-100 p-4 rounded\">\n * <code>npm install @intlayer/design-system</code>\n * </pre>\n * <CopyButton\n * content=\"npm install @intlayer/design-system\"\n * className=\"absolute top-2 right-2\"\n * label=\"Copy installation command\"\n * />\n * </div>\n * ```\n */\n\nexport const CopyButton: FC<CopyButtonProps> = ({ content, ...props }) => {\n const [copied, setCopied] = useState(false);\n const [error, setError] = useState(false);\n const { label } = useIntlayer('copy-button');\n\n const handleCopy = async () => {\n try {\n setError(false);\n await navigator.clipboard.writeText(
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/components/CopyButton/index.tsx"],"sourcesContent":["'use client';\n\nimport { CopyCheckIcon, CopyIcon } from 'lucide-react';\nimport { type FC, useEffect, useState } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { Button, type ButtonProps } from '../Button';\n\n/**\n * Props for the CopyButton component\n */\ntype CopyButtonProps = {\n /**\n * The text content to copy to the clipboard, or a resolver invoked when the\n * button is pressed.\n *\n * The resolver form exists for content that is not worth keeping in memory —\n * or shipping to the browser — until someone actually asks for it.\n *\n * @example\n * ```tsx\n * <CopyButton content=\"Hello World!\" />\n * <CopyButton content={() => fetch(url).then((response) => response.text())} />\n * ```\n */\n content: string | (() => string | Promise<string>);\n // `content` is also an RDFa attribute on `HTMLAttributes`, whose `string`\n // type would intersect the resolver form away.\n} & Omit<Partial<ButtonProps>, 'content'>;\n\n/**\n * CopyButton - A specialized button component for copying text to the clipboard\n *\n * This component provides a user-friendly way to copy text content to the system clipboard\n * with visual feedback and accessibility features. It uses the modern Clipboard API with\n * fallback error handling, and provides clear visual indication of successful copy operations.\n *\n * ## Key Features\n * - **Clipboard Integration**: Uses modern Clipboard API for reliable text copying\n * - **Visual Feedback**: Icon changes from copy to check mark on successful copy\n * - **Auto-Reset**: Automatically reverts to copy icon after 1 second\n * - **Error Handling**: Graceful error handling with console logging\n * - **Accessibility**: Full keyboard navigation and screen reader support\n * - **Internationalization**: Multi-language support via Intlayer\n *\n * ## Use Cases\n * - Code snippet copying in documentation\n * - Sharing URLs or links\n * - Copying configuration values\n * - Form data duplication\n * - API key or token copying\n * - Text content sharing in interfaces\n *\n * ## Accessibility\n * - Uses semantic button element with proper ARIA labeling\n * - Keyboard accessible (Tab, Enter, Space)\n * - Screen reader announces copy actions\n * - Focus management with visible indicators\n * - Proper error state handling for assistive technologies\n *\n * ## Browser Compatibility\n * - Requires modern browsers with Clipboard API support\n * - Falls back gracefully with error logging for unsupported browsers\n * - Works in secure contexts (HTTPS) as required by Clipboard API\n *\n * @example\n * Basic usage:\n * ```tsx\n * <CopyButton content=\"Text to copy\" />\n * ```\n *\n * @example\n * With custom styling and label:\n * ```tsx\n * <CopyButton\n * content=\"https://example.com/api/endpoint\"\n * label=\"Copy API endpoint\"\n * variant=\"outline\"\n * color=\"primary\"\n * size=\"icon-md\"\n * />\n * ```\n *\n * @example\n * In a code block context:\n * ```tsx\n * <div className=\"relative\">\n * <pre className=\"bg-gray-100 p-4 rounded\">\n * <code>npm install @intlayer/design-system</code>\n * </pre>\n * <CopyButton\n * content=\"npm install @intlayer/design-system\"\n * className=\"absolute top-2 right-2\"\n * label=\"Copy installation command\"\n * />\n * </div>\n * ```\n */\n\nexport const CopyButton: FC<CopyButtonProps> = ({ content, ...props }) => {\n const [copied, setCopied] = useState(false);\n const [error, setError] = useState(false);\n const { label } = useIntlayer('copy-button');\n\n const handleCopy = async () => {\n try {\n setError(false);\n const text = typeof content === 'function' ? await content() : content;\n await navigator.clipboard.writeText(text);\n setCopied(true);\n } catch (error) {\n console.error('Failed to copy text: ', error);\n setError(true);\n }\n };\n\n useEffect(() => {\n if (copied || error) {\n const timer = setTimeout(() => {\n setCopied(false);\n setError(false);\n }, 1000);\n return () => clearTimeout(timer);\n }\n }, [copied, error]);\n\n // Determine the current state for accessibility\n const getAriaLabel = () => {\n if (copied) return 'Content copied to clipboard';\n if (error) return 'Failed to copy content';\n return props.label ?? label.value;\n };\n\n return (\n <Button\n Icon={copied ? CopyCheckIcon : CopyIcon}\n onClick={handleCopy}\n variant=\"hoverable\"\n color=\"text\"\n size=\"icon-sm\"\n tabIndex={0}\n title={getAriaLabel()}\n {...props}\n label={getAriaLabel()}\n aria-describedby={\n copied ? 'copy-success' : error ? 'copy-error' : undefined\n }\n />\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkGA,MAAa,cAAmC,EAAE,SAAS,GAAG,YAAY;CACxE,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAC1C,MAAM,CAAC,OAAO,YAAY,SAAS,KAAK;CACxC,MAAM,EAAE,UAAU,YAAY,aAAa;CAE3C,MAAM,aAAa,YAAY;EAC7B,IAAI;GACF,SAAS,KAAK;GACd,MAAM,OAAO,OAAO,YAAY,aAAa,MAAM,QAAQ,IAAI;GAC/D,MAAM,UAAU,UAAU,UAAU,IAAI;GACxC,UAAU,IAAI;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,yBAAyB,KAAK;GAC5C,SAAS,IAAI;EACf;CACF;CAEA,gBAAgB;EACd,IAAI,UAAU,OAAO;GACnB,MAAM,QAAQ,iBAAiB;IAC7B,UAAU,KAAK;IACf,SAAS,KAAK;GAChB,GAAG,GAAI;GACP,aAAa,aAAa,KAAK;EACjC;CACF,GAAG,CAAC,QAAQ,KAAK,CAAC;CAGlB,MAAM,qBAAqB;EACzB,IAAI,QAAQ,OAAO;EACnB,IAAI,OAAO,OAAO;EAClB,OAAO,MAAM,SAAS,MAAM;CAC9B;CAEA,OACE,oBAAC,QAAD;EACE,MAAM,SAAS,gBAAgB;EAC/B,SAAS;EACT,SAAQ;EACR,OAAM;EACN,MAAK;EACL,UAAU;EACV,OAAO,aAAa;EACpB,GAAI;EACJ,OAAO,aAAa;EACpB,oBACE,SAAS,iBAAiB,QAAQ,eAAe;CAEpD;AAEL"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useGetDictionaries } from "../../api/hooks/dictionary.mjs";
|
|
4
3
|
import { Container } from "../Container/index.mjs";
|
|
4
|
+
import { useGetDictionaries } from "../../api/hooks/dictionary.mjs";
|
|
5
5
|
import { LocaleSwitcherContent } from "../LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs";
|
|
6
6
|
import { Pagination } from "../Pagination/Pagination.mjs";
|
|
7
7
|
import { TextEditorContainer } from "./ContentEditorView/TextEditor.mjs";
|
|
@@ -10,14 +10,14 @@ import { KeyPathBreadcrumb } from "./KeyPathBreadcrumb.mjs";
|
|
|
10
10
|
import { NavigationViewNode } from "./NavigationView/NavigationViewNode.mjs";
|
|
11
11
|
import { useCallback, useDeferredValue, useEffect, useMemo, useState, useTransition } from "react";
|
|
12
12
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
-
import { useEditedContent, useEditorLocale, useFocusUnmergedDictionary } from "@intlayer/editor-react";
|
|
14
13
|
import { useIntlayer } from "react-intlayer";
|
|
14
|
+
import { useEditedContent, useEditorLocale, useFocusUnmergedDictionary } from "@intlayer/editor-react";
|
|
15
15
|
import { getContentNodeByKeyPath } from "@intlayer/core/dictionaryManipulator";
|
|
16
16
|
|
|
17
17
|
//#region src/components/DictionaryFieldEditor/ContentEditor.tsx
|
|
18
18
|
/** Render a variant (named string or structured object) for display. */
|
|
19
19
|
const formatVariant = (variant) => variant !== null && typeof variant === "object" ? JSON.stringify(variant) : variant ?? "";
|
|
20
|
-
const ContentEditor = ({ dictionary
|
|
20
|
+
const ContentEditor = ({ dictionary }) => {
|
|
21
21
|
const { itemPagination, variantSwitcher } = useIntlayer("content-editor");
|
|
22
22
|
const { editedContent } = useEditedContent();
|
|
23
23
|
const { focusedContent, setFocusedContentKeyPath: _setFocusedContentKeyPath } = useFocusUnmergedDictionary();
|
|
@@ -126,8 +126,7 @@ const ContentEditor = ({ dictionary, isDarkMode }) => {
|
|
|
126
126
|
children: /* @__PURE__ */ jsx(TextEditorContainer, {
|
|
127
127
|
keyPath: deferredKeyPath ?? [],
|
|
128
128
|
section: deferredSection,
|
|
129
|
-
dictionary: activeDictionary
|
|
130
|
-
isDarkMode
|
|
129
|
+
dictionary: activeDictionary
|
|
131
130
|
})
|
|
132
131
|
})]
|
|
133
132
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContentEditor.mjs","names":[],"sources":["../../../../src/components/DictionaryFieldEditor/ContentEditor.tsx"],"sourcesContent":["'use client';\n\nimport { useGetDictionaries } from '@api/index';\nimport { getContentNodeByKeyPath } from '@intlayer/core/dictionaryManipulator';\nimport {\n useEditedContent,\n useEditorLocale,\n useFocusUnmergedDictionary,\n} from '@intlayer/editor-react';\nimport type { Dictionary, LocalDictionaryId } from '@intlayer/types/dictionary';\nimport {\n type FC,\n useCallback,\n useDeferredValue,\n useEffect,\n useMemo,\n useState,\n useTransition,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { Container } from '../Container';\nimport { LocaleSwitcherContent } from '../LocaleSwitcherContentDropDown';\nimport { Pagination } from '../Pagination';\nimport { TextEditorContainer } from './ContentEditorView/TextEditor';\nimport { getIsEditableSection } from './getIsEditableSection';\nimport { KeyPathBreadcrumb } from './KeyPathBreadcrumb';\nimport { NavigationViewNode } from './NavigationView/NavigationViewNode';\n\ntype NodeEditorProps = {\n dictionary: Dictionary;\n isDarkMode?: boolean;\n};\n\n/** Render a variant (named string or structured object) for display. */\nconst formatVariant = (\n variant: string | Record<string, string | number> | undefined\n): string =>\n variant !== null && typeof variant === 'object'\n ? JSON.stringify(variant)\n : (variant ?? '');\n\nexport const ContentEditor: FC<NodeEditorProps> = ({\n dictionary,\n isDarkMode,\n}) => {\n const { itemPagination, variantSwitcher } = useIntlayer('content-editor');\n const { editedContent } = useEditedContent();\n const {\n focusedContent,\n setFocusedContentKeyPath: _setFocusedContentKeyPath,\n } = useFocusUnmergedDictionary();\n const [, startTransition] = useTransition();\n const setFocusedContentKeyPath: typeof _setFocusedContentKeyPath = (\n keyPath\n ) => startTransition(() => _setFocusedContentKeyPath(keyPath));\n\n const [activeDictionary, setActiveDictionary] =\n useState<Dictionary>(dictionary);\n\n // Re-sync the locally tracked dictionary whenever the incoming prop changes\n // identity — not only when its `localId` changes. After a save, the query\n // refetches and hands back the same `localId` with fresh content; keying the\n // effect on `localId` alone left `activeDictionary` (the render source) stale\n // until a full page reload. The prop reference is stable across unrelated\n // re-renders (query data / store entry), so sibling switches made via\n // `switchSibling` are preserved.\n useEffect(() => {\n setActiveDictionary(dictionary);\n }, [dictionary]);\n\n const hasQualifier =\n dictionary.item !== undefined || dictionary.variant !== undefined;\n\n const { data: siblingsData } = useGetDictionaries(\n { keys: [dictionary.key] },\n { enabled: hasQualifier && !!dictionary.key }\n );\n\n const allSiblings = useMemo<Dictionary[]>(\n () => (siblingsData?.data ?? []) as unknown as Dictionary[],\n [siblingsData]\n );\n\n const itemDicts = useMemo<Dictionary[]>(() => {\n if (dictionary.item === undefined) return [];\n return allSiblings\n .filter((d) => d.item !== undefined)\n .sort((a, b) => (a.item ?? 0) - (b.item ?? 0));\n }, [dictionary, allSiblings]);\n\n const variantDicts = useMemo<Dictionary[]>(() => {\n if (dictionary.variant === undefined) return [];\n return allSiblings.filter((d) => d.variant !== undefined);\n }, [dictionary, allSiblings]);\n\n const switchSibling = useCallback(\n (sibling: Dictionary) => {\n setActiveDictionary(sibling);\n setFocusedContentKeyPath([]);\n },\n [setFocusedContentKeyPath]\n );\n\n const { content, key, localId } = activeDictionary;\n const focusedKeyPath = focusedContent?.keyPath;\n const section =\n typeof editedContent?.[localId as LocalDictionaryId]?.content ===\n 'undefined'\n ? content\n : editedContent?.[localId as LocalDictionaryId]?.content;\n\n const currentLocale = useEditorLocale();\n const focusedSection = getContentNodeByKeyPath(\n section,\n focusedKeyPath ?? [],\n currentLocale\n );\n\n const deferredKeyPath = useDeferredValue(focusedKeyPath);\n const deferredSection = useDeferredValue(focusedSection);\n const isStale = deferredSection !== focusedSection;\n\n const isEditableBaseSection = getIsEditableSection(section);\n const isEditableFocusedSection = getIsEditableSection(deferredSection);\n\n useEffect(() => {\n if (typeof focusedSection === 'undefined') {\n setFocusedContentKeyPath(focusedContent?.keyPath?.slice(0, -1) ?? []);\n }\n }, []);\n\n const currentItemIndex = itemDicts.findIndex(\n (dictionary) => dictionary.localId === activeDictionary.localId\n );\n const currentVariant = activeDictionary.variant;\n\n return (\n <div>\n {/* Qualifier navigation controls */}\n {hasQualifier && (\n <div className=\"mb-4 flex flex-wrap items-center gap-4\">\n {itemDicts.length > 0 && (\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium text-muted text-xs\">\n {itemPagination.label}\n </span>\n <Pagination\n currentPage={currentItemIndex >= 0 ? currentItemIndex + 1 : 1}\n totalPages={itemDicts.length}\n onPageChange={(page) => {\n const target = itemDicts[page - 1];\n if (target) switchSibling(target);\n }}\n size=\"sm\"\n />\n </div>\n )}\n\n {variantDicts.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium text-muted text-xs\">\n {variantSwitcher.label}:\n </span>\n {variantDicts.map((sibling) => {\n const isActive = sibling.localId === activeDictionary.localId;\n return (\n <button\n key={sibling.localId}\n type=\"button\"\n onClick={() => !isActive && switchSibling(sibling)}\n className={`rounded-lg px-3 py-1 text-xs transition-colors ${\n isActive\n ? 'bg-text font-semibold text-text-opposite'\n : 'cursor-pointer border border-border hover:bg-text/10'\n }`}\n >\n {formatVariant(sibling.variant)}\n </button>\n );\n })}\n {currentVariant !== undefined &&\n !variantDicts.some((d) => d.localId === dictionary.localId) && (\n <span className=\"rounded-lg bg-text px-3 py-1 font-semibold text-text-opposite text-xs\">\n {formatVariant(currentVariant)}\n </span>\n )}\n </div>\n )}\n </div>\n )}\n\n <div className=\"mb-6 flex items-center justify-between gap-2\">\n <KeyPathBreadcrumb\n dictionaryKey={key}\n keyPath={focusedKeyPath ?? []}\n onClickKeyPath={setFocusedContentKeyPath}\n />\n <div className=\"flex items-center gap-2\">\n <LocaleSwitcherContent />\n </div>\n </div>\n <div className=\"flex flex-1 gap-2 overflow-visible max-md:flex-col\">\n {typeof section === 'object' &&\n section &&\n !isEditableBaseSection &&\n Object.keys(section).length > 0 && (\n <Container\n border\n background=\"none\"\n className=\"top-10 flex h-full flex-col items-start gap-0.5 overflow-auto p-2 md:sticky md:max-w-[50%]\"\n roundedSize=\"2xl\"\n transparency=\"xs\"\n >\n <NavigationViewNode\n keyPath={[]}\n section={section}\n dictionary={activeDictionary}\n />\n </Container>\n )}\n {(isEditableFocusedSection || (deferredKeyPath ?? []).length > 0) && (\n <div\n className={\n isStale\n ? 'pointer-events-none flex-1 opacity-50 transition-opacity'\n : 'flex-1 transition-opacity'\n }\n >\n <TextEditorContainer\n keyPath={deferredKeyPath ?? []}\n section={deferredSection}\n dictionary={activeDictionary}\n isDarkMode={isDarkMode}\n />\n </div>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkCA,MAAM,iBACJ,YAEA,YAAY,QAAQ,OAAO,YAAY,WACnC,KAAK,UAAU,OAAO,IACrB,WAAW;AAElB,MAAa,iBAAsC,EACjD,YACA,iBACI;CACJ,MAAM,EAAE,gBAAgB,oBAAoB,YAAY,gBAAgB;CACxE,MAAM,EAAE,kBAAkB,iBAAiB;CAC3C,MAAM,EACJ,gBACA,0BAA0B,8BACxB,2BAA2B;CAC/B,MAAM,GAAG,mBAAmB,cAAc;CAC1C,MAAM,4BACJ,YACG,sBAAsB,0BAA0B,OAAO,CAAC;CAE7D,MAAM,CAAC,kBAAkB,uBACvB,SAAqB,UAAU;CASjC,gBAAgB;EACd,oBAAoB,UAAU;CAChC,GAAG,CAAC,UAAU,CAAC;CAEf,MAAM,eACJ,WAAW,SAAS,UAAa,WAAW,YAAY;CAE1D,MAAM,EAAE,MAAM,iBAAiB,mBAC7B,EAAE,MAAM,CAAC,WAAW,GAAG,EAAE,GACzB,EAAE,SAAS,gBAAgB,CAAC,CAAC,WAAW,IAAI,CAC9C;CAEA,MAAM,cAAc,cACX,cAAc,QAAQ,CAAC,GAC9B,CAAC,YAAY,CACf;CAEA,MAAM,YAAY,cAA4B;EAC5C,IAAI,WAAW,SAAS,QAAW,OAAO,CAAC;EAC3C,OAAO,YACJ,QAAQ,MAAM,EAAE,SAAS,MAAS,CAAC,CACnC,MAAM,GAAG,OAAO,EAAE,QAAQ,MAAM,EAAE,QAAQ,EAAE;CACjD,GAAG,CAAC,YAAY,WAAW,CAAC;CAE5B,MAAM,eAAe,cAA4B;EAC/C,IAAI,WAAW,YAAY,QAAW,OAAO,CAAC;EAC9C,OAAO,YAAY,QAAQ,MAAM,EAAE,YAAY,MAAS;CAC1D,GAAG,CAAC,YAAY,WAAW,CAAC;CAE5B,MAAM,gBAAgB,aACnB,YAAwB;EACvB,oBAAoB,OAAO;EAC3B,yBAAyB,CAAC,CAAC;CAC7B,GACA,CAAC,wBAAwB,CAC3B;CAEA,MAAM,EAAE,SAAS,KAAK,YAAY;CAClC,MAAM,iBAAiB,gBAAgB;CACvC,MAAM,UACJ,OAAO,gBAAgB,QAA6B,EAAE,YACtD,cACI,UACA,gBAAgB,QAA6B,EAAE;CAErD,MAAM,gBAAgB,gBAAgB;CACtC,MAAM,iBAAiB,wBACrB,SACA,kBAAkB,CAAC,GACnB,aACF;CAEA,MAAM,kBAAkB,iBAAiB,cAAc;CACvD,MAAM,kBAAkB,iBAAiB,cAAc;CACvD,MAAM,UAAU,oBAAoB;CAEpC,MAAM,wBAAwB,qBAAqB,OAAO;CAC1D,MAAM,2BAA2B,qBAAqB,eAAe;CAErE,gBAAgB;EACd,IAAI,OAAO,mBAAmB,aAC5B,yBAAyB,gBAAgB,SAAS,MAAM,GAAG,EAAE,KAAK,CAAC,CAAC;CAExE,GAAG,CAAC,CAAC;CAEL,MAAM,mBAAmB,UAAU,WAChC,eAAe,WAAW,YAAY,iBAAiB,OAC1D;CACA,MAAM,iBAAiB,iBAAiB;CAExC,OACE,qBAAC,OAAD;EAEG,gBACC,qBAAC,OAAD;GAAK,WAAU;aAAf,CACG,UAAU,SAAS,KAClB,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,oBAAC,QAAD;KAAM,WAAU;eACb,eAAe;IACZ,IACN,oBAAC,YAAD;KACE,aAAa,oBAAoB,IAAI,mBAAmB,IAAI;KAC5D,YAAY,UAAU;KACtB,eAAe,SAAS;MACtB,MAAM,SAAS,UAAU,OAAO;MAChC,IAAI,QAAQ,cAAc,MAAM;KAClC;KACA,MAAK;IACN,EACE;OAGN,aAAa,SAAS,KACrB,qBAAC,OAAD;IAAK,WAAU;cAAf;KACE,qBAAC,QAAD;MAAM,WAAU;gBAAhB,CACG,gBAAgB,OAAM,GACnB;;KACL,aAAa,KAAK,YAAY;MAC7B,MAAM,WAAW,QAAQ,YAAY,iBAAiB;MACtD,OACE,oBAAC,UAAD;OAEE,MAAK;OACL,eAAe,CAAC,YAAY,cAAc,OAAO;OACjD,WAAW,kDACT,WACI,6CACA;iBAGL,cAAc,QAAQ,OAAO;MACxB,GAVD,QAAQ,OAUP;KAEZ,CAAC;KACA,mBAAmB,UAClB,CAAC,aAAa,MAAM,MAAM,EAAE,YAAY,WAAW,OAAO,KACxD,oBAAC,QAAD;MAAM,WAAU;gBACb,cAAc,cAAc;KACzB;IAEP;KAEJ;;EAGP,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,oBAAC,mBAAD;IACE,eAAe;IACf,SAAS,kBAAkB,CAAC;IAC5B,gBAAgB;GACjB,IACD,oBAAC,OAAD;IAAK,WAAU;cACb,oBAAC,uBAAD,CAAwB;GACrB,EACF;;EACL,qBAAC,OAAD;GAAK,WAAU;aAAf,CACG,OAAO,YAAY,YAClB,WACA,CAAC,yBACD,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,KAC5B,oBAAC,WAAD;IACE;IACA,YAAW;IACX,WAAU;IACV,aAAY;IACZ,cAAa;cAEb,oBAAC,oBAAD;KACE,SAAS,CAAC;KACD;KACT,YAAY;IACb;GACQ,KAEb,6BAA6B,mBAAmB,CAAC,EAAC,CAAE,SAAS,MAC7D,oBAAC,OAAD;IACE,WACE,UACI,6DACA;cAGN,oBAAC,qBAAD;KACE,SAAS,mBAAmB,CAAC;KAC7B,SAAS;KACT,YAAY;KACA;IACb;GACE,EAEJ;;CACF;AAET"}
|
|
1
|
+
{"version":3,"file":"ContentEditor.mjs","names":[],"sources":["../../../../src/components/DictionaryFieldEditor/ContentEditor.tsx"],"sourcesContent":["'use client';\n\nimport { useGetDictionaries } from '@api/index';\nimport { getContentNodeByKeyPath } from '@intlayer/core/dictionaryManipulator';\nimport {\n useEditedContent,\n useEditorLocale,\n useFocusUnmergedDictionary,\n} from '@intlayer/editor-react';\nimport type { Dictionary, LocalDictionaryId } from '@intlayer/types/dictionary';\nimport {\n type FC,\n useCallback,\n useDeferredValue,\n useEffect,\n useMemo,\n useState,\n useTransition,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { Container } from '../Container';\nimport { LocaleSwitcherContent } from '../LocaleSwitcherContentDropDown';\nimport { Pagination } from '../Pagination';\nimport { TextEditorContainer } from './ContentEditorView/TextEditor';\nimport { getIsEditableSection } from './getIsEditableSection';\nimport { KeyPathBreadcrumb } from './KeyPathBreadcrumb';\nimport { NavigationViewNode } from './NavigationView/NavigationViewNode';\n\ntype NodeEditorProps = {\n dictionary: Dictionary;\n};\n\n/** Render a variant (named string or structured object) for display. */\nconst formatVariant = (\n variant: string | Record<string, string | number> | undefined\n): string =>\n variant !== null && typeof variant === 'object'\n ? JSON.stringify(variant)\n : (variant ?? '');\n\nexport const ContentEditor: FC<NodeEditorProps> = ({ dictionary }) => {\n const { itemPagination, variantSwitcher } = useIntlayer('content-editor');\n const { editedContent } = useEditedContent();\n const {\n focusedContent,\n setFocusedContentKeyPath: _setFocusedContentKeyPath,\n } = useFocusUnmergedDictionary();\n const [, startTransition] = useTransition();\n const setFocusedContentKeyPath: typeof _setFocusedContentKeyPath = (\n keyPath\n ) => startTransition(() => _setFocusedContentKeyPath(keyPath));\n\n const [activeDictionary, setActiveDictionary] =\n useState<Dictionary>(dictionary);\n\n // Re-sync the locally tracked dictionary whenever the incoming prop changes\n // identity — not only when its `localId` changes. After a save, the query\n // refetches and hands back the same `localId` with fresh content; keying the\n // effect on `localId` alone left `activeDictionary` (the render source) stale\n // until a full page reload. The prop reference is stable across unrelated\n // re-renders (query data / store entry), so sibling switches made via\n // `switchSibling` are preserved.\n useEffect(() => {\n setActiveDictionary(dictionary);\n }, [dictionary]);\n\n const hasQualifier =\n dictionary.item !== undefined || dictionary.variant !== undefined;\n\n const { data: siblingsData } = useGetDictionaries(\n { keys: [dictionary.key] },\n { enabled: hasQualifier && !!dictionary.key }\n );\n\n const allSiblings = useMemo<Dictionary[]>(\n () => (siblingsData?.data ?? []) as unknown as Dictionary[],\n [siblingsData]\n );\n\n const itemDicts = useMemo<Dictionary[]>(() => {\n if (dictionary.item === undefined) return [];\n return allSiblings\n .filter((d) => d.item !== undefined)\n .sort((a, b) => (a.item ?? 0) - (b.item ?? 0));\n }, [dictionary, allSiblings]);\n\n const variantDicts = useMemo<Dictionary[]>(() => {\n if (dictionary.variant === undefined) return [];\n return allSiblings.filter((d) => d.variant !== undefined);\n }, [dictionary, allSiblings]);\n\n const switchSibling = useCallback(\n (sibling: Dictionary) => {\n setActiveDictionary(sibling);\n setFocusedContentKeyPath([]);\n },\n [setFocusedContentKeyPath]\n );\n\n const { content, key, localId } = activeDictionary;\n const focusedKeyPath = focusedContent?.keyPath;\n const section =\n typeof editedContent?.[localId as LocalDictionaryId]?.content ===\n 'undefined'\n ? content\n : editedContent?.[localId as LocalDictionaryId]?.content;\n\n const currentLocale = useEditorLocale();\n const focusedSection = getContentNodeByKeyPath(\n section,\n focusedKeyPath ?? [],\n currentLocale\n );\n\n const deferredKeyPath = useDeferredValue(focusedKeyPath);\n const deferredSection = useDeferredValue(focusedSection);\n const isStale = deferredSection !== focusedSection;\n\n const isEditableBaseSection = getIsEditableSection(section);\n const isEditableFocusedSection = getIsEditableSection(deferredSection);\n\n useEffect(() => {\n if (typeof focusedSection === 'undefined') {\n setFocusedContentKeyPath(focusedContent?.keyPath?.slice(0, -1) ?? []);\n }\n }, []);\n\n const currentItemIndex = itemDicts.findIndex(\n (dictionary) => dictionary.localId === activeDictionary.localId\n );\n const currentVariant = activeDictionary.variant;\n\n return (\n <div>\n {/* Qualifier navigation controls */}\n {hasQualifier && (\n <div className=\"mb-4 flex flex-wrap items-center gap-4\">\n {itemDicts.length > 0 && (\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium text-muted text-xs\">\n {itemPagination.label}\n </span>\n <Pagination\n currentPage={currentItemIndex >= 0 ? currentItemIndex + 1 : 1}\n totalPages={itemDicts.length}\n onPageChange={(page) => {\n const target = itemDicts[page - 1];\n if (target) switchSibling(target);\n }}\n size=\"sm\"\n />\n </div>\n )}\n\n {variantDicts.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium text-muted text-xs\">\n {variantSwitcher.label}:\n </span>\n {variantDicts.map((sibling) => {\n const isActive = sibling.localId === activeDictionary.localId;\n return (\n <button\n key={sibling.localId}\n type=\"button\"\n onClick={() => !isActive && switchSibling(sibling)}\n className={`rounded-lg px-3 py-1 text-xs transition-colors ${\n isActive\n ? 'bg-text font-semibold text-text-opposite'\n : 'cursor-pointer border border-border hover:bg-text/10'\n }`}\n >\n {formatVariant(sibling.variant)}\n </button>\n );\n })}\n {currentVariant !== undefined &&\n !variantDicts.some((d) => d.localId === dictionary.localId) && (\n <span className=\"rounded-lg bg-text px-3 py-1 font-semibold text-text-opposite text-xs\">\n {formatVariant(currentVariant)}\n </span>\n )}\n </div>\n )}\n </div>\n )}\n\n <div className=\"mb-6 flex items-center justify-between gap-2\">\n <KeyPathBreadcrumb\n dictionaryKey={key}\n keyPath={focusedKeyPath ?? []}\n onClickKeyPath={setFocusedContentKeyPath}\n />\n <div className=\"flex items-center gap-2\">\n <LocaleSwitcherContent />\n </div>\n </div>\n <div className=\"flex flex-1 gap-2 overflow-visible max-md:flex-col\">\n {typeof section === 'object' &&\n section &&\n !isEditableBaseSection &&\n Object.keys(section).length > 0 && (\n <Container\n border\n background=\"none\"\n className=\"top-10 flex h-full flex-col items-start gap-0.5 overflow-auto p-2 md:sticky md:max-w-[50%]\"\n roundedSize=\"2xl\"\n transparency=\"xs\"\n >\n <NavigationViewNode\n keyPath={[]}\n section={section}\n dictionary={activeDictionary}\n />\n </Container>\n )}\n {(isEditableFocusedSection || (deferredKeyPath ?? []).length > 0) && (\n <div\n className={\n isStale\n ? 'pointer-events-none flex-1 opacity-50 transition-opacity'\n : 'flex-1 transition-opacity'\n }\n >\n <TextEditorContainer\n keyPath={deferredKeyPath ?? []}\n section={deferredSection}\n dictionary={activeDictionary}\n />\n </div>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useAuditContentDeclarationField } from "../../../api/hooks/ai.mjs";
|
|
4
3
|
import { Loader } from "../../Loader/index.mjs";
|
|
5
4
|
import { Button } from "../../Button/Button.mjs";
|
|
6
5
|
import { Accordion } from "../../Accordion/Accordion.mjs";
|
|
@@ -8,6 +7,7 @@ import { Container } from "../../Container/index.mjs";
|
|
|
8
7
|
import { ContentEditorInput as ContentEditorInput$1 } from "../../ContentEditor/ContentEditorInput.mjs";
|
|
9
8
|
import { ContentEditorTextArea as ContentEditorTextArea$1 } from "../../ContentEditor/ContentEditorTextArea.mjs";
|
|
10
9
|
import { SwitchSelector } from "../../SwitchSelector/SwitchSelector.mjs";
|
|
10
|
+
import { useAuditContentDeclarationField } from "../../../api/hooks/ai.mjs";
|
|
11
11
|
import { useLocaleSwitcherContent } from "../../LocaleSwitcherContentDropDown/LocaleSwitcherContentContext.mjs";
|
|
12
12
|
import { renameKey } from "./object.mjs";
|
|
13
13
|
import { Label } from "../../Label/index.mjs";
|
|
@@ -16,9 +16,9 @@ import { SafeHtmlRenderer } from "./SafeHtmlRenderer.mjs";
|
|
|
16
16
|
import { Fragment, Suspense, lazy, memo, useState } from "react";
|
|
17
17
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
18
18
|
import { Plus, Trash, WandSparkles } from "lucide-react";
|
|
19
|
-
import { useConfiguration, useEditedContent } from "@intlayer/editor-react";
|
|
20
19
|
import { useIntlayer, useLocale } from "react-intlayer";
|
|
21
20
|
import { getLocaleName } from "@intlayer/core/localization";
|
|
21
|
+
import { useConfiguration, useEditedContent } from "@intlayer/editor-react";
|
|
22
22
|
import { getEmptyNode, getNodeType } from "@intlayer/core/dictionaryManipulator";
|
|
23
23
|
import * as NodeTypes from "@intlayer/types/nodeType";
|
|
24
24
|
import { camelCaseToSentence } from "@intlayer/config/client";
|
|
@@ -584,7 +584,7 @@ const HtmlTextEditor = ({ section, keyPath, dictionary }) => {
|
|
|
584
584
|
})]
|
|
585
585
|
});
|
|
586
586
|
};
|
|
587
|
-
const MarkdownTextEditor = ({ section, keyPath, dictionary
|
|
587
|
+
const MarkdownTextEditor = ({ section, keyPath, dictionary }) => {
|
|
588
588
|
const [mode, setMode] = useState(0);
|
|
589
589
|
const { addEditedContent } = useEditedContent();
|
|
590
590
|
const toggleContent = [
|
|
@@ -619,10 +619,7 @@ const MarkdownTextEditor = ({ section, keyPath, dictionary, isDarkMode }) => {
|
|
|
619
619
|
});
|
|
620
620
|
const renderMarkdownPreview = (leafContent) => /* @__PURE__ */ jsx(Suspense, {
|
|
621
621
|
fallback: /* @__PURE__ */ jsx(Loader, {}),
|
|
622
|
-
children: /* @__PURE__ */ jsx(LazyMarkdownRenderer, {
|
|
623
|
-
isDarkMode,
|
|
624
|
-
children: leafContent
|
|
625
|
-
})
|
|
622
|
+
children: /* @__PURE__ */ jsx(LazyMarkdownRenderer, { children: leafContent })
|
|
626
623
|
});
|
|
627
624
|
return /* @__PURE__ */ jsxs("div", {
|
|
628
625
|
className: "flex w-full flex-col justify-center gap-6 p-2",
|
|
@@ -707,7 +704,7 @@ const NestedTextEditor = ({ keyPath, dictionary, renderSection, section, ...prop
|
|
|
707
704
|
]
|
|
708
705
|
});
|
|
709
706
|
};
|
|
710
|
-
const TextEditor = memo(function TextEditor({ section, keyPath, dictionary, renderSection
|
|
707
|
+
const TextEditor = memo(function TextEditor({ section, keyPath, dictionary, renderSection }) {
|
|
711
708
|
const { tsxNotEditable } = useIntlayer("navigation-view");
|
|
712
709
|
const nodeType = getNodeType(section);
|
|
713
710
|
if (nodeType === NodeTypes.REACT_NODE) return /* @__PURE__ */ jsxs("div", {
|
|
@@ -768,8 +765,7 @@ const TextEditor = memo(function TextEditor({ section, keyPath, dictionary, rend
|
|
|
768
765
|
if (nodeType === NodeTypes.MARKDOWN) return /* @__PURE__ */ jsx(MarkdownTextEditor, {
|
|
769
766
|
dictionary,
|
|
770
767
|
keyPath,
|
|
771
|
-
section
|
|
772
|
-
isDarkMode
|
|
768
|
+
section
|
|
773
769
|
});
|
|
774
770
|
if (nodeType === NodeTypes.HTML) return /* @__PURE__ */ jsx(HtmlTextEditor, {
|
|
775
771
|
dictionary,
|