authscape 1.0.744 → 1.0.750

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "authscape",
3
- "version": "1.0.744",
3
+ "version": "1.0.750",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -1,47 +1,278 @@
1
- import React, { useEffect, useCallback } from "react";
2
- import dynamic from 'next/dynamic';
3
- import Box from "@mui/material/Box";
4
- import Button from "@mui/material/Button";
1
+ import React, { useEffect, useCallback, useState } from 'react';
2
+ import { LexicalComposer } from '@lexical/react/LexicalComposer';
3
+ import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
4
+ import { ContentEditable } from '@lexical/react/LexicalContentEditable';
5
+ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
6
+ import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
7
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
8
+ import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
9
+ import { ListPlugin } from '@lexical/react/LexicalListPlugin';
10
+ import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin';
11
+ import { $generateHtmlFromNodes, $generateNodesFromDOM } from '@lexical/html';
12
+ import { $getRoot, $getSelection, $isRangeSelection, FORMAT_TEXT_COMMAND, $createParagraphNode } from 'lexical';
13
+ import { $setBlocksType } from '@lexical/selection';
14
+ import { $createHeadingNode, $createQuoteNode, HeadingNode, QuoteNode } from '@lexical/rich-text';
15
+ import { ListItemNode, ListNode, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND } from '@lexical/list';
16
+ import { LinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
17
+ import { CodeNode } from '@lexical/code';
18
+ import Box from '@mui/material/Box';
19
+ import Button from '@mui/material/Button';
20
+ import IconButton from '@mui/material/IconButton';
21
+ import Divider from '@mui/material/Divider';
22
+ import Select from '@mui/material/Select';
23
+ import MenuItem from '@mui/material/MenuItem';
24
+ import Tooltip from '@mui/material/Tooltip';
25
+ import FormatBoldIcon from '@mui/icons-material/FormatBold';
26
+ import FormatItalicIcon from '@mui/icons-material/FormatItalic';
27
+ import FormatUnderlinedIcon from '@mui/icons-material/FormatUnderlined';
28
+ import StrikethroughSIcon from '@mui/icons-material/StrikethroughS';
29
+ import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted';
30
+ import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
31
+ import CodeIcon from '@mui/icons-material/Code';
32
+ import LinkIcon from '@mui/icons-material/Link';
33
+ import LinkOffIcon from '@mui/icons-material/LinkOff';
34
+ import UndoIcon from '@mui/icons-material/Undo';
35
+ import RedoIcon from '@mui/icons-material/Redo';
5
36
 
6
- // Dynamically import the editor to avoid SSR issues
7
- const LexicalEditor = dynamic(
8
- () => import('./LexicalEditor').then(mod => mod.LexicalEditor),
9
- { ssr: false }
10
- );
37
+ const theme = {
38
+ paragraph: 'lexical-paragraph',
39
+ quote: 'lexical-quote',
40
+ heading: {
41
+ h1: 'lexical-h1',
42
+ h2: 'lexical-h2',
43
+ h3: 'lexical-h3',
44
+ h4: 'lexical-h4',
45
+ h5: 'lexical-h5',
46
+ h6: 'lexical-h6',
47
+ },
48
+ list: {
49
+ nested: { listitem: 'lexical-nested-listitem' },
50
+ ol: 'lexical-ol',
51
+ ul: 'lexical-ul',
52
+ listitem: 'lexical-listitem',
53
+ },
54
+ text: {
55
+ bold: 'lexical-bold',
56
+ italic: 'lexical-italic',
57
+ underline: 'lexical-underline',
58
+ strikethrough: 'lexical-strikethrough',
59
+ code: 'lexical-code',
60
+ },
61
+ link: 'lexical-link',
62
+ code: 'lexical-code-block',
63
+ };
11
64
 
12
- export const RichTextEditor = ({ html, onSave, height = 400, isDisabled = false }) => {
13
- const [editorHtml, setEditorHtml] = React.useState(html || '');
65
+ const editorStyles = `
66
+ .lexical-editor-container { border: 1px solid #ccc; border-radius: 4px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; }
67
+ .lexical-editor-inner { position: relative; }
68
+ .lexical-editor-input { padding: 12px; outline: none; overflow-y: auto; }
69
+ .lexical-editor-input:focus { outline: none; }
70
+ .lexical-placeholder { position: absolute; top: 12px; left: 12px; color: #999; pointer-events: none; user-select: none; }
71
+ .lexical-paragraph { margin: 0 0 8px 0; }
72
+ .lexical-h1 { font-size: 2em; font-weight: bold; margin: 16px 0 8px 0; }
73
+ .lexical-h2 { font-size: 1.5em; font-weight: bold; margin: 14px 0 8px 0; }
74
+ .lexical-h3 { font-size: 1.17em; font-weight: bold; margin: 12px 0 8px 0; }
75
+ .lexical-h4 { font-size: 1em; font-weight: bold; margin: 10px 0 8px 0; }
76
+ .lexical-h5 { font-size: 0.83em; font-weight: bold; margin: 8px 0 8px 0; }
77
+ .lexical-h6 { font-size: 0.67em; font-weight: bold; margin: 6px 0 8px 0; }
78
+ .lexical-quote { margin: 8px 0; padding: 8px 16px; border-left: 4px solid #ccc; background: #f9f9f9; font-style: italic; }
79
+ .lexical-ul, .lexical-ol { margin: 8px 0; padding-left: 24px; }
80
+ .lexical-listitem { margin: 4px 0; }
81
+ .lexical-bold { font-weight: bold; }
82
+ .lexical-italic { font-style: italic; }
83
+ .lexical-underline { text-decoration: underline; }
84
+ .lexical-strikethrough { text-decoration: line-through; }
85
+ .lexical-code { background: #f0f0f0; padding: 2px 4px; border-radius: 3px; font-family: monospace; }
86
+ .lexical-link { color: #0066cc; text-decoration: underline; }
87
+ .lexical-code-block { background: #f5f5f5; padding: 12px; border-radius: 4px; font-family: monospace; overflow-x: auto; }
88
+ `;
89
+
90
+ const toolbarButtonStyle = {
91
+ minWidth: 32, width: 32, height: 32, padding: '4px', margin: '2px', borderRadius: '2px',
92
+ border: '1px solid transparent',
93
+ '&:hover': { backgroundColor: '#f0f0f0', border: '1px solid #ccc' },
94
+ };
95
+
96
+ const activeButtonStyle = {
97
+ ...toolbarButtonStyle,
98
+ backgroundColor: '#e0e0e0',
99
+ border: '1px solid #ccc',
100
+ };
101
+
102
+ function HtmlImportPlugin({ initialHtml }) {
103
+ const [editor] = useLexicalComposerContext();
104
+ const [initialized, setInitialized] = useState(false);
14
105
 
15
106
  useEffect(() => {
16
- if (html != null) {
17
- setEditorHtml(html);
107
+ if (initialHtml && !initialized) {
108
+ editor.update(() => {
109
+ const parser = new DOMParser();
110
+ const dom = parser.parseFromString(initialHtml, 'text/html');
111
+ const nodes = $generateNodesFromDOM(editor, dom);
112
+ const root = $getRoot();
113
+ root.clear();
114
+ nodes.forEach(node => root.append(node));
115
+ });
116
+ setInitialized(true);
18
117
  }
19
- }, [html]);
118
+ }, [editor, initialHtml, initialized]);
119
+
120
+ return null;
121
+ }
20
122
 
21
- const handleChange = useCallback((newHtml) => {
22
- setEditorHtml(newHtml);
123
+ function HtmlExportPlugin({ onChange }) {
124
+ const [editor] = useLexicalComposerContext();
125
+
126
+ const handleChange = useCallback(() => {
127
+ editor.update(() => {
128
+ const html = $generateHtmlFromNodes(editor, null);
129
+ onChange(html);
130
+ });
131
+ }, [editor, onChange]);
132
+
133
+ return <OnChangePlugin onChange={handleChange} />;
134
+ }
135
+
136
+ function Toolbar({ isDisabled }) {
137
+ const [editor] = useLexicalComposerContext();
138
+ const [isBold, setIsBold] = useState(false);
139
+ const [isItalic, setIsItalic] = useState(false);
140
+ const [isUnderline, setIsUnderline] = useState(false);
141
+ const [isStrikethrough, setIsStrikethrough] = useState(false);
142
+ const [isCode, setIsCode] = useState(false);
143
+ const [blockType, setBlockType] = useState('paragraph');
144
+
145
+ const updateToolbar = useCallback(() => {
146
+ const selection = $getSelection();
147
+ if ($isRangeSelection(selection)) {
148
+ setIsBold(selection.hasFormat('bold'));
149
+ setIsItalic(selection.hasFormat('italic'));
150
+ setIsUnderline(selection.hasFormat('underline'));
151
+ setIsStrikethrough(selection.hasFormat('strikethrough'));
152
+ setIsCode(selection.hasFormat('code'));
153
+ }
23
154
  }, []);
24
155
 
156
+ useEffect(() => {
157
+ return editor.registerUpdateListener(({ editorState }) => {
158
+ editorState.read(() => { updateToolbar(); });
159
+ });
160
+ }, [editor, updateToolbar]);
161
+
162
+ const formatText = (format) => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, format); };
163
+
164
+ const formatBlock = (type) => {
165
+ editor.update(() => {
166
+ const selection = $getSelection();
167
+ if ($isRangeSelection(selection)) {
168
+ if (type === 'paragraph') $setBlocksType(selection, () => $createParagraphNode());
169
+ else if (type === 'quote') $setBlocksType(selection, () => $createQuoteNode());
170
+ else if (type.startsWith('h')) $setBlocksType(selection, () => $createHeadingNode(type));
171
+ setBlockType(type);
172
+ }
173
+ });
174
+ };
175
+
176
+ const formatList = (listType) => {
177
+ if (listType === 'bullet') editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
178
+ else if (listType === 'number') editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
179
+ };
180
+
181
+ const insertLink = () => {
182
+ const url = prompt('Enter URL:');
183
+ if (url) editor.dispatchCommand(TOGGLE_LINK_COMMAND, url);
184
+ };
185
+
186
+ const removeLink = () => { editor.dispatchCommand(TOGGLE_LINK_COMMAND, null); };
187
+ const undo = () => { editor.dispatchCommand('UNDO', undefined); };
188
+ const redo = () => { editor.dispatchCommand('REDO', undefined); };
189
+
190
+ if (isDisabled) return null;
191
+
192
+ return (
193
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', padding: '8px', borderBottom: '1px solid #ccc', backgroundColor: '#f9f9f9', gap: '4px' }}>
194
+ <Select size="small" value={blockType} onChange={(e) => formatBlock(e.target.value)} sx={{ minWidth: 120, height: 32, mr: 1 }}>
195
+ <MenuItem value="paragraph">Normal</MenuItem>
196
+ <MenuItem value="h1">Heading 1</MenuItem>
197
+ <MenuItem value="h2">Heading 2</MenuItem>
198
+ <MenuItem value="h3">Heading 3</MenuItem>
199
+ <MenuItem value="h4">Heading 4</MenuItem>
200
+ <MenuItem value="h5">Heading 5</MenuItem>
201
+ <MenuItem value="h6">Heading 6</MenuItem>
202
+ <MenuItem value="quote">Blockquote</MenuItem>
203
+ </Select>
204
+ <Divider orientation="vertical" flexItem sx={{ mx: 1 }} />
205
+ <Tooltip title="Bold (Ctrl+B)"><IconButton size="small" onClick={() => formatText('bold')} sx={isBold ? activeButtonStyle : toolbarButtonStyle}><FormatBoldIcon fontSize="small" /></IconButton></Tooltip>
206
+ <Tooltip title="Italic (Ctrl+I)"><IconButton size="small" onClick={() => formatText('italic')} sx={isItalic ? activeButtonStyle : toolbarButtonStyle}><FormatItalicIcon fontSize="small" /></IconButton></Tooltip>
207
+ <Tooltip title="Underline (Ctrl+U)"><IconButton size="small" onClick={() => formatText('underline')} sx={isUnderline ? activeButtonStyle : toolbarButtonStyle}><FormatUnderlinedIcon fontSize="small" /></IconButton></Tooltip>
208
+ <Tooltip title="Strikethrough"><IconButton size="small" onClick={() => formatText('strikethrough')} sx={isStrikethrough ? activeButtonStyle : toolbarButtonStyle}><StrikethroughSIcon fontSize="small" /></IconButton></Tooltip>
209
+ <Tooltip title="Code"><IconButton size="small" onClick={() => formatText('code')} sx={isCode ? activeButtonStyle : toolbarButtonStyle}><CodeIcon fontSize="small" /></IconButton></Tooltip>
210
+ <Divider orientation="vertical" flexItem sx={{ mx: 1 }} />
211
+ <Tooltip title="Bullet List"><IconButton size="small" onClick={() => formatList('bullet')} sx={toolbarButtonStyle}><FormatListBulletedIcon fontSize="small" /></IconButton></Tooltip>
212
+ <Tooltip title="Numbered List"><IconButton size="small" onClick={() => formatList('number')} sx={toolbarButtonStyle}><FormatListNumberedIcon fontSize="small" /></IconButton></Tooltip>
213
+ <Divider orientation="vertical" flexItem sx={{ mx: 1 }} />
214
+ <Tooltip title="Insert Link"><IconButton size="small" onClick={insertLink} sx={toolbarButtonStyle}><LinkIcon fontSize="small" /></IconButton></Tooltip>
215
+ <Tooltip title="Remove Link"><IconButton size="small" onClick={removeLink} sx={toolbarButtonStyle}><LinkOffIcon fontSize="small" /></IconButton></Tooltip>
216
+ <Divider orientation="vertical" flexItem sx={{ mx: 1 }} />
217
+ <Tooltip title="Undo (Ctrl+Z)"><IconButton size="small" onClick={undo} sx={toolbarButtonStyle}><UndoIcon fontSize="small" /></IconButton></Tooltip>
218
+ <Tooltip title="Redo (Ctrl+Y)"><IconButton size="small" onClick={redo} sx={toolbarButtonStyle}><RedoIcon fontSize="small" /></IconButton></Tooltip>
219
+ </Box>
220
+ );
221
+ }
222
+
223
+ function onError(error) { console.error('Lexical error:', error); }
224
+
225
+ export const RichTextEditor = ({ html, onSave, height = 400, isDisabled = false }) => {
226
+ const [editorHtml, setEditorHtml] = useState(html || '');
227
+ const [isMounted, setIsMounted] = useState(false);
228
+
229
+ useEffect(() => { setIsMounted(true); }, []);
230
+
231
+ useEffect(() => {
232
+ if (html != null) setEditorHtml(html);
233
+ }, [html]);
234
+
235
+ const handleChange = useCallback((newHtml) => { setEditorHtml(newHtml); }, []);
236
+
237
+ if (!isMounted) {
238
+ return <div style={{ minHeight: height, border: '1px solid #ccc', borderRadius: 4 }} />;
239
+ }
240
+
241
+ const initialConfig = {
242
+ namespace: 'RichTextEditor',
243
+ theme,
244
+ onError,
245
+ nodes: [HeadingNode, QuoteNode, ListNode, ListItemNode, LinkNode, CodeNode],
246
+ editable: !isDisabled,
247
+ };
248
+
25
249
  return (
26
250
  <>
27
- <LexicalEditor
28
- initialHtml={html}
29
- onChange={handleChange}
30
- height={height}
31
- isDisabled={isDisabled}
32
- />
251
+ <style>{editorStyles}</style>
252
+ <LexicalComposer initialConfig={initialConfig}>
253
+ <div className="lexical-editor-container">
254
+ <Toolbar isDisabled={isDisabled} />
255
+ <div className="lexical-editor-inner">
256
+ <RichTextPlugin
257
+ contentEditable={<ContentEditable className="lexical-editor-input" style={{ minHeight: height, maxHeight: height }} />}
258
+ placeholder={<div className="lexical-placeholder">Enter text...</div>}
259
+ ErrorBoundary={LexicalErrorBoundary}
260
+ />
261
+ <HistoryPlugin />
262
+ <ListPlugin />
263
+ <LinkPlugin />
264
+ <HtmlImportPlugin initialHtml={html} />
265
+ <HtmlExportPlugin onChange={handleChange} />
266
+ </div>
267
+ </div>
268
+ </LexicalComposer>
33
269
  <hr />
34
270
  <Box sx={{ textAlign: "right" }}>
35
- <Button
36
- variant="contained"
37
- disabled={isDisabled}
38
- onClick={async () => {
39
- await onSave(editorHtml);
40
- }}
41
- >
42
- Save
43
- </Button>
271
+ <Button variant="contained" disabled={isDisabled} onClick={async () => { await onSave(editorHtml); }}>Save</Button>
44
272
  </Box>
45
273
  </>
46
274
  );
47
275
  };
276
+
277
+ // Also export as LexicalEditor for backwards compatibility
278
+ export const LexicalEditor = RichTextEditor;