@arkitektbedriftene/fe-lib 0.3.12 → 0.3.14
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/dist/{Badge-3f33be41.js → Badge-5eab3787.js} +4 -3
- package/dist/colors.d.ts +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/icons.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/oidc.d.ts +1 -1
- package/dist/{lib/rich-text → rich-text}/Editor.d.ts +1 -1
- package/dist/rich-text.d.ts +1 -1
- package/dist/rich-text.es.js +4 -4
- package/dist/{lib/ui → ui}/stitches.config.d.ts +1 -0
- package/dist/ui.css.d.ts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.es.js +2 -2
- package/package.json +16 -9
- package/.github/workflows/auto-publish.yaml +0 -20
- package/dist/lib/index.d.ts +0 -1
- package/index.html +0 -13
- package/src/App.css +0 -42
- package/src/App.tsx +0 -44
- package/src/assets/react.svg +0 -1
- package/src/index.css +0 -69
- package/src/lib/colors/colors.ts +0 -19
- package/src/lib/hooks/hooks.ts +0 -3
- package/src/lib/icons/README.md +0 -1
- package/src/lib/icons/icons.tsx +0 -51
- package/src/lib/index.ts +0 -1
- package/src/lib/oidc/README.md +0 -28
- package/src/lib/oidc/firmAccess.ts +0 -36
- package/src/lib/oidc/impersonate.tsx +0 -104
- package/src/lib/oidc/oidc.tsx +0 -248
- package/src/lib/rich-text/Editor.tsx +0 -137
- package/src/lib/rich-text/config.ts +0 -16
- package/src/lib/rich-text/editorContext.ts +0 -34
- package/src/lib/rich-text/rich-text.ts +0 -4
- package/src/lib/rich-text/state.ts +0 -62
- package/src/lib/rich-text/theme.ts +0 -41
- package/src/lib/rich-text/trim.ts +0 -79
- package/src/lib/ui/components/Alert.tsx +0 -63
- package/src/lib/ui/components/Badge.tsx +0 -42
- package/src/lib/ui/components/Box.tsx +0 -3
- package/src/lib/ui/components/Button.tsx +0 -195
- package/src/lib/ui/components/OverlayCard.tsx +0 -50
- package/src/lib/ui/components/Popover.tsx +0 -164
- package/src/lib/ui/components/Spinner.tsx +0 -102
- package/src/lib/ui/components/Tooltip.tsx +0 -34
- package/src/lib/ui/stitches.config.ts +0 -144
- package/src/lib/ui/ui.css +0 -11
- package/src/lib/ui/ui.ts +0 -10
- package/src/main.tsx +0 -10
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.json +0 -21
- package/tsconfig.node.json +0 -9
- package/vite.config.ts +0 -45
- /package/dist/{lib/colors → colors}/colors.d.ts +0 -0
- /package/dist/{lib/hooks → hooks}/hooks.d.ts +0 -0
- /package/dist/{lib/icons → icons}/icons.d.ts +0 -0
- /package/dist/{lib/oidc → oidc}/firmAccess.d.ts +0 -0
- /package/dist/{lib/oidc → oidc}/impersonate.d.ts +0 -0
- /package/dist/{lib/oidc → oidc}/oidc.d.ts +0 -0
- /package/dist/{lib/rich-text → rich-text}/config.d.ts +0 -0
- /package/dist/{lib/rich-text → rich-text}/editorContext.d.ts +0 -0
- /package/dist/{lib/rich-text → rich-text}/rich-text.d.ts +0 -0
- /package/dist/{lib/rich-text → rich-text}/state.d.ts +0 -0
- /package/dist/{lib/rich-text → rich-text}/theme.d.ts +0 -0
- /package/dist/{lib/rich-text → rich-text}/trim.d.ts +0 -0
- /package/dist/{lib/ui → ui}/components/Alert.d.ts +0 -0
- /package/dist/{lib/ui → ui}/components/Badge.d.ts +0 -0
- /package/dist/{lib/ui → ui}/components/Box.d.ts +0 -0
- /package/dist/{lib/ui → ui}/components/Button.d.ts +0 -0
- /package/dist/{lib/ui → ui}/components/OverlayCard.d.ts +0 -0
- /package/dist/{lib/ui → ui}/components/Popover.d.ts +0 -0
- /package/dist/{lib/ui → ui}/components/Spinner.d.ts +0 -0
- /package/dist/{lib/ui → ui}/components/Tooltip.d.ts +0 -0
- /package/dist/{lib/ui → ui}/ui.d.ts +0 -0
package/src/lib/oidc/oidc.tsx
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
import { SigninRedirectArgs, User, UserManager } from "oidc-client-ts";
|
|
2
|
-
import {
|
|
3
|
-
Context,
|
|
4
|
-
createContext,
|
|
5
|
-
ReactNode,
|
|
6
|
-
useCallback,
|
|
7
|
-
useContext,
|
|
8
|
-
useEffect,
|
|
9
|
-
useMemo,
|
|
10
|
-
useRef,
|
|
11
|
-
useState,
|
|
12
|
-
} from "react";
|
|
13
|
-
|
|
14
|
-
export * from "oidc-client-ts";
|
|
15
|
-
export * from "./impersonate";
|
|
16
|
-
export * from "./firmAccess"
|
|
17
|
-
|
|
18
|
-
export type AuthState = {
|
|
19
|
-
user: User | null;
|
|
20
|
-
isLoading: boolean;
|
|
21
|
-
isAuthenticated: boolean;
|
|
22
|
-
isError: boolean;
|
|
23
|
-
error: Error | null;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export type AuthContextData = {
|
|
27
|
-
state: AuthState;
|
|
28
|
-
handleSigninCallback: () => Promise<User | undefined>;
|
|
29
|
-
redirectToSignin: UserManager["signinRedirect"];
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export type AuthProviderConfig = {
|
|
33
|
-
onSigninComplete: (user: User | null) => void;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const AuthProviderCore = ({
|
|
37
|
-
userManager,
|
|
38
|
-
context,
|
|
39
|
-
children,
|
|
40
|
-
}: {
|
|
41
|
-
userManager: UserManager;
|
|
42
|
-
context: Context<AuthContextData | null>;
|
|
43
|
-
children: ReactNode;
|
|
44
|
-
}) => {
|
|
45
|
-
const [state, setState] = useState<AuthState>({
|
|
46
|
-
user: null,
|
|
47
|
-
isLoading: true,
|
|
48
|
-
isAuthenticated: false,
|
|
49
|
-
isError: false,
|
|
50
|
-
error: null,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// Initialize on first render
|
|
54
|
-
const isInitialized = useRef(false);
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
if (isInitialized.current) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
isInitialized.current = true;
|
|
60
|
-
|
|
61
|
-
void (async () => {
|
|
62
|
-
try {
|
|
63
|
-
const user = await userManager.getUser();
|
|
64
|
-
setState({
|
|
65
|
-
user,
|
|
66
|
-
isLoading: false,
|
|
67
|
-
isAuthenticated: user ? !user.expired : false,
|
|
68
|
-
isError: false,
|
|
69
|
-
error: null,
|
|
70
|
-
});
|
|
71
|
-
} catch (error) {
|
|
72
|
-
setState({
|
|
73
|
-
user: null,
|
|
74
|
-
isLoading: false,
|
|
75
|
-
isAuthenticated: false,
|
|
76
|
-
isError: true,
|
|
77
|
-
error:
|
|
78
|
-
error instanceof Error
|
|
79
|
-
? error
|
|
80
|
-
: new Error("Unknown error during auth"),
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
})();
|
|
84
|
-
}, [userManager]);
|
|
85
|
-
|
|
86
|
-
// Set up UserManager events
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
const onUserLoaded = (user: User) => {
|
|
89
|
-
setState({
|
|
90
|
-
user,
|
|
91
|
-
isLoading: false,
|
|
92
|
-
isAuthenticated: !user.expired,
|
|
93
|
-
isError: false,
|
|
94
|
-
error: null,
|
|
95
|
-
});
|
|
96
|
-
};
|
|
97
|
-
userManager.events.addUserLoaded(onUserLoaded);
|
|
98
|
-
|
|
99
|
-
const onUserUnloaded = () => {
|
|
100
|
-
setState({
|
|
101
|
-
...state,
|
|
102
|
-
user: null,
|
|
103
|
-
isAuthenticated: false,
|
|
104
|
-
});
|
|
105
|
-
};
|
|
106
|
-
userManager.events.addUserUnloaded(onUserUnloaded);
|
|
107
|
-
|
|
108
|
-
const onSilentRenewError = (error: Error) => {
|
|
109
|
-
setState({
|
|
110
|
-
...state,
|
|
111
|
-
isLoading: false,
|
|
112
|
-
isError: true,
|
|
113
|
-
error,
|
|
114
|
-
});
|
|
115
|
-
};
|
|
116
|
-
userManager.events.addSilentRenewError(onSilentRenewError);
|
|
117
|
-
|
|
118
|
-
return () => {
|
|
119
|
-
userManager.events.removeUserLoaded(onUserLoaded);
|
|
120
|
-
userManager.events.removeUserUnloaded(onUserUnloaded);
|
|
121
|
-
userManager.events.removeSilentRenewError(onSilentRenewError);
|
|
122
|
-
};
|
|
123
|
-
}, [userManager]);
|
|
124
|
-
|
|
125
|
-
const handleSigninCallback = useCallback(async () => {
|
|
126
|
-
const user = await userManager.signinCallback();
|
|
127
|
-
|
|
128
|
-
setState({
|
|
129
|
-
user: user ?? null,
|
|
130
|
-
isLoading: false,
|
|
131
|
-
isAuthenticated: user ? !user.expired : false,
|
|
132
|
-
isError: false,
|
|
133
|
-
error: null,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
return user ?? undefined;
|
|
137
|
-
}, [userManager]);
|
|
138
|
-
|
|
139
|
-
const redirectToSignin = useCallback(
|
|
140
|
-
async (args?: SigninRedirectArgs | undefined) => {
|
|
141
|
-
try {
|
|
142
|
-
await userManager.signinRedirect(args);
|
|
143
|
-
} catch (error) {
|
|
144
|
-
console.error(error);
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
[userManager]
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
const contextValue = useMemo(
|
|
151
|
-
() => ({
|
|
152
|
-
state,
|
|
153
|
-
handleSigninCallback,
|
|
154
|
-
redirectToSignin,
|
|
155
|
-
}),
|
|
156
|
-
[state, handleSigninCallback, redirectToSignin]
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
return <context.Provider value={contextValue}>{children}</context.Provider>;
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const useAuthContextCore = (context: Context<AuthContextData | null>) => {
|
|
163
|
-
const contextData = useContext(context);
|
|
164
|
-
if (!contextData) {
|
|
165
|
-
throw new Error("useAuthContext must be used within an AuthProvider");
|
|
166
|
-
}
|
|
167
|
-
return contextData;
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const useAuthStateCore = (context: Context<AuthContextData | null>) => {
|
|
171
|
-
const { state } = useAuthContextCore(context);
|
|
172
|
-
return state;
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
const useSigninCallbackCore = (
|
|
176
|
-
context: Context<AuthContextData | null>,
|
|
177
|
-
onDone?: (user?: User) => void
|
|
178
|
-
) => {
|
|
179
|
-
const { state, handleSigninCallback } = useAuthContextCore(context);
|
|
180
|
-
|
|
181
|
-
const isInitialized = useRef(false);
|
|
182
|
-
useEffect(() => {
|
|
183
|
-
// Only run once
|
|
184
|
-
if (isInitialized.current) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
isInitialized.current = true;
|
|
188
|
-
handleSigninCallback()
|
|
189
|
-
// Wait for state to update
|
|
190
|
-
// Otherwise any navigation that happens in onDone will
|
|
191
|
-
// happen before the state update and the user will be
|
|
192
|
-
// redirected to the signin page again.
|
|
193
|
-
.then(
|
|
194
|
-
(u) =>
|
|
195
|
-
new Promise<User | undefined>((resolve) =>
|
|
196
|
-
setTimeout(() => resolve(u), 0)
|
|
197
|
-
)
|
|
198
|
-
)
|
|
199
|
-
.then((u) => onDone?.(u));
|
|
200
|
-
}, [handleSigninCallback]);
|
|
201
|
-
|
|
202
|
-
return state;
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
export const createAuthContext = (userManager: UserManager) => {
|
|
206
|
-
const AuthContext = createContext<AuthContextData | null>(null);
|
|
207
|
-
|
|
208
|
-
const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|
209
|
-
return (
|
|
210
|
-
<AuthProviderCore userManager={userManager} context={AuthContext}>
|
|
211
|
-
{children}
|
|
212
|
-
</AuthProviderCore>
|
|
213
|
-
);
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const useAuthContext = () => useAuthContextCore(AuthContext);
|
|
217
|
-
const useAuthState = () => useAuthStateCore(AuthContext);
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Hook to handle the signin callback. Use this on the signin callback page.
|
|
221
|
-
* @param onDone Optional callback to run after signin is complete. Useful to redirect to a different page.
|
|
222
|
-
*/
|
|
223
|
-
const useSigninCallback = (onDone?: (user?: User) => void) =>
|
|
224
|
-
useSigninCallbackCore(AuthContext, onDone);
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Async function to get the access token for the current user
|
|
228
|
-
* outside of a React component.
|
|
229
|
-
*
|
|
230
|
-
* Useful for prefetching data in React Router or similar.
|
|
231
|
-
*/
|
|
232
|
-
const getAccessToken = async () => {
|
|
233
|
-
const user = await userManager.getUser();
|
|
234
|
-
if (!user) {
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
return user.access_token;
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
AuthContext,
|
|
242
|
-
AuthProvider,
|
|
243
|
-
useAuthContext,
|
|
244
|
-
useAuthState,
|
|
245
|
-
useSigninCallback,
|
|
246
|
-
getAccessToken,
|
|
247
|
-
};
|
|
248
|
-
};
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { useRef, type ReactNode, useMemo, type ReactElement } from "react";
|
|
2
|
-
import { Box, Spinner, styled } from "../ui/ui";
|
|
3
|
-
import { type InitialConfigType, LexicalComposer } from "@lexical/react/LexicalComposer";
|
|
4
|
-
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
|
|
5
|
-
import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
|
|
6
|
-
import { lexicalTheme } from "./theme";
|
|
7
|
-
import { useHasFocusWithin, richTextContext } from "./editorContext";
|
|
8
|
-
|
|
9
|
-
const Container = styled("div", {
|
|
10
|
-
border: "1px solid $borderDarker",
|
|
11
|
-
borderRadius: "$md",
|
|
12
|
-
position: "relative",
|
|
13
|
-
backgroundColor: "white",
|
|
14
|
-
|
|
15
|
-
"&:hover": {
|
|
16
|
-
borderColor: "$gray200",
|
|
17
|
-
},
|
|
18
|
-
|
|
19
|
-
variants: {
|
|
20
|
-
hasFocus: {
|
|
21
|
-
true: {
|
|
22
|
-
boxShadow: "$md",
|
|
23
|
-
borderColor: "$gray200",
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
hideBorder: {
|
|
27
|
-
true: {
|
|
28
|
-
border: "none",
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
compoundVariants: [
|
|
34
|
-
{
|
|
35
|
-
hasFocus: true,
|
|
36
|
-
hideBorder: true,
|
|
37
|
-
css: {
|
|
38
|
-
boxShadow: "none",
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const EditorSpinner = ({
|
|
45
|
-
isLoading,
|
|
46
|
-
}: {
|
|
47
|
-
isLoading: boolean;
|
|
48
|
-
}) => {
|
|
49
|
-
return (
|
|
50
|
-
<Box
|
|
51
|
-
css={{
|
|
52
|
-
visibility: isLoading ? "visible" : "hidden",
|
|
53
|
-
position: "absolute",
|
|
54
|
-
bottom: "1rem",
|
|
55
|
-
right: "1rem",
|
|
56
|
-
display: "flex",
|
|
57
|
-
}}
|
|
58
|
-
>
|
|
59
|
-
<Spinner />
|
|
60
|
-
</Box>
|
|
61
|
-
);
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export const RichTextEditor = ({
|
|
65
|
-
isLoading,
|
|
66
|
-
children,
|
|
67
|
-
placeholderText,
|
|
68
|
-
nodes,
|
|
69
|
-
plugins,
|
|
70
|
-
toolbar,
|
|
71
|
-
content,
|
|
72
|
-
hideBorder,
|
|
73
|
-
onBlur,
|
|
74
|
-
}: {
|
|
75
|
-
isLoading: boolean;
|
|
76
|
-
children: ReactNode;
|
|
77
|
-
placeholderText?: string;
|
|
78
|
-
nodes: InitialConfigType["nodes"];
|
|
79
|
-
plugins?: ReactNode;
|
|
80
|
-
toolbar?: ReactNode;
|
|
81
|
-
content: ReactElement;
|
|
82
|
-
hideBorder?: boolean;
|
|
83
|
-
onBlur?: () => void;
|
|
84
|
-
}) => {
|
|
85
|
-
const { hasFocus, attributes } = useHasFocusWithin({ onBlur });
|
|
86
|
-
const editorRef = useRef<HTMLDivElement>(null);
|
|
87
|
-
const contextValue = useMemo(() => ({ hasFocus, editorRef }), [hasFocus]);
|
|
88
|
-
|
|
89
|
-
return (
|
|
90
|
-
<richTextContext.Provider value={contextValue}>
|
|
91
|
-
<LexicalComposer
|
|
92
|
-
initialConfig={{
|
|
93
|
-
namespace: "CommentEditor",
|
|
94
|
-
onError: (error) => {
|
|
95
|
-
console.error(error);
|
|
96
|
-
},
|
|
97
|
-
theme: lexicalTheme,
|
|
98
|
-
nodes,
|
|
99
|
-
editable: true,
|
|
100
|
-
}}
|
|
101
|
-
>
|
|
102
|
-
<Container
|
|
103
|
-
ref={editorRef}
|
|
104
|
-
hasFocus={hasFocus}
|
|
105
|
-
hideBorder={hideBorder}
|
|
106
|
-
{...attributes}
|
|
107
|
-
>
|
|
108
|
-
<RichTextPlugin
|
|
109
|
-
contentEditable={content}
|
|
110
|
-
placeholder={
|
|
111
|
-
placeholderText ? (
|
|
112
|
-
<Box
|
|
113
|
-
css={{
|
|
114
|
-
position: "absolute",
|
|
115
|
-
top: "$4",
|
|
116
|
-
left: "$4",
|
|
117
|
-
color: "$gray500",
|
|
118
|
-
pointerEvents: "none",
|
|
119
|
-
fontSize: "$sm",
|
|
120
|
-
}}
|
|
121
|
-
>
|
|
122
|
-
{placeholderText}
|
|
123
|
-
</Box>
|
|
124
|
-
) : null
|
|
125
|
-
}
|
|
126
|
-
ErrorBoundary={LexicalErrorBoundary}
|
|
127
|
-
/>
|
|
128
|
-
{plugins}
|
|
129
|
-
<EditorSpinner isLoading={isLoading} />
|
|
130
|
-
{toolbar}
|
|
131
|
-
</Container>
|
|
132
|
-
{/* biome-ignore lint/complexity/noUselessFragments: <explanation> */}
|
|
133
|
-
<>{children}</>
|
|
134
|
-
</LexicalComposer>
|
|
135
|
-
</richTextContext.Provider>
|
|
136
|
-
);
|
|
137
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { AutoLinkNode, LinkNode } from "@lexical/link";
|
|
2
|
-
import { ListItemNode, ListNode } from "@lexical/list";
|
|
3
|
-
import { HeadingNode, QuoteNode } from "@lexical/rich-text";
|
|
4
|
-
import { TableCellNode, TableNode, TableRowNode } from "@lexical/table";
|
|
5
|
-
|
|
6
|
-
export const defaultNodes = [
|
|
7
|
-
HeadingNode,
|
|
8
|
-
QuoteNode,
|
|
9
|
-
ListNode,
|
|
10
|
-
ListItemNode,
|
|
11
|
-
AutoLinkNode,
|
|
12
|
-
LinkNode,
|
|
13
|
-
TableNode,
|
|
14
|
-
TableRowNode,
|
|
15
|
-
TableCellNode,
|
|
16
|
-
];
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { type RefObject, createContext, useCallback, useContext, useRef, useState } from "react";
|
|
2
|
-
|
|
3
|
-
export const richTextContext = createContext({ hasFocus: false, editorRef: { current: null } } as { hasFocus: boolean, editorRef: RefObject<HTMLElement | null> });
|
|
4
|
-
|
|
5
|
-
export const useHasFocusWithin = ({ onBlur }: { onBlur?: () => void }) => {
|
|
6
|
-
const [hasFocus, setHasFocus] = useState(false);
|
|
7
|
-
const blurTimeout = useRef<number | null>(null);
|
|
8
|
-
|
|
9
|
-
const handleFocus = useCallback(() => {
|
|
10
|
-
setHasFocus(true);
|
|
11
|
-
if (blurTimeout.current) {
|
|
12
|
-
window.clearTimeout(blurTimeout.current);
|
|
13
|
-
}
|
|
14
|
-
}, []);
|
|
15
|
-
|
|
16
|
-
const handleBlur = useCallback(() => {
|
|
17
|
-
blurTimeout.current = window.setTimeout(() => {
|
|
18
|
-
setHasFocus(false);
|
|
19
|
-
onBlur?.();
|
|
20
|
-
}, 0);
|
|
21
|
-
}, [onBlur]);
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
hasFocus,
|
|
25
|
-
attributes: {
|
|
26
|
-
onFocus: handleFocus,
|
|
27
|
-
onBlur: handleBlur,
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export const useRichTextContext = () => {
|
|
33
|
-
return useContext(richTextContext);
|
|
34
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { createHeadlessEditor } from "@lexical/headless";
|
|
2
|
-
import { $generateHtmlFromNodes } from "@lexical/html";
|
|
3
|
-
import { $createParagraphNode, $createTextNode, $getRoot, type CreateEditorArgs, type SerializedEditorState } from "lexical";
|
|
4
|
-
import { trimState } from "./trim";
|
|
5
|
-
|
|
6
|
-
export const isJSON = (str: string) => {
|
|
7
|
-
return str[0] === "{";
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export const stateToHTML = (
|
|
11
|
-
state: SerializedEditorState | string | null | undefined,
|
|
12
|
-
nodes: CreateEditorArgs["nodes"],
|
|
13
|
-
options?: {
|
|
14
|
-
maxLines?: number | null;
|
|
15
|
-
onlyMainContent?: boolean;
|
|
16
|
-
},
|
|
17
|
-
): {
|
|
18
|
-
html: string;
|
|
19
|
-
trimCount: number;
|
|
20
|
-
} => {
|
|
21
|
-
let trimCount = 0;
|
|
22
|
-
|
|
23
|
-
const headlessEditor = createHeadlessEditor({
|
|
24
|
-
nodes,
|
|
25
|
-
editable: false,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
if (state) {
|
|
29
|
-
try {
|
|
30
|
-
if (typeof state === "string" && !isJSON(state)) {
|
|
31
|
-
// We have a string, but it's not JSON. We will assume it is plain text
|
|
32
|
-
// and create a paragraph node with the text.
|
|
33
|
-
headlessEditor.update(() => {
|
|
34
|
-
const root = $getRoot();
|
|
35
|
-
const p = $createParagraphNode();
|
|
36
|
-
p.append($createTextNode(state.trim()));
|
|
37
|
-
root.append(p);
|
|
38
|
-
});
|
|
39
|
-
} else {
|
|
40
|
-
const parsed = headlessEditor.parseEditorState(state, () => {
|
|
41
|
-
trimCount = trimState({
|
|
42
|
-
editor: headlessEditor,
|
|
43
|
-
maxLines: options?.maxLines,
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
if (!parsed.isEmpty()) {
|
|
47
|
-
headlessEditor.setEditorState(parsed);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
} catch (error) {
|
|
51
|
-
console.error(error);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let html = "";
|
|
56
|
-
|
|
57
|
-
headlessEditor.update(() => {
|
|
58
|
-
html = $generateHtmlFromNodes(headlessEditor);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
return { html, trimCount };
|
|
62
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { EditorThemeClasses } from "lexical";
|
|
2
|
-
import { css } from "../ui/ui";
|
|
3
|
-
|
|
4
|
-
const boldCss = css({
|
|
5
|
-
fontWeight: "bold",
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
const italicCss = css({
|
|
9
|
-
fontStyle: "italic",
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const underlineCss = css({
|
|
13
|
-
textDecoration: "underline",
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const nestedListItem = css({
|
|
17
|
-
listStyleType: "none",
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
// TODO: Improve
|
|
21
|
-
const quoteCss = css({
|
|
22
|
-
borderLeft: "4px solid #ccc",
|
|
23
|
-
color: "#666",
|
|
24
|
-
fontStyle: "italic",
|
|
25
|
-
margin: "1.5em 10px",
|
|
26
|
-
padding: "0.5em 10px",
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
export const lexicalTheme: EditorThemeClasses = {
|
|
30
|
-
quote: quoteCss().className,
|
|
31
|
-
text: {
|
|
32
|
-
bold: boldCss().className,
|
|
33
|
-
italic: italicCss().className,
|
|
34
|
-
underline: underlineCss().className,
|
|
35
|
-
},
|
|
36
|
-
list: {
|
|
37
|
-
nested: {
|
|
38
|
-
listitem: nestedListItem().className,
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
};
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { $getRoot, type LexicalEditor, ParagraphNode } from "lexical";
|
|
2
|
-
import { trimTextContentFromAnchor } from "@lexical/selection";
|
|
3
|
-
|
|
4
|
-
const findTrimPoint = ({
|
|
5
|
-
text,
|
|
6
|
-
maxChars,
|
|
7
|
-
maxLines,
|
|
8
|
-
}: {
|
|
9
|
-
text: string;
|
|
10
|
-
maxChars?: number | null;
|
|
11
|
-
maxLines?: number | null;
|
|
12
|
-
}) => {
|
|
13
|
-
if (text.length === 0) {
|
|
14
|
-
return 0;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const hasMaxLines = typeof maxLines === "number";
|
|
18
|
-
const hasMaxChars = typeof maxChars === "number";
|
|
19
|
-
|
|
20
|
-
if (!hasMaxLines && !hasMaxChars) {
|
|
21
|
-
return text.length;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
let lineIndex = 0;
|
|
25
|
-
let charIndex = 0;
|
|
26
|
-
while (
|
|
27
|
-
charIndex < text.length &&
|
|
28
|
-
(!hasMaxLines || lineIndex < maxLines) &&
|
|
29
|
-
(!hasMaxChars || charIndex < maxChars)
|
|
30
|
-
) {
|
|
31
|
-
const char = text[charIndex];
|
|
32
|
-
|
|
33
|
-
if (char === "\n") {
|
|
34
|
-
lineIndex++;
|
|
35
|
-
}
|
|
36
|
-
charIndex++;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const newText = text.slice(0, charIndex);
|
|
40
|
-
|
|
41
|
-
return newText.length;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export const trimState = ({
|
|
45
|
-
editor,
|
|
46
|
-
maxChars,
|
|
47
|
-
maxLines,
|
|
48
|
-
}: {
|
|
49
|
-
editor: LexicalEditor;
|
|
50
|
-
maxChars?: number | null;
|
|
51
|
-
maxLines?: number | null;
|
|
52
|
-
}) => {
|
|
53
|
-
const rootNode = $getRoot();
|
|
54
|
-
const text = rootNode.getTextContent();
|
|
55
|
-
|
|
56
|
-
const i = findTrimPoint({ text, maxChars, maxLines });
|
|
57
|
-
const trimCount = text.length - i;
|
|
58
|
-
|
|
59
|
-
const selection = rootNode.select();
|
|
60
|
-
const anchor = selection.anchor;
|
|
61
|
-
trimTextContentFromAnchor(editor, anchor, trimCount);
|
|
62
|
-
|
|
63
|
-
// Remove the last paragraph if it's empty.
|
|
64
|
-
const lastChild = rootNode.getLastChild();
|
|
65
|
-
if (lastChild instanceof ParagraphNode && lastChild.getChildrenSize() === 0) {
|
|
66
|
-
lastChild.remove();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// if (trimCount > 0) {
|
|
70
|
-
// // Append an ellipsis to the last text node is we can find it.
|
|
71
|
-
// const lastNonRootNode = rootNode.getLastDescendant();
|
|
72
|
-
|
|
73
|
-
// if (lastNonRootNode instanceof TextNode) {
|
|
74
|
-
// lastNonRootNode.getParent()?.append($createTextNode("... Les mer"));
|
|
75
|
-
// }
|
|
76
|
-
// }
|
|
77
|
-
|
|
78
|
-
return trimCount;
|
|
79
|
-
};
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { styled } from "../stitches.config";
|
|
2
|
-
import { forwardRef } from "react";
|
|
3
|
-
|
|
4
|
-
const AlertStyled = styled("div", {
|
|
5
|
-
padding: "$3",
|
|
6
|
-
borderRadius: "$md",
|
|
7
|
-
|
|
8
|
-
variants: {
|
|
9
|
-
color: {
|
|
10
|
-
warning: {
|
|
11
|
-
background: "$yellow200",
|
|
12
|
-
border: "1px solid $yellow400",
|
|
13
|
-
color: "$yellow900",
|
|
14
|
-
},
|
|
15
|
-
danger: {
|
|
16
|
-
background: "$red200",
|
|
17
|
-
border: "1px solid $red400",
|
|
18
|
-
color: "$red900",
|
|
19
|
-
},
|
|
20
|
-
info: {
|
|
21
|
-
background: "$blue200",
|
|
22
|
-
border: "1px solid $blue400",
|
|
23
|
-
color: "$blue900",
|
|
24
|
-
},
|
|
25
|
-
success: {
|
|
26
|
-
background: "$green200",
|
|
27
|
-
border: "1px solid $green400",
|
|
28
|
-
color: "$green900",
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
size: {
|
|
32
|
-
sm: {
|
|
33
|
-
fontSize: "$sm",
|
|
34
|
-
},
|
|
35
|
-
md: {
|
|
36
|
-
fontSize: "$md",
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
defaultVariants: {
|
|
42
|
-
color: "info",
|
|
43
|
-
size: "md",
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
type AlertProps = React.ComponentProps<typeof AlertStyled> & {
|
|
48
|
-
as?: React.ElementType;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export const Alert = forwardRef<HTMLDivElement, AlertProps>(
|
|
52
|
-
({ children, ...props }, ref) => {
|
|
53
|
-
return (
|
|
54
|
-
<AlertStyled
|
|
55
|
-
ref={ref}
|
|
56
|
-
role="alert"
|
|
57
|
-
{...props}
|
|
58
|
-
>
|
|
59
|
-
{children}
|
|
60
|
-
</AlertStyled>
|
|
61
|
-
);
|
|
62
|
-
},
|
|
63
|
-
);
|