@c-rex/contexts 0.1.9 → 0.1.11
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/package.json +7 -3
- package/src/config-provider.tsx +61 -42
- package/src/highlight-provider.tsx +77 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c-rex/contexts",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src"
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"./search": {
|
|
17
17
|
"types": "./src/search.tsx",
|
|
18
18
|
"import": "./src/search.tsx"
|
|
19
|
+
},
|
|
20
|
+
"./highlight-provider": {
|
|
21
|
+
"types": "./src/highlight-provider.tsx",
|
|
22
|
+
"import": "./src/highlight-provider.tsx"
|
|
19
23
|
}
|
|
20
24
|
},
|
|
21
25
|
"scripts": {
|
|
@@ -34,8 +38,8 @@
|
|
|
34
38
|
},
|
|
35
39
|
"dependencies": {
|
|
36
40
|
"next": "^14",
|
|
37
|
-
"react": "^18",
|
|
38
|
-
"react-dom": "^18",
|
|
41
|
+
"react": "^18.3.1",
|
|
42
|
+
"react-dom": "^18.3.1",
|
|
39
43
|
"@c-rex/core": "*",
|
|
40
44
|
"@c-rex/constants": "*",
|
|
41
45
|
"@c-rex/services": "*",
|
package/src/config-provider.tsx
CHANGED
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { CREX_TOKEN_HEADER_KEY, UI_LANG_OPTIONS } from '@c-rex/constants'
|
|
4
4
|
import { ConfigInterface, LanguageAndCountries, AvailableVersionsInterface } from '@c-rex/interfaces'
|
|
5
|
-
import { call
|
|
5
|
+
import { call } from '@c-rex/utils'
|
|
6
6
|
import { getConfigs } from '@c-rex/utils/next-cookies'
|
|
7
7
|
import React, { createContext, useContext, useEffect, useState } from 'react'
|
|
8
|
+
import { useLanguageStore } from '@c-rex/components/language-store'
|
|
8
9
|
|
|
9
10
|
type AppConfigContextType = {
|
|
10
11
|
error: Error | null
|
|
11
|
-
uiLang: string | null
|
|
12
12
|
configs: ConfigInterface
|
|
13
13
|
loading: boolean
|
|
14
14
|
packageID: string | null
|
|
15
|
-
contentLang: string | null
|
|
16
15
|
articleLang: string | null
|
|
17
16
|
availableVersions: AvailableVersionsInterface[] | null
|
|
18
|
-
availableLanguagesAndCountries: LanguageAndCountries[]
|
|
19
|
-
setUiLang: (lang: string | null) => void
|
|
20
|
-
setContentLang: (lang: string | null) => void
|
|
21
17
|
setAvailableVersions: (versions: AvailableVersionsInterface[] | null) => void
|
|
22
18
|
setPackageID: (id: string | null) => void
|
|
23
19
|
setArticleLang: (lang: string | null) => void
|
|
@@ -27,31 +23,34 @@ const AppConfigContext = createContext<AppConfigContextType | undefined>(undefin
|
|
|
27
23
|
|
|
28
24
|
export const AppConfigProvider = ({ children }: { children: React.ReactNode }) => {
|
|
29
25
|
const [error, setError] = useState<Error | null>(null)
|
|
30
|
-
const [uiLang, setUiLang] = useState<string | null>(null)
|
|
31
26
|
const [configs, setConfigs] = useState<ConfigInterface | null>(null)
|
|
32
27
|
const [loading, setLoading] = useState(true)
|
|
33
28
|
const [packageID, setPackageID] = useState<string | null>(null)
|
|
34
29
|
const [articleLang, setArticleLang] = useState<string | null>(null)
|
|
35
|
-
const [contentLang, setContentLang] = useState<string | null>(null)
|
|
36
30
|
const [availableVersions, setAvailableVersions] = useState<AvailableVersionsInterface[] | null>(null)
|
|
37
|
-
|
|
31
|
+
|
|
38
32
|
|
|
39
33
|
const manageUILanguage = async (configs: ConfigInterface): Promise<void> => {
|
|
40
|
-
|
|
34
|
+
const uiLang = useLanguageStore.getState().uiLang;
|
|
35
|
+
const setUiLang = useLanguageStore.getState().setUiLang;
|
|
36
|
+
let locale = uiLang
|
|
37
|
+
|
|
41
38
|
if (locale.length === 0) {
|
|
42
39
|
const browserLang = navigator.language;
|
|
43
40
|
|
|
44
41
|
locale = UI_LANG_OPTIONS.includes(browserLang.toLowerCase())
|
|
45
42
|
? browserLang
|
|
46
43
|
: configs.languageSwitcher.default;
|
|
47
|
-
|
|
48
|
-
await setCookie(UI_LANG_KEY, locale);
|
|
49
44
|
}
|
|
50
45
|
setUiLang(locale)
|
|
51
46
|
}
|
|
52
47
|
|
|
53
|
-
const manageContentLanguage = async (configs: ConfigInterface
|
|
54
|
-
|
|
48
|
+
const manageContentLanguage = async (configs: ConfigInterface): Promise<void> => {
|
|
49
|
+
const contentLang = useLanguageStore.getState().contentLang;
|
|
50
|
+
const setContentLang = useLanguageStore.getState().setContentLang;
|
|
51
|
+
const availableLanguages = useLanguageStore.getState().availableLanguages;
|
|
52
|
+
|
|
53
|
+
let locale = contentLang
|
|
55
54
|
if (locale.length === 0) {
|
|
56
55
|
const browserLang = navigator.language;
|
|
57
56
|
const hasLang = availableLanguages.some((item) => item.value === browserLang);
|
|
@@ -59,22 +58,25 @@ export const AppConfigProvider = ({ children }: { children: React.ReactNode }) =
|
|
|
59
58
|
locale = hasLang
|
|
60
59
|
? browserLang
|
|
61
60
|
: configs.languageSwitcher.default;
|
|
62
|
-
|
|
63
|
-
await setCookie(CONTENT_LANG_KEY, locale);
|
|
64
61
|
}
|
|
65
62
|
|
|
66
63
|
setContentLang(locale)
|
|
67
64
|
}
|
|
68
65
|
|
|
69
|
-
const manageToken = async (): Promise<void> => {
|
|
66
|
+
const manageToken = async (configs: ConfigInterface): Promise<void> => {
|
|
70
67
|
try {
|
|
71
|
-
const
|
|
68
|
+
const url = configs.publicNextApiUrl
|
|
69
|
+
const res = await fetch(`${url}/api/cookies?key=${CREX_TOKEN_HEADER_KEY}`);
|
|
70
|
+
|
|
71
|
+
if (!res.ok) {
|
|
72
|
+
await requestToken(configs);
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const hasToken = await res.json();
|
|
72
77
|
|
|
73
78
|
if (hasToken.value === null) {
|
|
74
|
-
await
|
|
75
|
-
method: 'POST',
|
|
76
|
-
credentials: 'include',
|
|
77
|
-
});
|
|
79
|
+
await requestToken(configs);
|
|
78
80
|
}
|
|
79
81
|
} catch (error) {
|
|
80
82
|
call("CrexLogger.log", {
|
|
@@ -84,40 +86,62 @@ export const AppConfigProvider = ({ children }: { children: React.ReactNode }) =
|
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
async
|
|
89
|
+
const requestToken = async (configs: ConfigInterface): Promise<void> => {
|
|
90
|
+
try {
|
|
91
|
+
await fetch(`${configs.publicNextApiUrl}/api/token`, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
credentials: 'include',
|
|
94
|
+
});
|
|
95
|
+
} catch (error) {
|
|
96
|
+
call("CrexLogger.log", {
|
|
97
|
+
level: "error",
|
|
98
|
+
message: `config-provider.requestToken error: ${error}`
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const interval = async (delayMs: number, configs: ConfigInterface) => {
|
|
88
104
|
while (true) {
|
|
89
105
|
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
90
|
-
await manageToken()
|
|
106
|
+
await manageToken(configs);
|
|
91
107
|
}
|
|
92
108
|
}
|
|
93
109
|
|
|
110
|
+
const updateLanguages = async () => {
|
|
111
|
+
const setAvailableLanguages = useLanguageStore.getState().setAvailableLanguages;
|
|
112
|
+
|
|
113
|
+
const langs = await call<LanguageAndCountries[]>("LanguageService.getLanguagesAndCountries")
|
|
114
|
+
setAvailableLanguages(langs)
|
|
115
|
+
}
|
|
116
|
+
|
|
94
117
|
useEffect(() => {
|
|
118
|
+
useLanguageStore.getState().hydrate();
|
|
119
|
+
|
|
95
120
|
const load = async () => {
|
|
96
121
|
try {
|
|
97
122
|
setLoading(true)
|
|
98
123
|
|
|
99
|
-
await
|
|
100
|
-
|
|
101
|
-
const [configsResult, langs] = await Promise.all([
|
|
102
|
-
getConfigs(),
|
|
103
|
-
call<LanguageAndCountries[]>("LanguageService.getLanguagesAndCountries")
|
|
104
|
-
])
|
|
105
|
-
|
|
124
|
+
const configsResult = await getConfigs()
|
|
106
125
|
if (!configsResult) {
|
|
107
126
|
throw new Error("Config cookie not available");
|
|
108
127
|
}
|
|
109
128
|
|
|
129
|
+
await manageToken(configsResult)
|
|
130
|
+
|
|
110
131
|
await Promise.all([
|
|
111
132
|
manageUILanguage(configsResult),
|
|
112
|
-
manageContentLanguage(configsResult,
|
|
133
|
+
manageContentLanguage(configsResult),
|
|
134
|
+
updateLanguages()
|
|
113
135
|
])
|
|
114
136
|
|
|
115
|
-
setAvailableLanguagesAndCountries(langs)
|
|
116
|
-
setConfigs(configsResult)
|
|
117
137
|
setError(null)
|
|
118
|
-
|
|
119
|
-
interval(1000 * 60 * 9);
|
|
138
|
+
setConfigs(configsResult)
|
|
139
|
+
interval(1000 * 60 * 9, configsResult);
|
|
120
140
|
} catch (err) {
|
|
141
|
+
call("CrexLogger.log", {
|
|
142
|
+
level: "error",
|
|
143
|
+
message: `config-provider.load error: ${err}`
|
|
144
|
+
});
|
|
121
145
|
setError(err as Error)
|
|
122
146
|
} finally {
|
|
123
147
|
setLoading(false)
|
|
@@ -137,17 +161,12 @@ export const AppConfigProvider = ({ children }: { children: React.ReactNode }) =
|
|
|
137
161
|
<AppConfigContext.Provider
|
|
138
162
|
value={{
|
|
139
163
|
configs: configs!,
|
|
140
|
-
availableLanguagesAndCountries: availableLanguagesAndCountries!,
|
|
141
164
|
loading,
|
|
142
165
|
error,
|
|
143
|
-
uiLang,
|
|
144
|
-
contentLang,
|
|
145
166
|
availableVersions,
|
|
146
167
|
packageID,
|
|
147
168
|
articleLang,
|
|
148
169
|
setArticleLang,
|
|
149
|
-
setUiLang,
|
|
150
|
-
setContentLang,
|
|
151
170
|
setAvailableVersions,
|
|
152
171
|
setPackageID
|
|
153
172
|
}}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
} from "react";
|
|
11
|
+
|
|
12
|
+
type HighlightContextType = {
|
|
13
|
+
currentIndex: number;
|
|
14
|
+
total: number;
|
|
15
|
+
next: () => void;
|
|
16
|
+
prev: () => void;
|
|
17
|
+
registerContainer: (el: HTMLElement | null) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const HighlightContext = createContext<HighlightContextType | null>(null);
|
|
21
|
+
|
|
22
|
+
export const HighlightProvider = ({ children }: { children: React.ReactNode }) => {
|
|
23
|
+
|
|
24
|
+
const containerRef = useRef<HTMLElement | null>(null);
|
|
25
|
+
const [marks, setMarks] = useState<NodeListOf<HTMLElement> | null>(null);
|
|
26
|
+
const [currentIndex, setCurrentIndex] = useState(-1);
|
|
27
|
+
|
|
28
|
+
const registerContainer = useCallback((el: HTMLElement | null) => {
|
|
29
|
+
containerRef.current = el;
|
|
30
|
+
if (el) {
|
|
31
|
+
const found = el.querySelectorAll("mark");
|
|
32
|
+
setMarks(found);
|
|
33
|
+
setCurrentIndex(found.length > 0 ? 0 : -1);
|
|
34
|
+
} else {
|
|
35
|
+
setMarks(null);
|
|
36
|
+
setCurrentIndex(-1);
|
|
37
|
+
}
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (marks && currentIndex >= 0 && marks[currentIndex]) {
|
|
42
|
+
marks.forEach((m) => m.classList.remove("bg-yellow-200", "bg-orange-300"));
|
|
43
|
+
marks[currentIndex].classList.add("bg-orange-300");
|
|
44
|
+
marks[currentIndex].scrollIntoView({ behavior: "smooth", block: "center" });
|
|
45
|
+
}
|
|
46
|
+
}, [currentIndex, marks]);
|
|
47
|
+
|
|
48
|
+
const next = () => {
|
|
49
|
+
if (!marks || marks.length === 0) return;
|
|
50
|
+
setCurrentIndex((i) => (i + 1) % marks.length);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const prev = () => {
|
|
54
|
+
if (!marks || marks.length === 0) return;
|
|
55
|
+
setCurrentIndex((i) => (i - 1 + marks.length) % marks.length);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<HighlightContext.Provider
|
|
60
|
+
value={{
|
|
61
|
+
currentIndex,
|
|
62
|
+
total: marks?.length ?? 0,
|
|
63
|
+
next,
|
|
64
|
+
prev,
|
|
65
|
+
registerContainer,
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
{children}
|
|
69
|
+
</HighlightContext.Provider>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const useHighlight = () => {
|
|
74
|
+
const ctx = useContext(HighlightContext);
|
|
75
|
+
if (!ctx) throw new Error("useHighlight must be used inside HighlightProvider");
|
|
76
|
+
return ctx;
|
|
77
|
+
};
|