dishui 0.0.105 β 0.0.108
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/.release-verified.json +2 -2
- package/dist/components/ChoicePicker/index.js +8 -6
- package/dist/components/ChoicePicker/index.js.map +1 -1
- package/dist/components/Column/index.d.ts +3 -2
- package/dist/components/Column/index.js +5 -17
- package/dist/components/Column/index.js.map +1 -1
- package/dist/components/ConfirmDialog/index.d.ts +20 -0
- package/dist/components/ConfirmDialog/index.js +84 -0
- package/dist/components/ConfirmDialog/index.js.map +1 -0
- package/dist/components/DateTimeInput/index.js +28 -26
- package/dist/components/DateTimeInput/index.js.map +1 -1
- package/dist/components/Input/index.js +1 -1
- package/dist/components/Input/index.js.map +1 -1
- package/dist/components/MultipleChoice/index.js +2 -1
- package/dist/components/MultipleChoice/index.js.map +1 -1
- package/dist/components/Row/index.d.ts +3 -2
- package/dist/components/Row/index.js +5 -17
- package/dist/components/Row/index.js.map +1 -1
- package/dist/components/Tab/index.js +80 -0
- package/dist/components/Tab/index.js.map +1 -0
- package/dist/components/Tabs/index.js +28 -33
- package/dist/components/Tabs/index.js.map +1 -1
- package/dist/components/Tabs/tabNavigation.d.ts +2 -0
- package/dist/components/Tabs/tabNavigation.js +14 -0
- package/dist/components/Tabs/tabNavigation.js.map +1 -0
- package/dist/components/pro/CodeEditor/editors/MarkdownEditor.d.ts +1 -1
- package/dist/components/pro/CodeEditor/editors/MarkdownEditor.js +25 -26
- package/dist/components/pro/CodeEditor/editors/MarkdownEditor.js.map +1 -1
- package/dist/components/pro/FileExplorer/Breadcrumb.js +8 -11
- package/dist/components/pro/FileExplorer/Breadcrumb.js.map +1 -1
- package/dist/components/pro/Flow/Flow.js +184 -182
- package/dist/components/pro/Flow/Flow.js.map +1 -1
- package/dist/components/pro/Flow/archify/convert.js +26 -26
- package/dist/components/pro/Flow/archify/convert.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useLayoutActions.js +13 -11
- package/dist/components/pro/Flow/hooks/useLayoutActions.js.map +1 -1
- package/dist/components/pro/RichTextEditor/AIRewrite.d.ts +1 -0
- package/dist/components/pro/RichTextEditor/AIRewrite.js +109 -88
- package/dist/components/pro/RichTextEditor/AIRewrite.js.map +1 -1
- package/dist/components/pro/RichTextEditor/BubbleToolbar.js +73 -83
- package/dist/components/pro/RichTextEditor/BubbleToolbar.js.map +1 -1
- package/dist/components/pro/RichTextEditor/RichTextEditor.js +133 -132
- package/dist/components/pro/RichTextEditor/RichTextEditor.js.map +1 -1
- package/dist/components/pro/RichTextEditor/Toolbar/ColorPicker.d.ts +2 -0
- package/dist/components/pro/RichTextEditor/Toolbar/ColorPicker.js +73 -49
- package/dist/components/pro/RichTextEditor/Toolbar/ColorPicker.js.map +1 -1
- package/dist/components/pro/RichTextEditor/Toolbar/SelectDropdown.d.ts +1 -0
- package/dist/components/pro/RichTextEditor/Toolbar/SelectDropdown.js +12 -14
- package/dist/components/pro/RichTextEditor/Toolbar/SelectDropdown.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/CodeBlockView.js +113 -113
- package/dist/components/pro/RichTextEditor/extensions/CodeBlockView.js.map +1 -1
- package/dist/components/pro/RichTextEditor/types.d.ts +2 -0
- package/dist/components/pro/RichTextEditor/types.js.map +1 -1
- package/dist/css.d.ts +1 -0
- package/dist/dishui.css +1 -1
- package/dist/index-nostyled.d.ts +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +113 -111
- package/dist/layouts/Sidebar.js +201 -107
- package/dist/layouts/Sidebar.js.map +1 -1
- package/dist/utils/flexLayout.d.ts +5 -0
- package/dist/utils/flexLayout.js +19 -0
- package/dist/utils/flexLayout.js.map +1 -0
- package/package.json +63 -57
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIRewrite.js","names":[],"sources":["../../../../src/components/pro/RichTextEditor/AIRewrite.tsx"],"sourcesContent":["import React, { useState, useRef, useCallback, useEffect, useId } from 'react';\nimport type { Editor } from '@tiptap/react';\nimport type { AIConfig, AIAction, RichTextEditorLabels } from './types';\nimport { defaultLabels } from './types';\nimport StreamingText from '../../ai/StreamingText';\nimport LoadingState from '../../ai/LoadingState';\n\ninterface AIRewriteProps {\n editor: Editor;\n aiConfig: AIConfig;\n labels?: RichTextEditorLabels;\n initialSelection: string;\n selectionRange: { from: number; to: number };\n onClose: () => void;\n}\n\nconst QUICK_ACTIONS: { key: AIAction; labelKey: keyof RichTextEditorLabels; icon: string }[] = [\n { key: 'rewrite', labelKey: 'aiRewrite', icon: 'β' },\n { key: 'shorten', labelKey: 'aiShorten', icon: 'π' },\n { key: 'expand', labelKey: 'aiExpand', icon: 'π' },\n { key: 'fix_grammar', labelKey: 'aiFixGrammar', icon: 'β' },\n { key: 'translate_en', labelKey: 'aiTranslateEn', icon: 'EN' },\n { key: 'translate_zh', labelKey: 'aiTranslateZh', icon: 'δΈ' },\n { key: 'professional', labelKey: 'aiProfessional', icon: 'πΌ' },\n { key: 'casual', labelKey: 'aiCasual', icon: 'π¬' },\n];\n\nfunction getSystemPrompt(action: AIAction | 'custom', customPrompt?: string): string {\n if (action === 'custom') return customPrompt || 'Follow the user instructions.';\n const prompts: Record<AIAction, string> = {\n rewrite: 'Rewrite the following text to improve clarity and readability. Keep the same meaning and tone. Return only the rewritten text.',\n shorten: 'Shorten the following text while preserving its key meaning. Be concise. Return only the shortened text.',\n expand: 'Expand the following text with more details and examples while keeping the same tone. Return only the expanded text.',\n fix_grammar: 'Fix any grammar, spelling, and punctuation errors in the following text. Return only the corrected text.',\n translate_en: 'Translate the following text to English. Return only the translated text.',\n translate_zh: 'Translate the following text to Chinese. Return only the translated text.',\n professional: 'Rewrite the following text in a professional, formal tone. Return only the rewritten text.',\n casual: 'Rewrite the following text in a casual, friendly tone. Return only the rewritten text.',\n };\n return customPrompt || prompts[action];\n}\n\nasync function* readSSE(response: Response): AsyncGenerator<string> {\n const reader = response.body?.getReader();\n if (!reader) throw new Error('No response body');\n const decoder = new TextDecoder();\n let buffer = '';\n let data: string[] = [];\n try {\n while (true) {\n const { done, value } = await reader.read();\n buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });\n // Normalize CRLF only when the complete line is available (chunks may split it).\n let newline: number;\n while ((newline = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, newline).replace(/\\r$/, '');\n buffer = buffer.slice(newline + 1);\n if (!line) {\n if (data.length) yield data.join('\\n');\n data = [];\n } else if (line.startsWith('data:')) {\n data.push(line.slice(5).replace(/^ /, ''));\n }\n }\n if (done) {\n if (buffer.startsWith('data:')) data.push(buffer.slice(5).replace(/^ /, '').replace(/\\r$/, ''));\n if (data.length) yield data.join('\\n');\n return;\n }\n }\n } finally {\n try { await reader.cancel(); } catch { /* The request may already be aborted. */ }\n reader.releaseLock();\n }\n}\n\nasync function* streamOpenAI(\n config: AIConfig,\n text: string,\n systemPrompt: string,\n signal: AbortSignal,\n): AsyncGenerator<string> {\n if (!config.gateway || !config.model) throw new Error('OpenAI protocol requires gateway and model');\n const body = JSON.stringify({\n model: config.model,\n messages: [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: text },\n ],\n stream: true,\n });\n\n const response = await fetch(config.gateway, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n ...config.headers,\n },\n body,\n signal,\n });\n\n if (!response.ok) throw new Error(`AI API error: ${response.status} ${response.statusText}`);\n for await (const payload of readSSE(response)) {\n if (payload.trim() === '[DONE]') return;\n let data;\n try { data = JSON.parse(payload); } catch { continue; }\n if (data?.error) throw new Error(data.error.message || 'AI stream error');\n const content = data?.choices?.[0]?.delta?.content;\n if (typeof content === 'string') yield content;\n }\n}\n\nasync function* streamAnthropic(\n config: AIConfig,\n text: string,\n systemPrompt: string,\n signal: AbortSignal,\n): AsyncGenerator<string> {\n if (!config.gateway || !config.model) throw new Error('Anthropic protocol requires gateway and model');\n const body = JSON.stringify({\n model: config.model,\n max_tokens: 4096,\n system: systemPrompt,\n messages: [{ role: 'user', content: text }],\n stream: true,\n });\n\n const response = await fetch(config.gateway, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { 'x-api-key': config.apiKey } : {}),\n 'anthropic-version': '2023-06-01',\n ...config.headers,\n },\n body,\n signal,\n });\n\n if (!response.ok) throw new Error(`AI API error: ${response.status} ${response.statusText}`);\n for await (const payload of readSSE(response)) {\n let data;\n try { data = JSON.parse(payload); } catch { continue; }\n if (data?.type === 'error') throw new Error(data.error?.message || 'AI stream error');\n if (data?.type === 'message_stop') return;\n if (data?.type === 'content_block_delta' && typeof data.delta?.text === 'string') yield data.delta.text;\n }\n}\n\nfunction getStream(\n config: AIConfig,\n text: string,\n systemPrompt: string,\n action: AIAction | 'custom',\n signal: AbortSignal,\n request?: { prompt: string; selectedText: string; mode: 'replace' | 'insert' },\n): AsyncIterable<string> {\n if (config.protocol === 'custom') {\n if (!config.customHandler) throw new Error('Custom AI protocol requires customHandler');\n return config.customHandler({\n text,\n action: action === 'custom' ? 'rewrite' : action,\n systemPrompt,\n signal,\n prompt: request?.prompt ?? '',\n selectedText: request?.selectedText ?? text,\n mode: request?.mode ?? 'replace',\n });\n }\n if (config.protocol === 'anthropic') return streamAnthropic(config, text, systemPrompt, signal);\n return streamOpenAI(config, text, systemPrompt, signal);\n}\n\nconst AIRewrite: React.FC<AIRewriteProps> = ({ editor, aiConfig, labels: propsLabels, initialSelection, selectionRange, onClose }) => {\n const labels = { ...defaultLabels, ...propsLabels };\n const promptId = useId();\n const [prompt, setPrompt] = useState('');\n const [isProcessing, setIsProcessing] = useState(false);\n const [streamedText, setStreamedText] = useState('');\n const [error, setError] = useState<string | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const promptRef = useRef<HTMLTextAreaElement>(null);\n const [editable, setEditable] = useState(() => !editor.isDestroyed && editor.isEditable);\n const [invalid, setInvalid] = useState(false);\n const sourceDoc = useRef(editor.state.doc);\n const invalidRef = useRef(false);\n\n const cancelRequest = useCallback(() => {\n const request = abortRef.current;\n abortRef.current = null;\n request?.abort();\n }, []);\n\n useEffect(() => {\n sourceDoc.current = editor.state.doc;\n invalidRef.current = false;\n setInvalid(false);\n setStreamedText('');\n setIsProcessing(false);\n setError(null);\n const invalidate = () => {\n setEditable(!editor.isDestroyed && editor.isEditable);\n if (editor.isDestroyed || !editor.isEditable || editor.state.doc !== sourceDoc.current) {\n invalidRef.current = true;\n setInvalid(true);\n cancelRequest();\n setIsProcessing(false);\n setStreamedText('');\n setError('The document or editing permissions changed. Close and reopen AI Assistant.');\n }\n };\n invalidate();\n editor.on('update', invalidate);\n editor.on('transaction', invalidate);\n editor.on('destroy', invalidate);\n return () => {\n cancelRequest();\n editor.off('update', invalidate);\n editor.off('transaction', invalidate);\n editor.off('destroy', invalidate);\n };\n }, [editor, initialSelection, selectionRange.from, selectionRange.to, cancelRequest]);\n\n const selectedText = initialSelection;\n\n useEffect(() => {\n if (promptRef.current) promptRef.current.focus();\n }, []);\n\n const handleGenerate = useCallback(async (action: AIAction | 'custom' = 'custom', customPromptOverride?: string) => {\n if (editor.isDestroyed || !editor.isEditable || invalidRef.current || abortRef.current) return;\n const mode = selectedText ? 'replace' : 'insert';\n if (mode === 'insert' && !prompt.trim()) return;\n\n const actionPrompt = action === 'custom'\n ? (mode === 'replace'\n ? 'Edit the selected text according to the user instruction. Return only the replacement text.'\n : 'Generate text according to the user instruction. Return only the text to insert.')\n : getSystemPrompt(action, customPromptOverride);\n const systemPrompt = [aiConfig.systemPrompt, actionPrompt].filter(Boolean).join('\\n\\n');\n const userText = mode === 'replace'\n ? (prompt.trim()\n ? `Instruction:\\n${prompt.trim()}\\n\\nSelected text:\\n${selectedText}`\n : selectedText)\n : prompt.trim();\n\n setIsProcessing(true);\n setStreamedText('');\n setError(null);\n\n const abort = new AbortController();\n abortRef.current = abort;\n\n try {\n let result = '';\n for await (const chunk of getStream(aiConfig, userText, systemPrompt, action, abort.signal, {\n prompt: prompt.trim(),\n selectedText,\n mode,\n })) {\n if (abort.signal.aborted || abortRef.current !== abort) break;\n result += chunk;\n setStreamedText(result);\n }\n } catch (err: unknown) {\n if (abort.signal.aborted || abortRef.current !== abort) return;\n setStreamedText('');\n setError(String(err instanceof Error ? err.message : err));\n } finally {\n if (abortRef.current === abort) {\n abortRef.current = null;\n setIsProcessing(false);\n }\n }\n }, [editor, aiConfig, selectedText, prompt]);\n\n const handleAccept = useCallback(() => {\n if (!streamedText || abortRef.current || editor.isDestroyed || !editor.isEditable || invalidRef.current) return;\n const { from, to } = selectionRange;\n if (editor.state.doc !== sourceDoc.current || !Number.isInteger(from) || !Number.isInteger(to) || from < 0 || to < from || to > editor.state.doc.content.size) {\n setError('The original selection is no longer valid. Close and reopen AI Assistant.');\n return;\n }\n // AI output is plain text, never HTML. Use the captured range even if the cursor moved.\n editor.view.dispatch(editor.state.tr.insertText(streamedText, from, to));\n setStreamedText('');\n setPrompt('');\n onClose();\n }, [editor, streamedText, selectionRange, onClose]);\n\n const handleReject = useCallback(() => {\n cancelRequest();\n setStreamedText('');\n setIsProcessing(false);\n setError(null);\n }, [cancelRequest]);\n\n const handleKeyDown = useCallback((e: React.KeyboardEvent) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleGenerate();\n }\n }, [handleGenerate]);\n\n return (\n <div className=\"rte-ai-panel-v2\">\n <div className=\"rte-ai-panel-v2__header\">\n <span className=\"rte-ai-panel-v2__title\">β¨ {labels.ai || 'AI Assistant'}</span>\n <button type=\"button\" className=\"rte-ai-panel-v2__close-btn\" onClick={() => { cancelRequest(); onClose(); }} title={labels.aiClose} aria-label={labels.aiClose}>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" /><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n </div>\n {/* Prompt input */}\n <div className=\"rte-ai-panel-v2__prompt-section\">\n <label className=\"rte-ai-panel-v2__label\" htmlFor={promptId}>{labels.aiPromptLabel}</label>\n <textarea\n id={promptId}\n ref={promptRef}\n className=\"rte-ai-panel-v2__prompt-input\"\n value={prompt}\n onChange={(e) => setPrompt(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={labels.aiPromptPlaceholder}\n rows={3}\n disabled={isProcessing || !editable || invalid}\n />\n </div>\n\n {/* Selected text display */}\n <div className=\"rte-ai-panel-v2__selection-section\">\n <span className=\"rte-ai-panel-v2__label\">{labels.aiSelectedText}</span>\n <div className=\"rte-ai-panel-v2__selection-box\">\n {selectedText ? (\n <span>{selectedText.length > 300 ? selectedText.slice(0, 300) + '...' : selectedText}</span>\n ) : (\n <span className=\"rte-ai-panel-v2__empty\">{labels.aiInsertHint}</span>\n )}\n </div>\n </div>\n\n {/* Quick actions */}\n <div className=\"rte-ai-panel-v2__quick-actions\">\n {QUICK_ACTIONS.map(({ key, labelKey, icon }) => (\n <button\n key={key}\n type=\"button\"\n className=\"rte-ai-panel-v2__quick-btn\"\n onClick={() => handleGenerate(key)}\n disabled={isProcessing || !editable || invalid || !selectedText.trim()}\n title={selectedText ? labels[labelKey] : labels.aiInsertHint}\n >\n <span>{icon}</span>\n <span>{labels[labelKey]}</span>\n </button>\n ))}\n </div>\n\n {/* Generate button */}\n <div className=\"rte-ai-panel-v2__actions\">\n <button\n type=\"button\"\n className=\"rte-ai-panel-v2__generate-btn\"\n onClick={() => handleGenerate()}\n disabled={isProcessing || !editable || invalid || (!prompt.trim() && !selectedText.trim())}\n >\n {isProcessing ? labels.aiProcessing : `β¨ ${labels.aiGenerate}`}\n </button>\n {isProcessing && (\n <button type=\"button\" className=\"rte-ai-panel-v2__cancel-btn\" onClick={handleReject}>\n {labels.aiCancel}\n </button>\n )}\n </div>\n\n {/* Streaming result */}\n {isProcessing && !streamedText && (\n <LoadingState\n className=\"rte-ai-panel-v2__loading\"\n label={labels.aiProcessing}\n variant=\"dots\"\n layout=\"block\"\n size=\"sm\"\n />\n )}\n {streamedText && (\n <div className=\"rte-ai-panel-v2__result\">\n <div className=\"rte-ai-panel-v2__result-header\">\n <span className=\"rte-ai-panel-v2__label\">{labels.aiResult}</span>\n <span className={`rte-ai-panel-v2__stream-status ${isProcessing ? 'rte-ai-panel-v2__stream-status--live' : ''}`} role=\"status\">\n <span className=\"rte-ai-panel-v2__stream-dot\" aria-hidden=\"true\" />\n {isProcessing ? labels.aiProcessing : `${streamedText.length} chars`}\n </span>\n </div>\n <div className=\"rte-ai-panel-v2__result-text\">\n <StreamingText text={streamedText} streaming={isProcessing} />\n </div>\n <div className=\"rte-ai-panel-v2__result-actions\">\n <button type=\"button\" className=\"rte-ai-panel-v2__accept-btn\" disabled={isProcessing || !editable || invalid} onClick={handleAccept}>\n {labels.aiAccept}\n </button>\n <button type=\"button\" className=\"rte-ai-panel-v2__reject-btn\" onClick={handleReject}>\n {labels.aiReject}\n </button>\n </div>\n </div>\n )}\n\n {/* Error */}\n {error && (\n <div className=\"rte-ai-panel-v2__error\">\n <span>{error}</span>\n <button type=\"button\" onClick={() => setError(null)}>{labels.aiDismiss}</button>\n </div>\n )}\n </div>\n );\n};\n\nexport default AIRewrite;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AIRewrite.js","names":[],"sources":["../../../../src/components/pro/RichTextEditor/AIRewrite.tsx"],"sourcesContent":["import React, { useState, useRef, useCallback, useEffect, useId } from 'react';\nimport type { Editor } from '@tiptap/react';\nimport type { AIConfig, AIAction, RichTextEditorLabels } from './types';\nimport { defaultLabels } from './types';\nimport StreamingText from '../../ai/StreamingText';\nimport LoadingState from '../../ai/LoadingState';\n\ninterface AIRewriteProps {\n editor: Editor;\n aiConfig: AIConfig;\n labels?: RichTextEditorLabels;\n initialSelection: string;\n selectionRange: { from: number; to: number };\n followSelection?: boolean;\n onClose: () => void;\n}\n\nconst QUICK_ACTIONS: { key: AIAction; labelKey: keyof RichTextEditorLabels; icon: string }[] = [\n { key: 'rewrite', labelKey: 'aiRewrite', icon: 'β' },\n { key: 'shorten', labelKey: 'aiShorten', icon: 'π' },\n { key: 'expand', labelKey: 'aiExpand', icon: 'π' },\n { key: 'fix_grammar', labelKey: 'aiFixGrammar', icon: 'β' },\n { key: 'translate_en', labelKey: 'aiTranslateEn', icon: 'EN' },\n { key: 'translate_zh', labelKey: 'aiTranslateZh', icon: 'δΈ' },\n { key: 'professional', labelKey: 'aiProfessional', icon: 'πΌ' },\n { key: 'casual', labelKey: 'aiCasual', icon: 'π¬' },\n];\n\nfunction getSystemPrompt(action: AIAction | 'custom', customPrompt?: string): string {\n if (action === 'custom') return customPrompt || 'Follow the user instructions.';\n const prompts: Record<AIAction, string> = {\n rewrite: 'Rewrite the following text to improve clarity and readability. Keep the same meaning and tone. Return only the rewritten text.',\n shorten: 'Shorten the following text while preserving its key meaning. Be concise. Return only the shortened text.',\n expand: 'Expand the following text with more details and examples while keeping the same tone. Return only the expanded text.',\n fix_grammar: 'Fix any grammar, spelling, and punctuation errors in the following text. Return only the corrected text.',\n translate_en: 'Translate the following text to English. Return only the translated text.',\n translate_zh: 'Translate the following text to Chinese. Return only the translated text.',\n professional: 'Rewrite the following text in a professional, formal tone. Return only the rewritten text.',\n casual: 'Rewrite the following text in a casual, friendly tone. Return only the rewritten text.',\n };\n return customPrompt || prompts[action];\n}\n\nasync function* readSSE(response: Response): AsyncGenerator<string> {\n const reader = response.body?.getReader();\n if (!reader) throw new Error('No response body');\n const decoder = new TextDecoder();\n let buffer = '';\n let data: string[] = [];\n try {\n while (true) {\n const { done, value } = await reader.read();\n buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });\n // Normalize CRLF only when the complete line is available (chunks may split it).\n let newline: number;\n while ((newline = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, newline).replace(/\\r$/, '');\n buffer = buffer.slice(newline + 1);\n if (!line) {\n if (data.length) yield data.join('\\n');\n data = [];\n } else if (line.startsWith('data:')) {\n data.push(line.slice(5).replace(/^ /, ''));\n }\n }\n if (done) {\n if (buffer.startsWith('data:')) data.push(buffer.slice(5).replace(/^ /, '').replace(/\\r$/, ''));\n if (data.length) yield data.join('\\n');\n return;\n }\n }\n } finally {\n try { await reader.cancel(); } catch { /* The request may already be aborted. */ }\n reader.releaseLock();\n }\n}\n\nasync function* streamOpenAI(\n config: AIConfig,\n text: string,\n systemPrompt: string,\n signal: AbortSignal,\n): AsyncGenerator<string> {\n if (!config.gateway || !config.model) throw new Error('OpenAI protocol requires gateway and model');\n const body = JSON.stringify({\n model: config.model,\n messages: [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: text },\n ],\n stream: true,\n });\n\n const response = await fetch(config.gateway, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n ...config.headers,\n },\n body,\n signal,\n });\n\n if (!response.ok) throw new Error(`AI API error: ${response.status} ${response.statusText}`);\n for await (const payload of readSSE(response)) {\n if (payload.trim() === '[DONE]') return;\n let data;\n try { data = JSON.parse(payload); } catch { continue; }\n if (data?.error) throw new Error(data.error.message || 'AI stream error');\n const content = data?.choices?.[0]?.delta?.content;\n if (typeof content === 'string') yield content;\n }\n}\n\nasync function* streamAnthropic(\n config: AIConfig,\n text: string,\n systemPrompt: string,\n signal: AbortSignal,\n): AsyncGenerator<string> {\n if (!config.gateway || !config.model) throw new Error('Anthropic protocol requires gateway and model');\n const body = JSON.stringify({\n model: config.model,\n max_tokens: 4096,\n system: systemPrompt,\n messages: [{ role: 'user', content: text }],\n stream: true,\n });\n\n const response = await fetch(config.gateway, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { 'x-api-key': config.apiKey } : {}),\n 'anthropic-version': '2023-06-01',\n ...config.headers,\n },\n body,\n signal,\n });\n\n if (!response.ok) throw new Error(`AI API error: ${response.status} ${response.statusText}`);\n for await (const payload of readSSE(response)) {\n let data;\n try { data = JSON.parse(payload); } catch { continue; }\n if (data?.type === 'error') throw new Error(data.error?.message || 'AI stream error');\n if (data?.type === 'message_stop') return;\n if (data?.type === 'content_block_delta' && typeof data.delta?.text === 'string') yield data.delta.text;\n }\n}\n\nfunction getStream(\n config: AIConfig,\n text: string,\n systemPrompt: string,\n action: AIAction | 'custom',\n signal: AbortSignal,\n request?: { prompt: string; selectedText: string; mode: 'replace' | 'insert' },\n): AsyncIterable<string> {\n if (config.protocol === 'custom') {\n if (!config.customHandler) throw new Error('Custom AI protocol requires customHandler');\n return config.customHandler({\n text,\n action: action === 'custom' ? 'rewrite' : action,\n systemPrompt,\n signal,\n prompt: request?.prompt ?? '',\n selectedText: request?.selectedText ?? text,\n mode: request?.mode ?? 'replace',\n });\n }\n if (config.protocol === 'anthropic') return streamAnthropic(config, text, systemPrompt, signal);\n return streamOpenAI(config, text, systemPrompt, signal);\n}\n\nconst AIRewrite: React.FC<AIRewriteProps> = ({ editor, aiConfig, labels: propsLabels, initialSelection, selectionRange, followSelection = false, onClose }) => {\n const labels = { ...defaultLabels, ...propsLabels };\n const promptId = useId();\n const [prompt, setPrompt] = useState('');\n const [isProcessing, setIsProcessing] = useState(false);\n const [streamedText, setStreamedText] = useState('');\n const [error, setError] = useState<string | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const promptRef = useRef<HTMLTextAreaElement>(null);\n const [editable, setEditable] = useState(() => !editor.isDestroyed && editor.isEditable);\n const [invalid, setInvalid] = useState(false);\n const sourceDoc = useRef(editor.state.doc);\n const invalidRef = useRef(false);\n const [selectionSnapshot, setSelectionSnapshot] = useState(() => ({ from: selectionRange.from, to: selectionRange.to, text: initialSelection }));\n const snapshotRef = useRef(selectionSnapshot);\n\n const cancelRequest = useCallback(() => {\n const request = abortRef.current;\n abortRef.current = null;\n request?.abort();\n }, []);\n\n useEffect(() => {\n snapshotRef.current = { from: selectionRange.from, to: selectionRange.to, text: initialSelection };\n setSelectionSnapshot(snapshotRef.current);\n }, [initialSelection, selectionRange.from, selectionRange.to]);\n\n useEffect(() => {\n sourceDoc.current = editor.state.doc;\n invalidRef.current = false;\n setInvalid(false);\n setStreamedText('');\n setIsProcessing(false);\n setError(null);\n const invalidate = () => {\n setEditable(!editor.isDestroyed && editor.isEditable);\n if (editor.isDestroyed || !editor.isEditable || editor.state.doc !== sourceDoc.current) {\n invalidRef.current = true;\n setInvalid(true);\n cancelRequest();\n setIsProcessing(false);\n setStreamedText('');\n setError('The document or editing permissions changed. Close and reopen AI Assistant.');\n }\n };\n const onSelection = () => {\n if (!followSelection || editor.isDestroyed || !editor.isEditable || editor.state.doc !== sourceDoc.current || invalidRef.current) return;\n const { from, to } = editor.state.selection;\n const previous = snapshotRef.current;\n // Ignore collapsed selections caused by focusing the AI prompt or toolbar.\n if (from === to || (from === previous.from && to === previous.to)) return;\n const next = { from, to, text: editor.state.doc.textBetween(from, to, ' ') };\n snapshotRef.current = next;\n setSelectionSnapshot(next);\n cancelRequest();\n setIsProcessing(false);\n setStreamedText('');\n setError(null);\n };\n invalidate();\n editor.on('update', invalidate);\n editor.on('transaction', invalidate);\n editor.on('selectionUpdate', onSelection);\n editor.on('destroy', invalidate);\n return () => {\n cancelRequest();\n editor.off('update', invalidate);\n editor.off('transaction', invalidate);\n editor.off('selectionUpdate', onSelection);\n editor.off('destroy', invalidate);\n };\n }, [editor, cancelRequest, followSelection]);\n\n const selectedText = selectionSnapshot.text;\n\n useEffect(() => {\n if (promptRef.current) promptRef.current.focus();\n }, []);\n\n const handleGenerate = useCallback(async (action: AIAction | 'custom' = 'custom', customPromptOverride?: string) => {\n if (editor.isDestroyed || !editor.isEditable || invalidRef.current || abortRef.current) return;\n const mode = selectedText ? 'replace' : 'insert';\n if (mode === 'insert' && !prompt.trim()) return;\n\n const actionPrompt = action === 'custom'\n ? (mode === 'replace'\n ? 'Edit the selected text according to the user instruction. Return only the replacement text.'\n : 'Generate text according to the user instruction. Return only the text to insert.')\n : getSystemPrompt(action, customPromptOverride);\n const systemPrompt = [aiConfig.systemPrompt, actionPrompt].filter(Boolean).join('\\n\\n');\n const userText = mode === 'replace'\n ? (prompt.trim()\n ? `Instruction:\\n${prompt.trim()}\\n\\nSelected text:\\n${selectedText}`\n : selectedText)\n : prompt.trim();\n\n setIsProcessing(true);\n setStreamedText('');\n setError(null);\n\n const abort = new AbortController();\n abortRef.current = abort;\n\n try {\n let result = '';\n for await (const chunk of getStream(aiConfig, userText, systemPrompt, action, abort.signal, {\n prompt: prompt.trim(),\n selectedText,\n mode,\n })) {\n if (abort.signal.aborted || abortRef.current !== abort) break;\n result += chunk;\n setStreamedText(result);\n }\n } catch (err: unknown) {\n if (abort.signal.aborted || abortRef.current !== abort) return;\n setStreamedText('');\n setError(String(err instanceof Error ? err.message : err));\n } finally {\n if (abortRef.current === abort) {\n abortRef.current = null;\n setIsProcessing(false);\n }\n }\n }, [editor, aiConfig, selectedText, prompt]);\n\n const handleAccept = useCallback(() => {\n if (!streamedText || abortRef.current || editor.isDestroyed || !editor.isEditable || invalidRef.current) return;\n const { from, to } = snapshotRef.current;\n if (editor.state.doc !== sourceDoc.current || !Number.isInteger(from) || !Number.isInteger(to) || from < 0 || to < from || to > editor.state.doc.content.size) {\n setError('The original selection is no longer valid. Close and reopen AI Assistant.');\n return;\n }\n // AI output is plain text, never HTML. Use the captured range even if the cursor moved.\n editor.view.dispatch(editor.state.tr.insertText(streamedText, from, to));\n setStreamedText('');\n setPrompt('');\n onClose();\n }, [editor, streamedText, onClose]);\n\n const handleReject = useCallback(() => {\n cancelRequest();\n setStreamedText('');\n setIsProcessing(false);\n setError(null);\n }, [cancelRequest]);\n\n const handleKeyDown = useCallback((e: React.KeyboardEvent) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleGenerate();\n }\n }, [handleGenerate]);\n\n return (\n <div className=\"rte-ai-panel-v2\">\n <div className=\"rte-ai-panel-v2__header\">\n <span className=\"rte-ai-panel-v2__title\">β¨ {labels.ai || 'AI Assistant'}</span>\n <button type=\"button\" className=\"rte-ai-panel-v2__close-btn\" onClick={() => { cancelRequest(); onClose(); }} title={labels.aiClose} aria-label={labels.aiClose}>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" /><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n </div>\n {/* Prompt input */}\n <div className=\"rte-ai-panel-v2__prompt-section\">\n <label className=\"rte-ai-panel-v2__label\" htmlFor={promptId}>{labels.aiPromptLabel}</label>\n <textarea\n id={promptId}\n ref={promptRef}\n className=\"rte-ai-panel-v2__prompt-input\"\n value={prompt}\n onChange={(e) => setPrompt(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={labels.aiPromptPlaceholder}\n rows={3}\n disabled={isProcessing || !editable || invalid}\n />\n </div>\n\n {/* Selected text display */}\n <div className=\"rte-ai-panel-v2__selection-section\">\n <span className=\"rte-ai-panel-v2__label\">{labels.aiSelectedText}</span>\n <div className=\"rte-ai-panel-v2__selection-box\">\n {selectedText ? (\n <span>{selectedText.length > 300 ? selectedText.slice(0, 300) + '...' : selectedText}</span>\n ) : (\n <span className=\"rte-ai-panel-v2__empty\">{labels.aiInsertHint}</span>\n )}\n </div>\n </div>\n\n {/* Quick actions */}\n <div className=\"rte-ai-panel-v2__quick-actions\">\n {QUICK_ACTIONS.map(({ key, labelKey, icon }) => (\n <button\n key={key}\n type=\"button\"\n className=\"rte-ai-panel-v2__quick-btn\"\n onClick={() => handleGenerate(key)}\n disabled={isProcessing || !editable || invalid || !selectedText.trim()}\n title={selectedText ? labels[labelKey] : labels.aiInsertHint}\n >\n <span>{icon}</span>\n <span>{labels[labelKey]}</span>\n </button>\n ))}\n </div>\n\n {/* Generate button */}\n <div className=\"rte-ai-panel-v2__actions\">\n <button\n type=\"button\"\n className=\"rte-ai-panel-v2__generate-btn\"\n onClick={() => handleGenerate()}\n disabled={isProcessing || !editable || invalid || (!prompt.trim() && !selectedText.trim())}\n >\n {isProcessing ? labels.aiProcessing : `β¨ ${labels.aiGenerate}`}\n </button>\n {isProcessing && (\n <button type=\"button\" className=\"rte-ai-panel-v2__cancel-btn\" onClick={handleReject}>\n {labels.aiCancel}\n </button>\n )}\n </div>\n\n {/* Streaming result */}\n {isProcessing && !streamedText && (\n <LoadingState\n className=\"rte-ai-panel-v2__loading\"\n label={labels.aiProcessing}\n variant=\"dots\"\n layout=\"block\"\n size=\"sm\"\n />\n )}\n {streamedText && (\n <div className=\"rte-ai-panel-v2__result\">\n <div className=\"rte-ai-panel-v2__result-header\">\n <span className=\"rte-ai-panel-v2__label\">{labels.aiResult}</span>\n <span className={`rte-ai-panel-v2__stream-status ${isProcessing ? 'rte-ai-panel-v2__stream-status--live' : ''}`} role=\"status\">\n <span className=\"rte-ai-panel-v2__stream-dot\" aria-hidden=\"true\" />\n {isProcessing ? labels.aiProcessing : `${streamedText.length} chars`}\n </span>\n </div>\n <div className=\"rte-ai-panel-v2__result-text\">\n <StreamingText text={streamedText} streaming={isProcessing} />\n </div>\n <div className=\"rte-ai-panel-v2__result-actions\">\n <button type=\"button\" className=\"rte-ai-panel-v2__accept-btn\" disabled={isProcessing || !editable || invalid} onClick={handleAccept}>\n {labels.aiAccept}\n </button>\n <button type=\"button\" className=\"rte-ai-panel-v2__reject-btn\" onClick={handleReject}>\n {labels.aiReject}\n </button>\n </div>\n </div>\n )}\n\n {/* Error */}\n {error && (\n <div className=\"rte-ai-panel-v2__error\">\n <span>{error}</span>\n <button type=\"button\" onClick={() => setError(null)}>{labels.aiDismiss}</button>\n </div>\n )}\n </div>\n );\n};\n\nexport default AIRewrite;\n"],"mappings":"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"}
|
|
@@ -1,68 +1,65 @@
|
|
|
1
1
|
import { ColorPicker as e } from "./Toolbar/ColorPicker.js";
|
|
2
|
-
import { FONT_FAMILIES as t, FONT_SIZES as n } from "./Toolbar/SelectDropdown.js";
|
|
3
|
-
import { useEffect as
|
|
4
|
-
import { Fragment as
|
|
5
|
-
import { useEditorState as
|
|
6
|
-
import { TextSelection as
|
|
2
|
+
import { FONT_FAMILIES as t, FONT_SIZES as n, SelectDropdown as r } from "./Toolbar/SelectDropdown.js";
|
|
3
|
+
import { useCallback as i, useEffect as a, useId as o, useLayoutEffect as s, useRef as c, useState as l } from "react";
|
|
4
|
+
import { Fragment as u, jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
5
|
+
import { useEditorState as p } from "@tiptap/react";
|
|
6
|
+
import { TextSelection as m } from "@tiptap/pm/state";
|
|
7
7
|
//#region src/components/pro/RichTextEditor/BubbleToolbar.tsx
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
editor:
|
|
8
|
+
var h = ({ editor: h, onAddComment: g }) => {
|
|
9
|
+
p({
|
|
10
|
+
editor: h,
|
|
11
11
|
selector: ({ transactionNumber: e }) => e
|
|
12
12
|
});
|
|
13
|
-
let [
|
|
13
|
+
let [_, v] = l(!1), [y, b] = l({
|
|
14
14
|
x: 0,
|
|
15
15
|
y: 0
|
|
16
|
-
}),
|
|
17
|
-
if (
|
|
18
|
-
if (
|
|
19
|
-
let e =
|
|
16
|
+
}), x = c(null), S = o(), C = i((e) => e instanceof Element && e.closest(`[data-rte-palette-owner="${S}"]`) !== null, [S]);
|
|
17
|
+
if (a(() => {
|
|
18
|
+
if (h.isDestroyed) return;
|
|
19
|
+
let e = h.view.dom.ownerDocument, t = e.defaultView;
|
|
20
20
|
if (!t) return;
|
|
21
21
|
let n = null, r = () => {
|
|
22
|
-
n !== null && t.cancelAnimationFrame(n), n = null,
|
|
22
|
+
n !== null && t.cancelAnimationFrame(n), n = null, v(!1);
|
|
23
23
|
}, i = () => {
|
|
24
|
-
if (n = null,
|
|
24
|
+
if (n = null, h.isDestroyed) {
|
|
25
25
|
r();
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
|
-
let { from: t, to: i } =
|
|
29
|
-
if (!
|
|
30
|
-
|
|
28
|
+
let { from: t, to: i } = h.state.selection;
|
|
29
|
+
if (!h.isEditable || !(h.state.selection instanceof m) || t === i || !h.isFocused && !x.current?.contains(e.activeElement) && !C(e.activeElement)) {
|
|
30
|
+
v(!1);
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
-
let a =
|
|
33
|
+
let a = h.view.coordsAtPos(t), o = h.view.coordsAtPos(i), s = h.view.dom.closest(".rte-editor-wrapper");
|
|
34
34
|
if (!s) {
|
|
35
35
|
r();
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
let c = s.getBoundingClientRect(), l = (a.left + o.left) / 2 - c.left - s.clientLeft + s.scrollLeft, u = Math.min(a.top, o.top) - c.top - s.clientTop + s.scrollTop - 44;
|
|
39
|
-
|
|
39
|
+
b({
|
|
40
40
|
x: l,
|
|
41
41
|
y: u
|
|
42
|
-
}),
|
|
42
|
+
}), v(!0);
|
|
43
43
|
}, a = () => {
|
|
44
44
|
n !== null && t.cancelAnimationFrame(n), n = t.requestAnimationFrame(i);
|
|
45
45
|
}, o = ({ event: e }) => {
|
|
46
|
-
e.relatedTarget instanceof Node &&
|
|
46
|
+
e.relatedTarget instanceof Node && (x.current?.contains(e.relatedTarget) || C(e.relatedTarget)) || r();
|
|
47
47
|
};
|
|
48
|
-
return
|
|
49
|
-
n !== null && t.cancelAnimationFrame(n),
|
|
48
|
+
return h.on("transaction", a), h.on("selectionUpdate", a), h.on("focus", a), h.on("blur", o), h.on("destroy", r), e.addEventListener("scroll", a, !0), t.addEventListener("resize", a), i(), () => {
|
|
49
|
+
n !== null && t.cancelAnimationFrame(n), h.off("transaction", a), h.off("selectionUpdate", a), h.off("focus", a), h.off("blur", o), h.off("destroy", r), e.removeEventListener("scroll", a, !0), t.removeEventListener("resize", a);
|
|
50
50
|
};
|
|
51
|
-
}, [
|
|
52
|
-
if (!
|
|
53
|
-
let e =
|
|
51
|
+
}, [h, C]), s(() => {
|
|
52
|
+
if (!_ || !x.current) return;
|
|
53
|
+
let e = x.current, t = e.parentElement;
|
|
54
54
|
if (!t) return;
|
|
55
|
-
let n =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}));
|
|
60
|
-
}, [m, g.x]), !m || f.isDestroyed || !f.isEditable) return null;
|
|
61
|
-
let y = (e, t, n, r) => /* @__PURE__ */ c("button", {
|
|
55
|
+
let n = e.offsetWidth;
|
|
56
|
+
e.style.left = `${Math.max(0, Math.min(y.x - n / 2, t.clientWidth - n))}px`;
|
|
57
|
+
}, [_, y.x]), !_ || h.isDestroyed || !h.isEditable) return null;
|
|
58
|
+
let w = (e, t, n, r) => /* @__PURE__ */ d("button", {
|
|
62
59
|
type: "button",
|
|
63
60
|
className: `rte-bubble__btn ${n ? "rte-bubble__btn--active" : ""}`,
|
|
64
61
|
onClick: () => {
|
|
65
|
-
!
|
|
62
|
+
!h.isDestroyed && h.isEditable && e();
|
|
66
63
|
},
|
|
67
64
|
title: t,
|
|
68
65
|
"aria-label": t,
|
|
@@ -70,124 +67,117 @@ var f = ({ editor: f, onAddComment: p }) => {
|
|
|
70
67
|
onMouseDown: (e) => e.preventDefault(),
|
|
71
68
|
children: r
|
|
72
69
|
});
|
|
73
|
-
return /* @__PURE__ */
|
|
74
|
-
ref:
|
|
70
|
+
return /* @__PURE__ */ f("div", {
|
|
71
|
+
ref: x,
|
|
75
72
|
className: "rte-bubble",
|
|
76
73
|
role: "group",
|
|
77
74
|
"aria-label": "Selection formatting",
|
|
78
75
|
onBlur: (e) => {
|
|
79
|
-
!e.currentTarget.contains(e.relatedTarget) && !
|
|
76
|
+
!e.currentTarget.contains(e.relatedTarget) && !C(e.relatedTarget) && !h.isFocused && v(!1);
|
|
80
77
|
},
|
|
81
78
|
onKeyDown: (e) => {
|
|
82
|
-
e.key === "Escape" && (e.preventDefault(),
|
|
83
|
-
},
|
|
84
|
-
style: {
|
|
85
|
-
left: g.x,
|
|
86
|
-
top: g.y
|
|
79
|
+
e.key === "Escape" && (e.preventDefault(), h.commands.focus(), v(!1));
|
|
87
80
|
},
|
|
81
|
+
style: { top: y.y },
|
|
88
82
|
children: [
|
|
89
|
-
|
|
83
|
+
w(() => h.chain().focus().toggleBold().run(), "Bold", h.isActive("bold"), /* @__PURE__ */ d("strong", {
|
|
90
84
|
style: { fontSize: 13 },
|
|
91
85
|
children: "B"
|
|
92
86
|
})),
|
|
93
|
-
|
|
87
|
+
w(() => h.chain().focus().toggleItalic().run(), "Italic", h.isActive("italic"), /* @__PURE__ */ d("em", {
|
|
94
88
|
style: { fontSize: 13 },
|
|
95
89
|
children: "I"
|
|
96
90
|
})),
|
|
97
|
-
|
|
91
|
+
w(() => h.chain().focus().toggleUnderline().run(), "Underline", h.isActive("underline"), /* @__PURE__ */ d("span", {
|
|
98
92
|
style: {
|
|
99
93
|
fontSize: 13,
|
|
100
94
|
textDecoration: "underline"
|
|
101
95
|
},
|
|
102
96
|
children: "U"
|
|
103
97
|
})),
|
|
104
|
-
|
|
98
|
+
w(() => h.chain().focus().toggleStrike().run(), "Strike", h.isActive("strike"), /* @__PURE__ */ d("span", {
|
|
105
99
|
style: {
|
|
106
100
|
fontSize: 13,
|
|
107
101
|
textDecoration: "line-through"
|
|
108
102
|
},
|
|
109
103
|
children: "S"
|
|
110
104
|
})),
|
|
111
|
-
|
|
105
|
+
w(() => h.chain().focus().toggleCode().run(), "Code", h.isActive("code"), /* @__PURE__ */ d("span", {
|
|
112
106
|
style: {
|
|
113
107
|
fontSize: 11,
|
|
114
108
|
fontFamily: "monospace"
|
|
115
109
|
},
|
|
116
110
|
children: "</>"
|
|
117
111
|
})),
|
|
118
|
-
|
|
119
|
-
let e = window.prompt("URL:",
|
|
120
|
-
if (e === null ||
|
|
121
|
-
let t =
|
|
112
|
+
w(() => {
|
|
113
|
+
let e = window.prompt("URL:", h.getAttributes("link").href || "");
|
|
114
|
+
if (e === null || h.isDestroyed || !h.isEditable) return;
|
|
115
|
+
let t = h.chain().focus().extendMarkRange("link");
|
|
122
116
|
e.trim() === "" ? t.unsetLink().run() : t.setLink({ href: e.trim() }).run();
|
|
123
|
-
}, "Link",
|
|
117
|
+
}, "Link", h.isActive("link"), /* @__PURE__ */ f("svg", {
|
|
124
118
|
width: "14",
|
|
125
119
|
height: "14",
|
|
126
120
|
viewBox: "0 0 24 24",
|
|
127
121
|
fill: "none",
|
|
128
122
|
stroke: "currentColor",
|
|
129
123
|
strokeWidth: "2",
|
|
130
|
-
children: [/* @__PURE__ */
|
|
124
|
+
children: [/* @__PURE__ */ d("path", { d: "M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" }), /* @__PURE__ */ d("path", { d: "M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" })]
|
|
131
125
|
})),
|
|
132
|
-
/* @__PURE__ */
|
|
133
|
-
/* @__PURE__ */
|
|
134
|
-
value:
|
|
126
|
+
/* @__PURE__ */ d("div", { className: "rte-bubble__divider" }),
|
|
127
|
+
/* @__PURE__ */ d(e, {
|
|
128
|
+
value: h.getAttributes("textStyle").color || "",
|
|
135
129
|
onChange: (e) => {
|
|
136
|
-
!
|
|
130
|
+
!h.isDestroyed && h.isEditable && (e ? h.chain().focus().setColor(e).run() : h.chain().focus().unsetColor().run());
|
|
137
131
|
},
|
|
138
132
|
title: "Text color",
|
|
139
133
|
buttonClassName: "rte-bubble__btn",
|
|
134
|
+
portalOwner: S,
|
|
140
135
|
kind: "text"
|
|
141
136
|
}),
|
|
142
|
-
/* @__PURE__ */
|
|
143
|
-
value:
|
|
137
|
+
/* @__PURE__ */ d(e, {
|
|
138
|
+
value: h.getAttributes("highlight").color || "",
|
|
144
139
|
onChange: (e) => {
|
|
145
|
-
!
|
|
140
|
+
!h.isDestroyed && h.isEditable && (e ? h.chain().focus().toggleHighlight({ color: e }).run() : h.chain().focus().unsetHighlight().run());
|
|
146
141
|
},
|
|
147
142
|
title: "Highlight color",
|
|
148
143
|
buttonClassName: "rte-bubble__btn",
|
|
144
|
+
portalOwner: S,
|
|
149
145
|
kind: "highlight"
|
|
150
146
|
}),
|
|
151
|
-
/* @__PURE__ */
|
|
152
|
-
/* @__PURE__ */
|
|
147
|
+
/* @__PURE__ */ d("div", { className: "rte-bubble__divider" }),
|
|
148
|
+
/* @__PURE__ */ d(r, {
|
|
153
149
|
className: "rte-bubble__select",
|
|
154
|
-
|
|
150
|
+
width: 96,
|
|
151
|
+
value: h.getAttributes("textStyle").fontFamily || "",
|
|
152
|
+
options: t,
|
|
155
153
|
onChange: (e) => {
|
|
156
|
-
!
|
|
154
|
+
!h.isDestroyed && h.isEditable && (e ? h.chain().focus().setFontFamily(e).run() : h.chain().focus().unsetFontFamily().run());
|
|
157
155
|
},
|
|
158
|
-
title: "Font family"
|
|
159
|
-
"aria-label": "Font family",
|
|
160
|
-
children: t.map((e) => /* @__PURE__ */ c("option", {
|
|
161
|
-
value: e.value,
|
|
162
|
-
children: e.label
|
|
163
|
-
}, e.value))
|
|
156
|
+
title: "Font family"
|
|
164
157
|
}),
|
|
165
|
-
/* @__PURE__ */
|
|
158
|
+
/* @__PURE__ */ d(r, {
|
|
166
159
|
className: "rte-bubble__select rte-bubble__select--size",
|
|
167
|
-
|
|
160
|
+
width: 72,
|
|
161
|
+
value: h.getAttributes("textStyle").fontSize || "",
|
|
162
|
+
options: n,
|
|
168
163
|
onChange: (e) => {
|
|
169
|
-
!
|
|
164
|
+
!h.isDestroyed && h.isEditable && (e ? h.chain().focus().setFontSize(e).run() : h.chain().focus().unsetFontSize().run());
|
|
170
165
|
},
|
|
171
|
-
title: "Font size"
|
|
172
|
-
"aria-label": "Font size",
|
|
173
|
-
children: n.map((e) => /* @__PURE__ */ c("option", {
|
|
174
|
-
value: e.value,
|
|
175
|
-
children: e.label
|
|
176
|
-
}, e.value))
|
|
166
|
+
title: "Font size"
|
|
177
167
|
}),
|
|
178
|
-
|
|
168
|
+
g && /* @__PURE__ */ f(u, { children: [/* @__PURE__ */ d("div", { className: "rte-bubble__divider" }), w(g, "Comment", void 0, /* @__PURE__ */ d("svg", {
|
|
179
169
|
width: "14",
|
|
180
170
|
height: "14",
|
|
181
171
|
viewBox: "0 0 24 24",
|
|
182
172
|
fill: "none",
|
|
183
173
|
stroke: "currentColor",
|
|
184
174
|
strokeWidth: "2",
|
|
185
|
-
children: /* @__PURE__ */
|
|
175
|
+
children: /* @__PURE__ */ d("path", { d: "M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z" })
|
|
186
176
|
}))] })
|
|
187
177
|
]
|
|
188
178
|
});
|
|
189
179
|
};
|
|
190
180
|
//#endregion
|
|
191
|
-
export {
|
|
181
|
+
export { h as default };
|
|
192
182
|
|
|
193
183
|
//# sourceMappingURL=BubbleToolbar.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BubbleToolbar.js","names":[],"sources":["../../../../src/components/pro/RichTextEditor/BubbleToolbar.tsx"],"sourcesContent":["import React, { useState, useEffect, useLayoutEffect, useRef } from 'react';\nimport { useEditorState, type Editor } from '@tiptap/react';\nimport { TextSelection } from '@tiptap/pm/state';\nimport { ColorPicker } from './Toolbar/ColorPicker';\nimport { FONT_FAMILIES, FONT_SIZES } from './Toolbar/SelectDropdown';\n\ninterface BubbleToolbarProps {\n editor: Editor;\n onAddComment?: () => void;\n}\n\nconst BubbleToolbar: React.FC<BubbleToolbarProps> = ({ editor, onAddComment }) => {\n useEditorState({ editor, selector: ({ transactionNumber }) => transactionNumber });\n const [visible, setVisible] = useState(false);\n const [pos, setPos] = useState({ x: 0, y: 0 });\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (editor.isDestroyed) return;\n const doc = editor.view.dom.ownerDocument;\n const win = doc.defaultView;\n if (!win) return;\n let frame: number | null = null;\n const hide = () => {\n if (frame !== null) win.cancelAnimationFrame(frame);\n frame = null;\n setVisible(false);\n };\n const update = () => {\n frame = null;\n if (editor.isDestroyed) { hide(); return; }\n const { from, to } = editor.state.selection;\n if (!editor.isEditable || !(editor.state.selection instanceof TextSelection) || from === to || (!editor.isFocused && !ref.current?.contains(doc.activeElement))) {\n setVisible(false);\n return;\n }\n const coords = editor.view.coordsAtPos(from);\n const endCoords = editor.view.coordsAtPos(to);\n const wrapperEl = editor.view.dom.closest<HTMLElement>('.rte-editor-wrapper');\n if (!wrapperEl) { hide(); return; }\n const rect = wrapperEl.getBoundingClientRect();\n\n const centerX = (coords.left + endCoords.left) / 2 - rect.left - wrapperEl.clientLeft + wrapperEl.scrollLeft;\n const topY = Math.min(coords.top, endCoords.top) - rect.top - wrapperEl.clientTop + wrapperEl.scrollTop - 44;\n\n setPos({ x: centerX, y: topY });\n setVisible(true);\n };\n\n const scheduleUpdate = () => {\n if (frame !== null) win.cancelAnimationFrame(frame);\n frame = win.requestAnimationFrame(update);\n };\n const onBlur = ({ event }: { event: FocusEvent }) => {\n if (event.relatedTarget instanceof Node && ref.current?.contains(event.relatedTarget)) return;\n hide();\n };\n editor.on('transaction', scheduleUpdate);\n editor.on('selectionUpdate', scheduleUpdate);\n editor.on('focus', scheduleUpdate);\n editor.on('blur', onBlur);\n editor.on('destroy', hide);\n doc.addEventListener('scroll', scheduleUpdate, true);\n win.addEventListener('resize', scheduleUpdate);\n update();\n return () => {\n if (frame !== null) win.cancelAnimationFrame(frame);\n editor.off('transaction', scheduleUpdate);\n editor.off('selectionUpdate', scheduleUpdate);\n editor.off('focus', scheduleUpdate);\n editor.off('blur', onBlur);\n editor.off('destroy', hide);\n doc.removeEventListener('scroll', scheduleUpdate, true);\n win.removeEventListener('resize', scheduleUpdate);\n };\n }, [editor]);\n\n // Keep the bubble inside the wrapper horizontally: with the extra color /\n // font controls it can be wider than a phone-size editor, and transform:\n // translateX(-50%) would otherwise clip it at either edge.\n useLayoutEffect(() => {\n if (!visible || !ref.current) return;\n const el = ref.current;\n const wrapper = el.parentElement;\n if (!wrapper) return;\n const w = wrapper.clientWidth;\n const half = el.offsetWidth / 2;\n const min = Math.min(half + 4, w / 2);\n const max = Math.max(w - half - 4, w / 2);\n const clamped = Math.min(Math.max(pos.x, min), max);\n if (clamped !== pos.x) setPos((p) => ({ ...p, x: clamped }));\n }, [visible, pos.x]);\n\n if (!visible || editor.isDestroyed || !editor.isEditable) return null;\n\n const btn = (onClick: () => void, title: string, isActive: boolean | undefined, children: React.ReactNode) => (\n <button\n type=\"button\"\n className={`rte-bubble__btn ${isActive ? 'rte-bubble__btn--active' : ''}`}\n onClick={() => { if (!editor.isDestroyed && editor.isEditable) onClick(); }}\n title={title}\n aria-label={title}\n aria-pressed={isActive}\n onMouseDown={(e) => e.preventDefault()}\n >\n {children}\n </button>\n );\n\n return (\n <div ref={ref} className=\"rte-bubble\" role=\"group\" aria-label=\"Selection formatting\"\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget) && !editor.isFocused) setVisible(false);\n }}\n onKeyDown={(event) => {\n if (event.key === 'Escape') {\n event.preventDefault();\n editor.commands.focus();\n setVisible(false);\n }\n }}\n style={{ left: pos.x, top: pos.y }}>\n {btn(() => editor.chain().focus().toggleBold().run(), 'Bold', editor.isActive('bold'),\n <strong style={{ fontSize: 13 }}>B</strong>)}\n {btn(() => editor.chain().focus().toggleItalic().run(), 'Italic', editor.isActive('italic'),\n <em style={{ fontSize: 13 }}>I</em>)}\n {btn(() => editor.chain().focus().toggleUnderline().run(), 'Underline', editor.isActive('underline'),\n <span style={{ fontSize: 13, textDecoration: 'underline' }}>U</span>)}\n {btn(() => editor.chain().focus().toggleStrike().run(), 'Strike', editor.isActive('strike'),\n <span style={{ fontSize: 13, textDecoration: 'line-through' }}>S</span>)}\n {btn(() => editor.chain().focus().toggleCode().run(), 'Code', editor.isActive('code'),\n <span style={{ fontSize: 11, fontFamily: 'monospace' }}></></span>)}\n {btn(() => {\n const url = window.prompt('URL:', editor.getAttributes('link').href || '');\n if (url === null || editor.isDestroyed || !editor.isEditable) return;\n const chain = editor.chain().focus().extendMarkRange('link');\n if (url.trim() === '') chain.unsetLink().run();\n else chain.setLink({ href: url.trim() }).run();\n }, 'Link', editor.isActive('link'),\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71\" /><path d=\"M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71\" /></svg>)}\n <div className=\"rte-bubble__divider\" />\n <ColorPicker\n value={editor.getAttributes('textStyle').color || ''}\n onChange={(color) => {\n if (editor.isDestroyed || !editor.isEditable) return;\n if (color) editor.chain().focus().setColor(color).run();\n else editor.chain().focus().unsetColor().run();\n }}\n title=\"Text color\"\n buttonClassName=\"rte-bubble__btn\"\n kind=\"text\"\n />\n <ColorPicker\n value={editor.getAttributes('highlight').color || ''}\n onChange={(color) => {\n if (editor.isDestroyed || !editor.isEditable) return;\n if (color) editor.chain().focus().toggleHighlight({ color }).run();\n else editor.chain().focus().unsetHighlight().run();\n }}\n title=\"Highlight color\"\n buttonClassName=\"rte-bubble__btn\"\n kind=\"highlight\"\n />\n <div className=\"rte-bubble__divider\" />\n <select\n className=\"rte-bubble__select\"\n value={editor.getAttributes('textStyle').fontFamily || ''}\n onChange={(e) => {\n if (editor.isDestroyed || !editor.isEditable) return;\n if (e.target.value) editor.chain().focus().setFontFamily(e.target.value).run();\n else editor.chain().focus().unsetFontFamily().run();\n }}\n title=\"Font family\"\n aria-label=\"Font family\"\n >\n {FONT_FAMILIES.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n <select\n className=\"rte-bubble__select rte-bubble__select--size\"\n value={editor.getAttributes('textStyle').fontSize || ''}\n onChange={(e) => {\n if (editor.isDestroyed || !editor.isEditable) return;\n if (e.target.value) (editor.chain().focus() as any).setFontSize(e.target.value).run();\n else (editor.chain().focus() as any).unsetFontSize().run();\n }}\n title=\"Font size\"\n aria-label=\"Font size\"\n >\n {FONT_SIZES.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n {onAddComment && (\n <>\n <div className=\"rte-bubble__divider\" />\n {btn(onAddComment, 'Comment', undefined,\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z\" /></svg>)}\n </>\n )}\n </div>\n );\n};\n\nexport default BubbleToolbar;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BubbleToolbar.js","names":[],"sources":["../../../../src/components/pro/RichTextEditor/BubbleToolbar.tsx"],"sourcesContent":["import React, { useState, useEffect, useLayoutEffect, useRef, useId, useCallback } from 'react';\nimport { useEditorState, type Editor } from '@tiptap/react';\nimport { TextSelection } from '@tiptap/pm/state';\nimport { ColorPicker } from './Toolbar/ColorPicker';\nimport { SelectDropdown, FONT_FAMILIES, FONT_SIZES } from './Toolbar/SelectDropdown';\n\ninterface BubbleToolbarProps {\n editor: Editor;\n onAddComment?: () => void;\n}\n\nconst BubbleToolbar: React.FC<BubbleToolbarProps> = ({ editor, onAddComment }) => {\n useEditorState({ editor, selector: ({ transactionNumber }) => transactionNumber });\n const [visible, setVisible] = useState(false);\n const [pos, setPos] = useState({ x: 0, y: 0 });\n const ref = useRef<HTMLDivElement>(null);\n const bubbleId = useId();\n const ownsPalette = useCallback((target: EventTarget | null) =>\n target instanceof Element && target.closest(`[data-rte-palette-owner=\"${bubbleId}\"]`) !== null, [bubbleId]);\n\n useEffect(() => {\n if (editor.isDestroyed) return;\n const doc = editor.view.dom.ownerDocument;\n const win = doc.defaultView;\n if (!win) return;\n let frame: number | null = null;\n const hide = () => {\n if (frame !== null) win.cancelAnimationFrame(frame);\n frame = null;\n setVisible(false);\n };\n const update = () => {\n frame = null;\n if (editor.isDestroyed) { hide(); return; }\n const { from, to } = editor.state.selection;\n if (!editor.isEditable || !(editor.state.selection instanceof TextSelection) || from === to || (!editor.isFocused && !ref.current?.contains(doc.activeElement) && !ownsPalette(doc.activeElement))) {\n setVisible(false);\n return;\n }\n const coords = editor.view.coordsAtPos(from);\n const endCoords = editor.view.coordsAtPos(to);\n const wrapperEl = editor.view.dom.closest<HTMLElement>('.rte-editor-wrapper');\n if (!wrapperEl) { hide(); return; }\n const rect = wrapperEl.getBoundingClientRect();\n\n const centerX = (coords.left + endCoords.left) / 2 - rect.left - wrapperEl.clientLeft + wrapperEl.scrollLeft;\n const topY = Math.min(coords.top, endCoords.top) - rect.top - wrapperEl.clientTop + wrapperEl.scrollTop - 44;\n\n setPos({ x: centerX, y: topY });\n setVisible(true);\n };\n\n const scheduleUpdate = () => {\n if (frame !== null) win.cancelAnimationFrame(frame);\n frame = win.requestAnimationFrame(update);\n };\n const onBlur = ({ event }: { event: FocusEvent }) => {\n if (event.relatedTarget instanceof Node && (ref.current?.contains(event.relatedTarget) || ownsPalette(event.relatedTarget))) return;\n hide();\n };\n editor.on('transaction', scheduleUpdate);\n editor.on('selectionUpdate', scheduleUpdate);\n editor.on('focus', scheduleUpdate);\n editor.on('blur', onBlur);\n editor.on('destroy', hide);\n doc.addEventListener('scroll', scheduleUpdate, true);\n win.addEventListener('resize', scheduleUpdate);\n update();\n return () => {\n if (frame !== null) win.cancelAnimationFrame(frame);\n editor.off('transaction', scheduleUpdate);\n editor.off('selectionUpdate', scheduleUpdate);\n editor.off('focus', scheduleUpdate);\n editor.off('blur', onBlur);\n editor.off('destroy', hide);\n doc.removeEventListener('scroll', scheduleUpdate, true);\n win.removeEventListener('resize', scheduleUpdate);\n };\n }, [editor, ownsPalette]);\n\n // A transform here turns fixed-position color palettes into descendants of\n // the scrolling editor, where overflow clips them. Position the bubble's\n // left edge directly instead of centering it with translateX(-50%).\n useLayoutEffect(() => {\n if (!visible || !ref.current) return;\n const el = ref.current;\n const wrapper = el.parentElement;\n if (!wrapper) return;\n const width = el.offsetWidth;\n el.style.left = `${Math.max(0, Math.min(pos.x - width / 2, wrapper.clientWidth - width))}px`;\n }, [visible, pos.x]);\n\n if (!visible || editor.isDestroyed || !editor.isEditable) return null;\n\n const btn = (onClick: () => void, title: string, isActive: boolean | undefined, children: React.ReactNode) => (\n <button\n type=\"button\"\n className={`rte-bubble__btn ${isActive ? 'rte-bubble__btn--active' : ''}`}\n onClick={() => { if (!editor.isDestroyed && editor.isEditable) onClick(); }}\n title={title}\n aria-label={title}\n aria-pressed={isActive}\n onMouseDown={(e) => e.preventDefault()}\n >\n {children}\n </button>\n );\n\n return (\n <div ref={ref} className=\"rte-bubble\" role=\"group\" aria-label=\"Selection formatting\"\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget) && !ownsPalette(event.relatedTarget) && !editor.isFocused) setVisible(false);\n }}\n onKeyDown={(event) => {\n if (event.key === 'Escape') {\n event.preventDefault();\n editor.commands.focus();\n setVisible(false);\n }\n }}\n style={{ top: pos.y }}>\n {btn(() => editor.chain().focus().toggleBold().run(), 'Bold', editor.isActive('bold'),\n <strong style={{ fontSize: 13 }}>B</strong>)}\n {btn(() => editor.chain().focus().toggleItalic().run(), 'Italic', editor.isActive('italic'),\n <em style={{ fontSize: 13 }}>I</em>)}\n {btn(() => editor.chain().focus().toggleUnderline().run(), 'Underline', editor.isActive('underline'),\n <span style={{ fontSize: 13, textDecoration: 'underline' }}>U</span>)}\n {btn(() => editor.chain().focus().toggleStrike().run(), 'Strike', editor.isActive('strike'),\n <span style={{ fontSize: 13, textDecoration: 'line-through' }}>S</span>)}\n {btn(() => editor.chain().focus().toggleCode().run(), 'Code', editor.isActive('code'),\n <span style={{ fontSize: 11, fontFamily: 'monospace' }}></></span>)}\n {btn(() => {\n const url = window.prompt('URL:', editor.getAttributes('link').href || '');\n if (url === null || editor.isDestroyed || !editor.isEditable) return;\n const chain = editor.chain().focus().extendMarkRange('link');\n if (url.trim() === '') chain.unsetLink().run();\n else chain.setLink({ href: url.trim() }).run();\n }, 'Link', editor.isActive('link'),\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71\" /><path d=\"M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71\" /></svg>)}\n <div className=\"rte-bubble__divider\" />\n <ColorPicker\n value={editor.getAttributes('textStyle').color || ''}\n onChange={(color) => {\n if (editor.isDestroyed || !editor.isEditable) return;\n if (color) editor.chain().focus().setColor(color).run();\n else editor.chain().focus().unsetColor().run();\n }}\n title=\"Text color\"\n buttonClassName=\"rte-bubble__btn\"\n portalOwner={bubbleId}\n kind=\"text\"\n />\n <ColorPicker\n value={editor.getAttributes('highlight').color || ''}\n onChange={(color) => {\n if (editor.isDestroyed || !editor.isEditable) return;\n if (color) editor.chain().focus().toggleHighlight({ color }).run();\n else editor.chain().focus().unsetHighlight().run();\n }}\n title=\"Highlight color\"\n buttonClassName=\"rte-bubble__btn\"\n portalOwner={bubbleId}\n kind=\"highlight\"\n />\n <div className=\"rte-bubble__divider\" />\n <SelectDropdown\n className=\"rte-bubble__select\"\n width={96}\n value={editor.getAttributes('textStyle').fontFamily || ''}\n options={FONT_FAMILIES}\n onChange={(value) => {\n if (editor.isDestroyed || !editor.isEditable) return;\n if (value) editor.chain().focus().setFontFamily(value).run();\n else editor.chain().focus().unsetFontFamily().run();\n }}\n title=\"Font family\"\n />\n <SelectDropdown\n className=\"rte-bubble__select rte-bubble__select--size\"\n width={72}\n value={editor.getAttributes('textStyle').fontSize || ''}\n options={FONT_SIZES}\n onChange={(value) => {\n if (editor.isDestroyed || !editor.isEditable) return;\n if (value) (editor.chain().focus() as any).setFontSize(value).run();\n else (editor.chain().focus() as any).unsetFontSize().run();\n }}\n title=\"Font size\"\n />\n {onAddComment && (\n <>\n <div className=\"rte-bubble__divider\" />\n {btn(onAddComment, 'Comment', undefined,\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z\" /></svg>)}\n </>\n )}\n </div>\n );\n};\n\nexport default BubbleToolbar;\n"],"mappings":"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"}
|