diff --git a/.github/workflows/gitleaks.yaml b/.github/workflows/gitleaks.yaml new file mode 100644 index 0000000..9ae62e1 --- /dev/null +++ b/.github/workflows/gitleaks.yaml @@ -0,0 +1,44 @@ +name: Secret Value found!! +on: + push: + public: +jobs: + scan: + name: gitleaks + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4.2.2 + - name: Install the gitleaks + run: wget https://github.com/zricethezav/gitleaks/releases/download/v8.15.2/gitleaks_8.15.2_linux_x64.tar.gz + shell: pwsh + - name: Extract the tar file + run: tar xzvf gitleaks_8.15.2_linux_x64.tar.gz + - name: Generate the report + id: gitleaks + run: $GITHUB_WORKSPACE/gitleaks detect -s $GITHUB_WORKSPACE -f json -r $GITHUB_WORKSPACE/leaksreport.json + shell: bash + continue-on-error: true + - name: Setup NuGet.exe + if: steps.gitleaks.outcome != 'success' + uses: nuget/setup-nuget@v2 + with: + nuget-version: latest + - name: Install Mono + if: steps.gitleaks.outcome != 'success' + run: | + sudo apt update + sudo apt install -y mono-complete + - name: Install the dotnet SDK to a custom directory + if: steps.gitleaks.outcome != 'success' + run: | + mkdir -p $GITHUB_WORKSPACE/dotnet + curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --install-dir $GITHUB_WORKSPACE/dotnet --channel 6.0 + - name: Install the report tool packages + if: steps.gitleaks.outcome != 'success' + run: | + export PATH=$GITHUB_WORKSPACE/dotnet:$PATH + nuget install "Syncfusion.Email" -source ${{ secrets.NexusFeedLink }} -ExcludeVersion + dir $GITHUB_WORKSPACE/Syncfusion.Email/lib/net6.0 + dotnet $GITHUB_WORKSPACE/Syncfusion.Email/lib/net6.0/GitleaksReportMail.dll ${{ secrets.CITEAMCREDENTIALS }} "$GITHUB_REF_NAME" ${{ secrets.NETWORKCREDENTIALS }} ${{ secrets.NETWORKKEY }} "$GITHUB_WORKSPACE" ${{ secrets.ORGANIZATIONNAME }} + exit 1 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d29575 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/README.md b/README.md index 5c9fecd..def2963 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,71 @@ -# ai-powered-docx-editor-sample +# ai-powered-react-docx-editor-sample A sample project showcasing the DOCX Editor with AI assistance for generating, refining, and summarizing content. + +## Getting Started + +To get started, clone the `ai-powered-react-docx-editor-sample` repository. + +``` +git clone https://github.com/SyncfusionExamples/ai-powered-react-docx-editor-sample.git +``` + +## Installation + +All required packages are pre-configured in the `package.json` file. Install the dependencies by running: + +``` +npm install +``` + +## License Registration + +Before using the Syncfusion DOCX Editor, register your license key in the `src/index.tsx` file: + +``` +import { registerLicense } from '@syncfusion/ej2-base'; + +// Registering Syncfusion license key +registerLicense('Replace your generated license key here'); + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +root.render( + + + +); +``` + +For more details on license registration, refer to the [official documentation](https://ej2.syncfusion.com/react/documentation/licensing/license-key-registration#register-syncfusion-license-key-in-the-project). + +## Configuring AI Services + +To set up the AI services, navigate to `src/ai-models.ts` and replace the placeholders with your actual credentials: + +``` +const azure = createAzure({ + resourceName: 'YOUR_RESOURCE_NAME', + apiKey: 'YOUR_API_KEY', +}); +const aiModel = azure('YOUR_MODEL_NAME'); + +// for gemini model +const google = createGoogleGenerativeAI({ + baseURL: "https://generativelanguage.googleapis.com/v1beta", + apiKey: "API_KEY" +}); +const aiModel = google('YOUR_MODEL_NAME'); +``` + +Your Azure endpoint should resemble: `https://{resource_name}.openai.azure.com/` + +For more information on Azure OpenAI configuration, consult the [Vercel AI SDK documentation](https://sdk.vercel.ai/providers/ai-sdk-providers/azure). + +## Development Server + +To run the application, use the following npm script: + +``` +npm start +``` + +This command will start the DOCX Editor and open it in your default web browser. diff --git a/package.json b/package.json new file mode 100644 index 0000000..3fa29d6 --- /dev/null +++ b/package.json @@ -0,0 +1,70 @@ +{ + "name": "my-app", + "version": "0.1.0", + "private": true, + "dependencies": { + "@syncfusion/ej2": "*", + "@syncfusion/ej2-base": "*", + "@syncfusion/ej2-react-base": "*", + "@syncfusion/ej2-react-buttons": "*", + "@syncfusion/ej2-react-documenteditor": "*", + "@syncfusion/ej2-react-dropdowns": "*", + "@syncfusion/ej2-react-inputs": "*", + "@syncfusion/ej2-react-interactive-chat": "*", + "@syncfusion/ej2-react-layouts": "*", + "@syncfusion/ej2-react-lists": "*", + "@syncfusion/ej2-react-navigations": "*", + "@syncfusion/ej2-react-notifications": "*", + "@syncfusion/ej2-react-popups": "*", + "@syncfusion/ej2-react-splitbuttons": "*", + "@xenova/transformers": "^2.17.2", + "@ai-sdk/azure": "^3.0.6", + "@ai-sdk/google": "^3.0.6", + "@azure/openai": "^2.0.0-beta.1", + "@google/generative-ai": "^0.13.0", + "ai": "^6.0.27", + "form-data": "^4.0.0", + "groq-sdk": "^0.5.0", + "openai": "^4.56.0", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.2", + "@types/node": "^16.18.126", + "@types/react": "^19.1.6", + "@types/react-dom": "^19.1.5", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-scripts": "5.0.1", + "typescript": "^4.9.5", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@types/codemirror": "^5.60.16" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..a11777c Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..3121d5c --- /dev/null +++ b/public/index.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + AI-Powered React DOCX Editor + + + + +
+ + + \ No newline at end of file diff --git a/public/logo192.png b/public/logo192.png new file mode 100644 index 0000000..fc44b0a Binary files /dev/null and b/public/logo192.png differ diff --git a/public/logo512.png b/public/logo512.png new file mode 100644 index 0000000..a4e47a6 Binary files /dev/null and b/public/logo512.png differ diff --git a/src/AIPopup.css b/src/AIPopup.css new file mode 100644 index 0000000..df79aba --- /dev/null +++ b/src/AIPopup.css @@ -0,0 +1,351 @@ +.ai-assist-dialog, +.e-stop-generating-dialog { + max-height: none !important; +} + +.e-dlg-container.e-smart-editor-dialog, +.ai-settings-menu .e-control.e-contextmenu, +.ai-smart-menu ul { + position: fixed !important; +} + +.ai-assist-dialog, +.e-stop-generating-dialog, +.e-smart-editor-dialog { + min-width: 280px !important; +} + +.e-original-word .e-original-word { + color: #DC143C; + text-decoration: line-through; +} + +.e-improved-word { + color: #228B22; +} + +.ai-chat-btn.e-btn { + height: 40px; + width: 40px; + min-height: 40px; + min-width: 40px; +} + +.ai-input-wrapper { + flex: 1; +} + +.e-caret-hide .e-caret { + display: none; +} + +.ai-input-wrapper .e-send.e-disabled { + pointer-events: none; +} + +.ai-gc-row { + display: flex; + align-items: center; + gap: 8px; +} + +.e-smart-editor-dialog .pane-content-header { + display: flex; + height: 48px; + align-items: center; +} + +.e-smart-editor-dialog .pane-text-area { + height: 280px; + border: 1px solid #ccc; + border-radius: 4px; + padding: 6px 10px !important; + min-height: 280px; + overflow-y: auto; +} + +.e-smart-editor-dialog .e-split-bar.e-resizable-split-bar, +.e-smart-editor-dialog .e-resizable-split-bar.e-split-bar-hover, +.e-smart-editor-dialog .e-resize-handler.e-icons { + display: none !important; +} + +.e-smart-editor-dialog .e-dlg-header-content { + height: 56px; + padding: 12px 16px; +} + +.e-bigger .e-smart-editor-dialog .e-dlg-header-content { + height: 64px; + padding: 12px 20px; +} + +.e-smart-editor-dialog .e-dlg-header-content .e-custom-header { + height: 32px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.e-bigger .e-smart-editor-dialog .e-dlg-header-content .e-custom-header { + height: 42px; +} + +.e-ai-assist-toolbar, +.e-ai-assist-toolbar .e-toolbar-items, +.e-ai-assist-toolbar .e-toolbar-item, +.e-ai-assist-toolbar .e-toolbar-item:hover, +.e-ai-assist-toolbar .e-toolbar-item:focus, +.e-ai-assist-toolbar .e-toolbar-item .e-tbar-btn, +.e-ai-assist-toolbar .e-dropdown-btn.settings-btn { + background-color: #fff !important; +} + +.e-ai-assist-toolbar .e-toolbar-item .e-tbar-btn, +.e-ai-assist-toolbar .settings-btn { + box-shadow: none !important; +} + +.e-ai-assist-toolbar .e-toolbar-item .e-tbar-btn-text, +.e-ai-assist-toolbar .e-toolbar-item .e-btn-icon.e-icons { + color: #212529 !important; +} + +.e-smart-editor-dialog .e-footer-content { + padding: 14px 16px 12px 16px; +} + +.e-bigger .e-smart-editor-dialog .e-footer-content { + padding: 16px 20px 16px 20px; +} + +.e-smart-editor-dialog .e-splitter.e-splitter-vertical { + border: none; +} + +.e-ai-chat-btn::before, +.stop-popup-text-icon::before { + content: '\e903'; +} + +.ai-assist-btn .e-ai-assist-btn::before { + content: '\e950'; +} + +.e-bigger .ai-assist-btn .e-icons.e-ai-assist-btn { + height: 16px; + width: 16px; + margin: 10px; +} + +.ai-assist-btn.e-btn { + background-color: #ffffff !important; + border-radius: 4px !important; + border: 1px solid #0F6CBD !important; + color: #0F6CBD !important; + min-height: 4px !important; + min-width: 4px !important; +} + +.ai-generate-dialog .e-dlg-header-content { + display: none !important; +} + +.settings-btn.e-dropdown-btn { + width: 36px; + height: 32px; + border: none; + color: #6b7280 !important; + box-shadow: none !important; + background: #fff !important; +} + +.e-bigger .e-smart-editor-dialog .settings-btn { + height: 40px; + width: 40px; +} + +.settings-btn .e-btn-icon { + color: #6b7280; +} + +.e-smart-editor-dialog .e-dlg-header-content .e-dlg-closeicon-btn { + height: 32px !important; + width: 32px !important; +} + +.e-smart-editor-dialog .e-dlg-header-content .e-icon-dlg-close.e-icons { + position: static !important; +} + +.e-stop-generating-dialog { + max-height: 100%; +} + +.ai-stop-popup { + padding: 12px; + border-radius: 8px; + box-shadow: 0 0 16px 0 rgba(60, 60, 119, 0.11); +} + +.e-bigger .ai-stop-popup { + height: 72px; + padding: 16px; +} + +.ai-stop-popup .stop-popup-text { + margin: 0 6px; +} + +.ai-stop-popup .stop-popup-text-icon { + color: #0F6CBD; + font-size: 18px; + vertical-align: middle; +} + +.stop-popup-text-icon, +.stop-popup-text { + line-height: 32px; +} + +.e-bigger .stop-popup-text-icon, +.e-bigger .stop-popup-text { + line-height: 40px; +} + +.ai-stop-popup .ai-stop-btn { + height: 32px; + min-width: 151px; + line-height: 14px; + float: right; + z-index: 1000000001; +} + +.e-bigger .ai-stop-popup .ai-stop-btn { + height: 40px; + min-width: 151px; + padding: 9px 16px; +} + +.ai-stop-popup .ai-stop-btn, +.ai-stop-popup .stop-popup-text, +.e-ai-assist-toolbar .e-icons.e-btn, +.e-smart-editor-dialog .translate-label, +.e-smart-editor-dialog .e-icon-dlg-close, +.ai-assist-dialog .e-dlg-content .e-icons.e-settings, +.e-ai-assist-toolbar .e-toolbar-item.page-count span, +.ai-assist-dialog .e-dlg-content .e-icons.e-reference, +.ai-assist-dialog .e-dlg-content .e-icons.e-send, +.ai-assist-dialog .e-dlg-content .e-textbox.e-input, +.ai-assist-dialog .e-dlg-header-content .e-popup-header, +.ai-assist-btn .e-icons.e-ai-assist-btn { + font-size: 14px !important; +} + +.e-bigger .ai-stop-popup .ai-stop-btn, +.e-bigger .e-ai-assist-toolbar .e-icons.e-btn, +.e-bigger .ai-stop-popup .stop-popup-text, +.e-bigger .e-smart-editor-dialog .translate-label, +.e-smart-editor-dialog .e-dlg-header-content .e-popup-header, +.e-bigger .e-ai-assist-toolbar .e-toolbar-item.page-count span, +.e-bigger .e-smart-editor-dialog .e-icon-dlg-close, +.e-bigger .ai-assist-dialog .e-dlg-content .e-icons.e-settings, +.e-bigger .ai-assist-dialog .e-dlg-content .e-textbox.e-input, +.e-bigger .ai-assist-dialog .e-dlg-header-content .e-popup-header, +.ai-chat-btn .e-icons.e-ai-chat-btn, +.e-bigger .ai-assist-btn .e-icons.e-ai-assist-btn { + font-size: 16px !important; +} + +.ai-stop-popup .stop-popup-text-icon, +.e-ai-assist-toolbar .e-toolbar-item.page-count span, +.e-ai-assist-toolbar .e-icons.e-btn, +.e-smart-editor-dialog .e-icon-dlg-close, +.ai-assist-dialog .e-dlg-content .e-icons.e-settings, +.ai-assist-dialog .e-dlg-content .e-icons.e-reference, +.ai-assist-dialog .e-dlg-content .e-icons.e-send, +.e-bigger .ai-assist-dialog .e-dlg-content .e-icons.e-reference, +.e-bigger .ai-assist-dialog .e-dlg-content .e-icons.e-send, +.ai-assist-dialog .e-dlg-content .e-textbox.e-input, +.ai-chat-btn .e-icons.e-ai-chat-btn { + font-weight: 400 !important; +} + +.e-smart-editor-dialog .translate-label, +.ai-stop-popup .stop-popup-text, +.e-bigger .ai-stop-popup .stop-popup-text, +.e-smart-editor-dialog .e-dlg-header-content .e-popup-header { + font-weight: 600 !important; +} + +.ai-settings-menu .e-menu-parent.e-ul .e-menu-item:not(.e-selected) { + padding-left: 34px; +} + +.ai-settings-menu .e-menu-parent.e-ul .e-menu-item.e-selected { + padding-left: 8px; +} + +.e-smart-editor-dialog .e-multiselect.e-control-wrapper, +.e-smart-editor-dialog .e-ddl.e-control-wrapper { + margin-left: 8px; +} + +.e-bigger .e-smart-editor-dialog .e-ddl.e-control-wrapper { + margin-left: 12px; +} + +.e-bigger .e-smart-editor-dialog .e-multiselect.e-control-wrapper { + margin-left: 9px; +} + +.e-smart-editor-dialog .e-header-toolbar { + height: 39px; +} + +.e-bigger .e-smart-editor-dialog .e-header-toolbar { + height: 48px; +} + +.e-smart-editor-dialog .e-icons.e-settings { + vertical-align: baseline; +} + +.e-bigger .e-smart-editor-dialog .e-icons.e-settings { + height: 16px; + width: 16px; +} + +.e-bigger .e-smart-editor-dialog .e-dlg-closeicon-btn { + top: 1px; +} + +.e-smart-editor-dialog .e-dlg-content { + padding: 0 16px !important; +} + +.e-bigger .e-smart-editor-dialog .e-dlg-content { + padding: 0 20px !important; +} + +.ai-settings-menu ul { + z-index: 1000000005 !important; +} + +.e-ribbon-help-template.e-hide { + display: none !important; +} + +.e-smart-editor-dialog .e-footer-content { + margin-top: 12px; +} + +.ai-assist-dialog .settings-btn { + color: #6b7280; +} + +.ai-assist-dialog .e-footer-content .e-discard-btn { + background-color: #fff !important; + border-color: #C50F1F !important; + color: #C50F1F !important; +} \ No newline at end of file diff --git a/src/AIPopup.jsx b/src/AIPopup.jsx new file mode 100644 index 0000000..8ac4bd2 --- /dev/null +++ b/src/AIPopup.jsx @@ -0,0 +1,1093 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { ButtonComponent } from '@syncfusion/ej2-react-buttons'; +import { DialogComponent } from '@syncfusion/ej2-react-popups'; +import { ContextMenuComponent } from '@syncfusion/ej2-react-navigations'; +import { ToolbarComponent, ItemsDirective, ItemDirective } from '@syncfusion/ej2-react-navigations'; +import { SplitterComponent, PanesDirective, PaneDirective } from '@syncfusion/ej2-react-layouts'; +import { TextBoxComponent } from '@syncfusion/ej2-react-inputs'; +import { ComboBoxComponent, MultiSelectComponent, CheckBoxSelection, Inject } from '@syncfusion/ej2-react-dropdowns'; +import { DropDownButtonComponent } from '@syncfusion/ej2-react-splitbuttons'; +import { FabComponent } from '@syncfusion/ej2-react-buttons'; +import { createSpinner, showSpinner, hideSpinner } from '@syncfusion/ej2-popups'; +import './editor-helpers.js'; +import './AIPopup.css'; +import { getAzureChatAIRequest } from './ai-models'; + +const GrammarOptions = [ + { Name: 'Subject-Verb Agreement' }, { Name: 'Tense Consistency' }, { Name: 'Pronoun Agreement' }, + { Name: 'Comma Usage' }, { Name: 'Parallel Structure' }, { Name: 'Misplaced Modifiers' }, + { Name: 'Dangling Modifiers' }, { Name: 'Word Choice' }, { Name: 'Redundancy' }, + { Name: 'Use of Articles' }, { Name: 'Punctuation Marks' }, { Name: 'Apostrophes for Possessives and Contractions' }, + { Name: 'Spelling Errors' }, +]; + +const TranslateList = ['English', 'Simplified Chinese', 'Spanish', 'French', 'Arabic', 'Portuguese', 'Russian', 'Urdu', 'Indonesian', 'German', 'Japanese']; + +const AiTask = { Generate: 'Generate', Rephrase: 'Rephrase', Translate: 'Translate', Grammar: 'Grammar' }; + +let aiResults = []; + +function levenshtein(a, b) { + const dp = Array(a.length + 1).fill(0).map(() => Array(b.length + 1).fill(0)); + for (let i = 0; i <= a.length; i++) dp[i][0] = i; + for (let j = 0; j <= b.length; j++) dp[0][j] = j; + for (let i = 1; i <= a.length; i++) { + for (let j = 1; j <= b.length; j++) { + const cost = a[i - 1] === b[j - 1] ? 0 : 1; + dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost); + } + } + return dp[a.length][b.length]; +} + +function isSimilar(a, b, threshold = 1) { + if (!a || !b) return false; + return levenshtein(a, b) <= threshold; +} + +function highlightDifferences(original, modified) { + const oWords = (original || '').split(/\s+/); + const mWords = (modified || '').split(/\s+/); + const lcs = Array(oWords.length + 1).fill(0).map(() => Array(mWords.length + 1).fill(0)); + for (let i = 0; i < oWords.length; i++) { + for (let j = 0; j < mWords.length; j++) { + if (oWords[i] === mWords[j] || isSimilar(oWords[i], mWords[j])) lcs[i + 1][j + 1] = lcs[i][j] + 1; + else lcs[i + 1][j + 1] = Math.max(lcs[i + 1][j], lcs[i][j + 1]); + } + } + + const unchanged = new Set(); + let x = oWords.length, y = mWords.length; + + while (x > 0 && y > 0) { + if (oWords[x - 1] === mWords[y - 1] || isSimilar(oWords[x - 1], mWords[y - 1])) { unchanged.add(`${x - 1}|${y - 1}`); x--; y--; } + else if (lcs[x - 1][y] >= lcs[x][y - 1]) x--; + else y--; + } + + const highlightedOriginal = oWords.map((w, i) => + [...unchanged].some(k => k.startsWith(`${i}|`)) ? w : `${w}`).join(' '); + + const highlightedModified = mWords.map((w, j) => + [...unchanged].some(k => k.endsWith(`|${j}`)) ? w : `${w}`).join(' '); + + return { highlightedOriginal, highlightedModified }; +} + +export default function AIPopup({ editorRef, onShowChatPane, chatOpen, assistInitialPos, isAIEnabled }) { + const [isSmartEditor, setIsSmartEditor] = useState(false); + const [popupType, setPopupType] = useState(''); + const [tone, setTone] = useState('Professional'); + const [format, setFormat] = useState('Paragraph'); + const [length, setLength] = useState('Medium'); + const [translateTo, setTranslateTo] = useState('French'); + const [checks, setChecks] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [outHtml, setOutHtml] = useState(''); + const [inHtml, setInHtml] = useState(''); + const [suggestions, setSuggestions] = useState([]); + const [currentIndex, setCurrentIndex] = useState(0); + const [userPrompt, setUserPrompt] = useState(''); + const [dialogPos, setDialogPos] = useState({ x: '200', y: '160' }); + const cmAssistRef = useRef(null); + const gearRef = useRef(null); + const genDialogRef = useRef(null); + const smartDialogRef = useRef(null); + const textboxRef = useRef(null); + const stopDlgRef = useRef(null); + const [draftRange, setDraftRange] = useState({ start: null, end: null }); + const [stopVisible, setStopVisible] = useState(false); + const [stopPos, setStopPos] = useState({ x: 0, y: 0 }); + const canceledRef = useRef(false); + const gearHeaderRef = useRef(null); + const cmSettingsRef = useRef(null); + const [genVisible, setGenVisible] = useState(false); + const [smartVisible, setSmartVisible] = useState(false); + const assistFabRef = useRef(null); + const chatFabRef = useRef(null); + const [viewerHost, setViewerHost] = useState(null); + const [fabChatVisible, setFabChatVisible] = useState(false); + const [fabAssistVisible, setFabAssistVisible] = useState(false); + + const [assistBtn, setAssistBtn] = useState({ + left: 80, + top: 160, + width: 24, + height: 24, + visible: true + }); + + const isContentGenerated = popupType === AiTask.Generate && !isSmartEditor && !!outHtml; + + const menuItems = [ + { text: 'Rephrase', iconCss: 'e-icons e-rephrase' }, + { text: 'Translate', iconCss: 'e-icons e-translate' }, + { text: 'Grammar', iconCss: 'e-icons e-grammar-check' }, + ]; + + const settingsMenuItems = [ + { + id: 'parent-tone', text: 'Choose Tone', items: [ + { id: 'child-tone-professional', text: 'Professional' }, + { id: 'child-tone-friendly', text: 'Friendly' }, + { id: 'child-tone-instructional', text: 'Instructional' }, + { id: 'child-tone-marketing', text: 'Marketing' }, + { id: 'child-tone-academic', text: 'Academic' }, + { id: 'child-tone-legal', text: 'Legal' }, + { id: 'child-tone-technical', text: 'Technical' }, + { id: 'child-tone-narrative', text: 'Narrative' }, + { id: 'child-tone-direct', text: 'Direct' } + ] + }, + { + id: 'parent-format', text: 'Choose Format', items: [ + { id: 'child-format-paragraph', text: 'Paragraph' }, + { id: 'child-format-blog-post', text: 'Blog post' }, + { id: 'child-format-technical-documentation', text: 'Technical Documentation' }, + { id: 'child-format-report', text: 'Report' }, + { id: 'child-format-research-papers', text: 'Research Papers' }, + { id: 'child-format-tutorial', text: 'Tutorial' }, + { id: 'child-format-meeting-notes', text: 'Meeting Notes' } + ] + }, + { + id: 'parent-size', text: 'Choose Size', items: [ + { id: 'child-size-short', text: 'Short' }, + { id: 'child-size-medium', text: 'Medium' }, + { id: 'child-size-long', text: 'Long' } + ] + } + ]; + + function buildPrompt(task, text, Regenerate, { + tone = 'Professional', + format = 'Paragraph', + length = 'Medium', + fromLang = 'English', + toLang = 'French', + checks = [], + userHint = '' + } = {}) { + const content = (text || '').trim().toLowerCase(); + const toneValue = String(tone).toLowerCase(); + const formatValue = String(format).toLowerCase(); + const lengthValue = String(length).toLowerCase(); + switch (task) { + case 'Generate': { + const currentResult = getSelectionText(); + if (!Regenerate) { + return (currentResult.length > 0) ? { + messages: [ + { role: "system", content: `You are a helpful assistant. Your task is to analyze the provided text and revise it based on the provided suggestion: '${content}'. Please adjust the text to reflect a tone of '${toneValue}', formatted in '${formatValue}' style, and maintain a length of '${lengthValue}'. Always respond in proper HTML format, excluding , , and tags.` }, + { role: "user", content: currentResult } + ], + model: "gpt-4", + } : { + messages: [ + { role: "system", content: `You are a helpful assistant. Your task is to generate content based on the provided text. Please adjust the text to reflect a tone of '${toneValue}', formatted in '${formatValue}' style, and maintain a length of '${lengthValue}'. Always respond in proper text format not a md format. Always respond in proper HTML format, excluding , , and tags.` }, + { role: "user", content: content } + ], + model: "gpt-4", + }; + } else { + return { + messages: [ + { role: "system", content: `You are a helpful assistant. Your task is to analyze the provided text and rephrase it. Please adjust the text to reflect a tone of '${toneValue}', formatted in '${formatValue}' style, and maintain a length of '${lengthValue}'. Always respond in proper HTML format, excluding , , and tags.` }, + { role: "user", content: currentResult } + ], + model: "gpt-4", + }; + } + } + + case 'Rephrase': { + if (!Regenerate) { + return { + messages: [ + { role: "system", content: `You are a helpful assistant. Your task is to analyze the provided text and rephrase it. Please adjust the text to reflect a tone of '${toneValue}', formatted in '${formatValue}' style, and maintain a length of '${lengthValue}'. Always respond in proper HTML format, excluding , , and tags.` }, + { role: "user", content: content } + ], + model: "gpt-4", + } + } else { + return { + messages: [ + { role: "system", content: `You are a helpful assistant. Your task is to analyze the provided text and revise it based on the provided suggestion: '${aiResults}'. Please adjust the text to reflect a tone of '${toneValue}', formatted in '${formatValue}' style, and maintain a length of '${lengthValue}'. Always respond in proper HTML format, excluding , , and tags.` }, + { role: "user", content: content } + ], + model: "gpt-4", + } + } + } + + case 'Translate': + return { + messages: [ + { role: "system", content: `You are a helpful assistant. Your task is to translate the provided text into '${toLang}'. Always respond in proper HTML format, excluding and tags.` }, + { role: "user", content: content } + ], + model: "gpt-4", + }; + + case 'Grammar': { + let value = ''; + let systemPrompt = ''; + if (checks.length > 0) { + checks.forEach((item) => { + value += item + ', '; + }); + systemPrompt = `You are a helpful assistant. Your task is to analyze the provided text and perform the following grammar checks: ${value}. Please ensure that the revised text reflects these corrections. Always respond in proper HTML format, but do not include , , or tags.`; + } else { + systemPrompt = "You are a helpful assistant. Your task is to analyze the provided text, check for and correct any grammatical errors, and rephrase it. Always respond in proper HTML format, but do not include , , or tags."; + } + return { + messages: [ + { role: "system", content: systemPrompt }, + { role: "user", content: content } + ], + model: "gpt-4", + }; + } + default: + return content; + } + } + + const generateContentOpen = (args) => { + if (isContentGenerated) { + const aiAssistBtnPosition = window?.getAIAssistPopupPosition?.(); + const updatedPosition = window?.getRegeneratePopupPosition?.(); + if (!aiAssistBtnPosition || !updatedPosition) return; + var x = String(Math.round(aiAssistBtnPosition.x)); + var y = String(Math.round(updatedPosition.y)); + genDialogRef.current.position.X = x; + genDialogRef.current.position.Y = y; + setDialogPos({ x: x, y: y }); + } + } + + const generateFooterTemplate = () => ( +
+ + Keep it + + runTask(AiTask.Generate, true)}> + Regenerate + + Discard +
+ ); + + useEffect(() => { + if (!assistInitialPos) return; + if (!isAIEnabled) { + setFabAssistVisible(false); + setFabChatVisible(false); + } + setAssistBtn(prev => ({ + ...prev, + left: assistInitialPos.left ?? prev.left, + top: assistInitialPos.top ?? prev.top, + width: assistInitialPos.width ?? prev.width, + height: assistInitialPos.height ?? prev.height + })); + }, [ + assistInitialPos?.left, + assistInitialPos?.top, + assistInitialPos?.width, + assistInitialPos?.height + ]); + + useEffect(() => { + let mounted = true; + const pick = () => { + if (!mounted) return; + const el = document.querySelector('#document-editor #documentEditorDiv'); + if (el && el !== viewerHost) { + setViewerHost(el); + } + }; + pick(); + const id = setInterval(pick, 300); + const onResize = () => pick(); + window.addEventListener('resize', onResize); + return () => { + mounted = false; + clearInterval(id); + window.removeEventListener('resize', onResize); + }; + }, [viewerHost]); + + + useEffect(() => { + if (isAIEnabled) { + setFabChatVisible(!chatOpen); + } + if (!chatOpen) { + requestAnimationFrame(positionChatFabByHelper); + } + }, [chatOpen]); + + const getSelectionText = () => { + try { + return (editorRef?.current?.documentEditor?.selection?.text || '').trim(); + } catch { return ''; } + }; + + const replaceSelectionWithPlainText = async (html) => { + const text = (html || '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim(); + try { + const editor = editorRef?.current?.documentEditor; + if (editor?.selection?.text) editor.editor.delete(); + editor?.editor?.insertText(text); + } catch (e) { alert('Replace failed: ' + e.message); } + }; + + const insertContent = async (out) => { + if (canceledRef.current) return; + closeStopDialog(); + setInHtml(''); + setOutHtml(out); + try { + const ed = editorRef?.current?.documentEditor; + if (ed) { + await ed.editor.delete(); + ed.focusIn(); + const { end: caretEndBefore } = getOffsets(); + if (caretEndBefore != null) selectOffsets(caretEndBefore, caretEndBefore); + const plain = htmlToPlain(out); + if (canceledRef.current) { + return; + } + ed.editor.insertText(plain); + ed.editor.insertText('\n'); + const { end: endAfter } = getOffsets(); + if (caretEndBefore != null && endAfter != null && endAfter >= caretEndBefore) { + selectOffsets(caretEndBefore, endAfter); + setDraftRange({ start: caretEndBefore, end: endAfter }); + } else { + setDraftRange({ start: null, end: null }); + } + } + } catch (e) { + alert('Insert failed: ' + (e?.message || e)); + } + setUserPrompt(''); + } + + async function runTask(task, isRegenerate = false, toLanguage) { + setIsLoading(true); + canceledRef.current = false; + var out = ''; + aiResults = []; + try { + let sourceText = ''; + let options = ''; + if (task === AiTask.Generate) { + sourceText = userPrompt?.trim() || textboxRef.current.value?.trim(); + setUserPrompt(''); + window?.toggleSendIcon?.(false); + openStopDialog(); + if (!isRegenerate && !sourceText) { setIsLoading(false); return; } + options = buildPrompt(AiTask.Generate, sourceText, isRegenerate, { tone, format, length }); + setTimeout(async () => { + out = await getAzureChatAIRequest(options); + out = out.replace("```html\n", "").replace("\n```", ""); + insertContent(out, isRegenerate); + }, 1000); + } else { + sourceText = getSelectionText(); + if (!sourceText || sourceText.trim().length < 3) { setIsLoading(false); return; } + if (task === AiTask.Rephrase) { + const userHint = isRegenerate ? '' : (userPrompt?.trim() || ''); + for (var i = 0; i < 3; i++) { + options = buildPrompt(AiTask.Rephrase, sourceText, isRegenerate, { tone, format, length, userHint }); + out = await getAzureChatAIRequest(options); + out = out.replace("```html\n", "").replace("\n```", ""); + if (!aiResults.includes(out)) { + aiResults.push(out); + } + } + } + else if (task === AiTask.Grammar) { + options = buildPrompt(AiTask.Grammar, sourceText, isRegenerate, { checks: checks }); + out = await getAzureChatAIRequest(options); + out = out.replace("```html\n", "").replace("\n```", ""); + } + else { + var toLang = toLanguage || translateTo; + options = buildPrompt(AiTask.Translate, sourceText, false, { fromLang: 'English', toLang: toLang }); + out = await getAzureChatAIRequest(options); + out = out.replace("```html\n", "").replace("\n```", ""); + } + out = aiResults.length > 0 ? aiResults[0] : out; + const { highlightedOriginal, highlightedModified } = highlightDifferences(`

${sourceText}

`, out); + setInHtml(highlightedOriginal); + setOutHtml(highlightedModified); + setSuggestions(prev => { + setCurrentIndex(0); + return aiResults; + }); + hideSpinner(document.getElementById('spinner-container')); + } + } catch (e) { + if (!canceledRef.current) alert('AI error: ' + (e?.message || e)); + } finally { + setIsLoading(false); + } + } + + const onMenuSelect = (args) => { + const sel = args.item?.text; + const action = sel === 'Rephrase' ? AiTask.Rephrase : (sel === 'Translate' ? AiTask.Translate : AiTask.Grammar); + if (!sel) return; + setPopupType(action); + setSuggestions([]); setCurrentIndex(0); setUserPrompt(''); + setIsSmartEditor(true); + setInHtml(`

${getSelectionText()}

`); + setOutHtml(''); + showSpinner(document.getElementById('spinner-container')); + setSmartVisible(true); + setTimeout(() => runTask(action), 100); + }; + + + const openAssistMenu = (ev) => { + ev?.preventDefault?.(); + ev?.stopPropagation?.(); + const sel = getSelectionText(); + if (!sel) { + if (stopVisible) return; + setPopupType(AiTask.Generate); + setIsSmartEditor(false); + setInHtml(''); setOutHtml(''); setUserPrompt(''); setSuggestions([]); + const pos = window.getAIAssistPopupPosition ? window?.getAIAssistPopupPosition?.() : { x: 200, y: 160 }; + setDialogPos({ x: String(Math.round(pos.x)), y: String(Math.round(pos.y)) }); + setGenVisible(true); + requestAnimationFrame(() => genDialogRef.current?.refreshPosition?.()); + return; + } + if (genVisible || stopVisible) return; + const aiPos = window.getAIButtonPosition ? window.getAIButtonPosition() : null; + if (aiPos && cmAssistRef.current?.open) { + cmAssistRef.current.open(Math.round(aiPos.y), Math.round(aiPos.x + 24)); + return; + } + const el = assistFabRef.current?.element || assistFabRef.current; + if (!el || !cmAssistRef.current?.open) return; + const r = el.getBoundingClientRect(); + const x = Math.round(r.left + window.scrollX + 24); + const y = Math.round(r.top + window.scrollY); + cmAssistRef.current.open(x, y); + }; + + const openChat = () => { + document.querySelector('.e-ribbon-help-template')?.classList.add('e-hide'); + document.querySelector('.e-fab.ai-assist-btn')?.classList.add('e-hide'); + document.querySelector('.document-editor-container')?.classList.add('e-hide'); + setFabChatVisible(false); + onShowChatPane?.(); + }; + + const onReplace = async () => { await replaceSelectionWithPlainText(outHtml); setSmartVisible(false); }; + + const prevSuggestion = () => { + setCurrentIndex(i => { + const next = Math.max(i - 1, 0); + setOutHtml(suggestions[next] || outHtml); + return next; + }); + }; + + const nextSuggestion = () => { + setCurrentIndex(i => { + const next = Math.min(i + 1, suggestions.length - 1); + setOutHtml(suggestions[next] || outHtml); + return next; + }); + }; + + const headerText = useMemo(() => { + if (popupType === AiTask.Rephrase) return 'Rephrased Content'; + if (popupType === AiTask.Translate) return 'Translate'; + if (popupType === AiTask.Grammar) return 'Grammar Check'; + return 'AI Assistant'; + }, [popupType]); + + const openHeaderSettingsMenu = () => { + const btn = gearHeaderRef.current; + if (!btn || !cmSettingsRef.current) return; + openMenuBesideButton(btn, cmSettingsRef); + }; + + const UpdateIconCss = (items) => { + items.forEach((item) => { + const id = item.id?.toLowerCase() || ""; + var isChild = id.includes("child"); + + var match = id.includes(tone.toLowerCase()) || + id.includes(format.split(" ").join('-').toLowerCase()) || + id.includes(length.toLowerCase()); + + item.iconCss = (isChild && match) ? "e-icons e-check" : null; + + if (Array.isArray(item.items) && item.items.length) { + UpdateIconCss(item.items); + } + }) + } + + const onSettingsMenuSelect = (args) => { + const id = args.item?.id || ''; + const text = args.item?.text || ''; + if (id.startsWith('child')) { + if (id.startsWith('child-tone-')) setTone(text); + else if (id.startsWith('child-format-')) setFormat(text); + else if (id.startsWith('child-size-')) setLength(text); + UpdateIconCss(settingsMenuItems); + } + }; + + const openGearMenu = (args) => { + const btn = gearRef.current; + if (!btn || !cmSettingsRef.current) return; + openMenuBesideButton(btn, cmSettingsRef); + }; + + const onSend = () => { + runTask(AiTask.Generate); + }; + + const changeLanguage = (e) => { + setTranslateTo(e.value); + setTimeout(() => { + runTask(AiTask.Translate, false, e.value); + }, 100); + }; + + const smartHeaderTemplate = () => ( +
+
{headerText}
+ {popupType === AiTask.Rephrase && ( +
+ + + + + = suggestions.length} + click={nextSuggestion} + /> + ( + { args.cancel = true; openHeaderSettingsMenu(); }} + /> + )} + /> + + +
+ )} +
+ ); + + const smartFooterTemplate = () => ( +
+ + Replace + + {popupType === AiTask.Rephrase && ( + { showSpinner(document.getElementById('spinner-container')); setTimeout(() => runTask(AiTask.Rephrase, true), 10); }} + isPrimary + > + Regenerate + + )} + {popupType === AiTask.Grammar && ( + { showSpinner(document.getElementById('spinner-container')); setTimeout(() => runTask(AiTask.Grammar, true), 10); }} + isPrimary + > + Regenerate + + )} + setSmartVisible(false)}>Cancel +
+ ); + + const positionChatFabByHelper = () => { + const position = window?.getAIChatBtnPosition?.(); + if (!position) return; + window?.setAiAssistBtnPosition?.(Math.round(position.x), Math.round(position.y)); + }; + + const onChatFabCreated = () => { + requestAnimationFrame(positionChatFabByHelper); + window.addEventListener('resize', positionChatFabByHelper); + }; + + useEffect(() => { + createSpinner({ + target: document.getElementById('spinner-container'), + }); + return () => window.removeEventListener('resize', positionChatFabByHelper); + }, []); + + const positionAssistFabInitial = () => { + try { + const pos = window?.getAIAssistBtnPosition?.(); + if (!pos) return; + setAssistBtn(s => ({ + ...s, + left: Math.round(pos.x), + top: Math.round(pos.y), + width: 24, + height: 24, + visible: true + })); + } catch { } + }; + + useEffect(() => { + if (viewerHost) positionAssistFabInitial(); + }, [viewerHost]); + + useEffect(() => { + if (editorRef && editorRef?.current) { + const ed = editorRef?.current?.documentEditor; + if (!ed) return; + const onSelectionChange = () => { + try { + const pos = window?.getAIAssistBtnPosition?.(); + if (pos) { + setAssistBtn(prev => ({ + ...prev, + left: Math.round(pos.x), + top: Math.round(pos.y) + })); + } + } catch { } + }; + ed.selectionChange = onSelectionChange; + + } + }, [editorRef, viewerHost]); + + useEffect(() => { + const viewerEl = + viewerHost || document.querySelector('#document-editor #documentEditorDiv'); + if (!viewerEl) return; + + let tracking = false; + + const onMouseDown = () => { + tracking = true; + try { + const sel = editorRef?.current?.documentEditor?.selection?.text || ''; + if (sel && isSmartEditor) setIsSmartEditor(false); + } catch { } + }; + + const onMouseUp = () => { + if (!tracking) return; + tracking = false; + + setTimeout(() => { + try { + const selText = editorRef?.current?.documentEditor?.selection?.text || ''; + if (!!selText && selText.trim().length > 0) { + setIsSmartEditor(true); + assistFabRef.title = 'Refine the content'; + } + + const pos = window?.getAIAssistBtnPosition?.(); + if (pos && typeof pos.x === 'number' && typeof pos.y === 'number') { + setAssistBtn(prev => ({ + ...prev, + left: Math.round(pos.x), + top: Math.round(pos.y) + })); + } + } catch { } + }, 10); + }; + + viewerEl.addEventListener('mousedown', onMouseDown, false); + viewerEl.addEventListener('mouseup', onMouseUp, false); + return () => { + viewerEl.removeEventListener('mousedown', onMouseDown, false); + viewerEl.removeEventListener('mouseup', onMouseUp, false); + }; + }, [viewerHost, isSmartEditor, editorRef]); + + useEffect(() => { + if (!genVisible) return; + + const isInside = (el, target) => { + if (!el || !target) return false; + if (el.contains(target)) return true; + const path = typeof target.composedPath === 'function' ? target.composedPath() : []; + return path.includes(el); + }; + + const onOutsidePress = (e) => { + try { + const dlgEl = genDialogRef.current?.element; + const settingsEl = document.querySelector('.ai-settings-menu'); + + const inDialog = isInside(dlgEl, e.target); + const inSettings = isInside(settingsEl, e.target); + + if (!inDialog && !inSettings) { + setGenVisible(false); + } + } catch { } + }; + + document.addEventListener('pointerdown', onOutsidePress, true); + document.addEventListener('touchstart', onOutsidePress, { capture: true, passive: true }); + document.addEventListener('mousedown', onOutsidePress, true); + return () => { + document.removeEventListener('pointerdown', onOutsidePress, true); + document.removeEventListener('touchstart', onOutsidePress, true); + document.removeEventListener('mousedown', onOutsidePress, true); + }; + }, [genVisible]); + + function openMenuBesideButton(btnRef, cmRef) { + if (!btnRef || !cmRef?.current?.open) return; + const rect = btnRef.element.getBoundingClientRect(); + const y = rect.bottom; + const x = rect.left; + cmRef.current.open(Math.round(y), Math.round(x)); + } + + const TextBoxCreated = () => { + const inst = textboxRef.current; + if (!inst) return; + const isDisabled = userPrompt?.trim() ? '' : 'e-disabled' + const className = 'e-icons e-send ' + isDisabled; + inst.addIcon('append', className); + const wrapper = inst.element?.parentElement; + const icon = wrapper?.querySelector('.e-input-group-icon.e-send'); + if (icon) { + icon.setAttribute('title', 'Generate'); + icon.setAttribute('role', 'button'); + icon.setAttribute('aria-label', 'Generate'); + icon.addEventListener('click', onSend); + } + return; + } + + const textboxValueChange = (e) => { + const value = e.value; + setUserPrompt(value); + window?.toggleSendIcon?.(value.length > 0); + } + + const htmlToPlain = (html) => + (html || '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim(); + + const getDE = () => editorRef?.current?.documentEditor || null; + + const getOffsets = () => { + const ed = getDE(); + const sel = ed?.selection; + try { + const start = sel.startOffset; + const end = sel?.endOffset; + return { start, end }; + } catch { + return { start: null, end: null }; + } + }; + + const selectOffsets = (start, end) => { + const sel = getDE()?.selection; + if (sel?.select && start != null && end != null) sel.select(start, end); + }; + + const onKeepGenerated = () => { + const ed = getDE(); + const { end } = draftRange || {}; + if (ed && end != null) { + selectOffsets(end, end); + } + setDraftRange({ start: null, end: null }); + setGenVisible(false); + }; + + const onDiscardGenerated = () => { + const ed = getDE(); + const { start, end } = draftRange || {}; + if (ed && start != null && end != null) { + selectOffsets(start, end); + ed.editor.delete(); + } + setDraftRange({ start: null, end: null }); + setGenVisible(false); + }; + + const openStopDialog = () => { + setGenVisible(false); + const pos = window?.getGeneratingDraftPosition?.() || { x: 200, y: 160 }; + setStopPos({ x: Math.round(pos.x + 24), y: Math.round(pos.y) }); + setStopVisible(true); + }; + + const closeStopDialog = () => { + setGenVisible(true); + setStopVisible(false); + } + + const stopContentTemplate = () => ( +
+ + Generating a draft... +
+ ); + + useEffect(() => { + if (!isAIEnabled) { + setFabAssistVisible(false); + setFabChatVisible(false); + } else { + setFabAssistVisible(isAIEnabled); + setFabChatVisible(isAIEnabled); + } + }, [isAIEnabled]); + + const shouldTick = (id) => { + const key = (id || '').toLowerCase(); + const toneKey = (tone || '').toLowerCase(); + const formatKey = (format || '').toLowerCase().replace(/\s+/g, '-'); + const lengthKey = (length || '').toLowerCase(); + const isChild = key.includes('child'); + const match = key.includes(toneKey) || key.includes(formatKey) || key.includes(lengthKey); + return isChild && match; + }; + + const onSettingsBeforeItemRender = (args) => { + const id = args.item?.id || ''; + const li = args.element; + const hasIcon = !!li.querySelector('.e-menu-icon'); + if (shouldTick(id)) { + if (!hasIcon) { + const icon = document.createElement('span'); + icon.className = 'e-menu-icon e-icons e-check'; + li.insertBefore(icon, li.firstChild); + li.className = li.className + ' e-selected'; + } else { + li.querySelector('.e-menu-icon').className = 'e-menu-icon e-icons e-check'; + } + } + }; + + const floatFocus = (args) => { + args.target.parentElement.classList.add("e-input-focus"); + }; + + const floatBlur = (args) => { + args.target.parentElement.classList.remove('e-input-focus'); + }; + + return ( +
+
+
+ { e.preventDefault(); e.stopPropagation(); }} + onClick={openAssistMenu} + style={{ + position: 'absolute', + left: `${assistBtn.left}px`, + top: `${assistBtn.top}px`, + width: `${assistBtn.width}px`, + height: `${assistBtn.height}px` + }} + /> + + + +
+
+
+
+ { + if (e.key === 'Enter' && userPrompt?.trim()) { + e.preventDefault(); + onSend(); + } + }} + /> +
+ { args.cancel = true; openGearMenu(); }} + /> +
+
+
+
+ + setSmartVisible(false)} + cssClass="e-smart-editor-dialog"> +
+ + + +
+
+ +
+
+
+ } /> + +
+
+ + {popupType === AiTask.Translate && ( + + )} + {popupType === AiTask.Grammar && ( + setChecks(e.value || [])} + mode="CheckBox" + showSelectAll={true} + showDropDownIcon={true} + allowFiltering={true} + placeholder="e.g. Spelling Errors" + width="180px" + popupHeight="260px" + > + + + )} +
+
+
+ } /> + + +
+
+ + + + + + + + +
+ ); +} diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..1728e0d --- /dev/null +++ b/src/App.css @@ -0,0 +1,650 @@ +body { + overflow: hidden !important; + margin: 8px 8px 0 8px !important; +} + +body .col-lg-9 { + width: 100% !important; +} + +#documenteditor_titlebar { + height: 36px; + line-height: 26px; + padding-left: 15px; + padding-right: 10px; + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; +} + +#documenteditor_title_contentEditor { + height: 26px; + max-width: 85%; + width: auto; + overflow: hidden; + display: inline-block; + padding-left: 4px; + padding-right: 4px; + margin: 5px; +} + +.single-line { + cursor: text !important; + outline: none; +} + +.single-line:hover { + border-color: #e4e4e4 !important; +} + +[contenteditable="true"].single-line { + white-space: nowrap; + border-color: #e4e4e4 !important; +} + +[contenteditable="true"].single-line { + white-space: nowrap; + border-color: #e4e4e4 !important; +} + +[contenteditable="true"].single-line * { + white-space: nowrap; +} + +@font-face { + font-family: 'Sample brower icons'; + src: url(data:application/x-font-ttf;charset=utf-8;base64,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bwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAHUAcwBpAG4AZwAgAFMAeQBuAGMAZgB1AHMAaQBvAG4AIABNAGUAdAByAG8AIABTAHQAdQBkAGkAbwB3AHcAdwAuAHMAeQBuAGMAZgB1AHMAaQBvAG4ALgBjAG8AbQAAAAACAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMBAgEDAQQBBQEGAQcBCAEJAQoBCwEMAQ0BDgEPARABEQESARMBFAEVARYBFwEYARkBGgEbARwBHQEeAR8BIAEhASIBIwEkASUBJgEnASgBKQEqASsBLAEtAS4BLwEwATEBMgEzATQBNQE2ATcBOAE5AToBOwE8AT0BPgE/AUABQQFCAUMBRAALU3Ryb2tlU3R5bGUIQm9va21hcmsHUGljdHVyZQRGaW5kDU91dHNpZGVCb3JkZXIHSnVzdGlmeQVDbG9zZQ5EZWNyZWFzZUluZGVudBVQaXhlbEFsaWduQ2VudGVyVGFibGUPQmFja2dyb3VuZENvbG9yC0FsaWduQm90dG9tCVBhZ2VTZXR1cA5IaWdobGlnaHRDb2xvcgtTdXBlcnNjcmlwdAVUYWJsZQRVbmRvC0luc2VydEJlbG93CVRvcEJvcmRlcgpQYWdlTnVtYmVyEEFsaWduQ2VudGVyVGFibGUOSW5jcmVhc2VJbmRlbnQEQm9sZAlBbGlnbkxlZnQGRm9vdGVyC0luc2VydFJpZ2h0CVVuZGVybGluZQpJbnNlcnRMZWZ0BExvY2sGSGVhZGVyDVN0cmlrZXRocm91Z2gIQ2xlYXJBbGwLUmlnaHRCb3JkZXIKQWxpZ25SaWdodARPcGVuClN0cm9rZVNpemUFUHJpbnQLRGVsZXRlVGFibGUJRm9udENvbG9yDUluc2lkZUJvcmRlcnMKRGVsZXRlUm93cwhEb3dubG9hZAtMaW5lU3BhY2luZxRJbnNpZGVWZXJ0aWNhbEJvcmRlcghBbGlnblRPcARSZWRvDEJvdHRvbUJvcmRlcgNOZXcFUGFzdGUHQnVsbGV0cwRDZWxsDURlbGV0ZUNvbHVtbnMKQWxsQm9yZGVycwlTdWJzY3JpcHQQU2hvd0hpZGVQcm9wZXJ0eQ5UYWJsZU9mQ29udGVudAZJdGFsaWMWSW5zaWRlSG9yaXpvbmRhbGJvcmRlcgtMZWZ0Qm9yZGVycwlOdW1iZXJpbmcETGluawtBbGlnbkNlbnRlcgtJbnNlcnRBYm92ZQZCcmVha3MITmV4dFBhZ2USU2VsZWN0ZnJvbUNvbXB1dGVyCVBhZ2VCcmVhawAAAAA=) format('truetype'); + font-weight: normal; + font-style: normal; +} + +[class^="e-de-icon-"], +[class*=" e-de-icon-"] { + font-family: 'Sample brower icons' !important; +} + +.e-de-icon-Print:before { + content: "\e723"; +} + +.e-de-icon-Download:before { + content: "\e70a"; +} + +#documenteditor_titlebar #de-print, +#documenteditor_titlebar #documenteditor-share { + font-size: 12px !important; +} + +.content-wrapper { + margin: 10px; +} + +.center-vertical { + min-height: calc(100vh - 20px); + flex-direction: column; +} + +.uploader-page { + margin: 100px 250px; +} + +.upload-wrapper, +.uploader-page-header, +.create-new-button-section { + text-align: center; + max-width: 100%; +} + +.upload-wrapper .e-file-select-wrap { + padding: 48px 28px !important; +} + +.upload-wrapper .e-file-select-wrap .e-btn { + background: #fff; + border: 1px solid #D3D3D3; + color: #000; + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-size: 16px !important; + height: 48px; + width: 160px; + border-radius: 10px; + font-weight: 600; + padding: 12px 20px; + line-height: 16px; +} + +.upload-wrapper .e-upload { + border: 2px dashed #0F6CBD; + border-radius: 16px; +} + +.uploader-page-header { + font-family: Segoe UI; + font-size: 28px; + line-height: 36px; + margin-bottom: 24px; +} + +.e-upload .e-file-drop { + color: #6b7280; + font-size: 18px !important; + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + display: block; + margin-top: 14px; + margin-left: 0px !important; +} + +.create-new-button { + border-radius: 12px; + border: none; + font-size: 18px !important; + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + height: 52px; + min-height: 48px; + width: 262px; + line-height: 54px; + padding: 14px 28px; +} + +.create-new-button-section { + margin-top: 28px; +} + +.create-new-button-section .e-new-icons.e-icons { + line-height: 28px; + margin-top: 0; +} + + +.e-back-button { + width: 32px !important; + height: 32px !important; +} + +.e-new-icons, +.e-new-icons::before { + width: 28px !important; + height: 28px !important; +} + +.e-new-icons::before { + line-height: 26px; +} + +.create-new-button, +.create-new-button-section { + align-items: center; +} + +.e-back-button { + border: none; +} + +.center-vertical, +.create-new-button, +.create-new-button-section { + display: flex; +} + +.back-button-container { + display: inline-block; +} + +.e-back-button { + font-size: 12px; +} + +.e-new-icons { + font-size: 14px !important; + font-weight: 900 !important; +} + +.e-new-icons, +.create-new-button { + font-weight: 400; +} + +.uploader-page-header { + font-weight: 600; +} + +.center-vertical, +.create-new-button-section { + justify-content: center; +} + +.e-back-button { + border-radius: 50%; + margin-right: 12px; +} + +/* Ensure we target only this uploader instance */ +.e-upload.e-control-wrapper .e-file-drop::after { + content: "Supported formats: DOCX, DOC and RTF."; + display: block; + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + margin-top: 14px; + font-size: 14px; + color: #7a7f88; + /* tweak color as needed */ +} + +/* Optional: center-align if your theme differs */ +.e-upload.e-control-wrapper .e-file-drop, +.e-upload.e-control-wrapper .e-file-drop::after { + text-align: center; +} + +.e-fe-upload:before { + content: "\e712"; +} + +.e-fe-upload { + margin-top: -4px !important; + margin-right: 8px; + font-weight: 600 !important; +} + +.e-documenteditorcontainer .e-de-status-bar .e-de-pagenumber-input { + color: rgb(73, 80, 87) !important; + font-family: inherit !important; + font-size: 14px !important; + text-align: center !important; + width: 22px !important; + background: rgb(255, 255, 255) !important; + border: 1px solid rgb(222, 226, 230) !important; + border-radius: 2px !important; + padding: 0px !important; + height: 100% !important; +} + +.e-documenteditorcontainer .e-de-status-bar .e-btn-pageweb-spellcheck { + margin-left: calc(100% - 400px) !important; +} + +.e-ribbon .help-pane-content { + color: #0F172AE0; + display: flex; + align-items: center; + font-size: 14px; + font-weight: 500; +} + +.e-ribbon .help-pane-content .syncfusion-logo { + margin-right: 10px; +} + +.e-ribbon .help-pane-content a, +.e-ribbon .help-pane-content a:hover { + text-decoration: none; +} + +.help-pane-content a, +.help-pane-content a:visited, +.help-pane-content a:hover, +.help-pane-content a:active { + color: #0d6efd; +} + +.footer { + position: fixed; + left: 8px; + right: 8px; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + background: linear-gradient(90.4deg, #C5D9FF 0.03%, #E8F0FF 11.56%, #E8F0FF 85.56%, #C5D9FF 99.97%); + border: 1px solid #D8DEE9; + z-index: 100; + height: 74px; +} + +.footer-container { + display: flex; + justify-content: center; + align-items: center; + gap: 16px; +} + +.docx-icon { + display: flex; + align-items: center; + gap: 10px; + flex: 0 0 auto; + width: 40px; + height: 40px; +} + +.footer-content { + display: flex; + align-items: center; + gap: 40px; +} + + +.footer-content .title span { + color: #000; + font-weight: 400; + font-size: 16px; + font-family: 'Open Sans'; +} + +.footer-content .title span .main-title { + font-weight: 700; + font-size: 16px; + font-family: 'Open Sans'; +} + +.footer-content .buttons { + display: flex; + align-items: center; + gap: 8px; +} + +.footer-content .e-trial-btn::before { + content: 'Start Free Trial'; +} + +.footer-content .e-trial-btn { + display: flex; + align-items: center; + gap: 10px; + background: #0057FF; + border-radius: 8px; + padding: 10px 16px; + font-size: 14px; + font-weight: 700; + font-family: 'Open Sans'; + height: 40px; + box-shadow: none; +} + +.footer-content .e-demo-btn { + display: flex; + align-items: center; + background-color: transparent; + border: transparent; + color: #0057FF; + border-radius: 6px; + gap: 10px; + padding: 10px 16px; + font-size: 14px; + font-weight: 700; + font-family: 'Open Sans'; + height: 40px; + box-shadow: none; +} + +.footer-content .e-demo-btn:hover { + background-color: #D4E2FF; + color: #0057FF; +} + +.footer-content .e-trial-btn::after { + content: "\e7f9"; + font-family: 'e-icons'; +} + +.footer-content .e-primary:hover { + background: #0050EB; +} + +#documenteditor_titlebar #de-print::after { + content: 'Print'; +} + +#documenteditor_titlebar #documenteditor-share::after { + content: 'Download'; +} + + +@media (max-width: 1140px) { + .footer-content .title { + display: flex; + flex-direction: column; + } + + .footer-content .title span, + .footer-content .title span .main-title { + font-size: 14px; + } +} + +/* To hide the footer content: "Try our DOCX Editor SDK — create, edit, and collaborate!" */ +@media (max-width: 900px) { + + .e-ribbon .help-pane-content, + .footer-content .title span:last-child { + display: none; + } +} + +/* To hide the "Request Demo" button */ +@media (max-width: 730px) { + .footer-content .buttons .e-demo-btn { + display: none; + } + + .footer-content .buttons .e-trial-btn::before { + content: 'Free Trial'; + } + + .footer-content .buttons .e-trial-btn::after { + content: none; + } +} + +@media (max-width: 767px) { + .uploader-page { + margin: 0 0 50px 0 !important; + } +} + +@media (max-width: 600px) { + .ai-generate-dialog.ai-assist-dialog { + width: 300px !important; + } + + #documenteditor_titlebar #de-print::after, + #documenteditor_titlebar #documenteditor-share::after { + content: ''; + } + + #documenteditor_titlebar #de-print, + #documenteditor_titlebar #documenteditor-share { + padding: 4px 8px; + } + + .e-fab.ai-assist-btn.e-hide, + .document-editor-container.e-hide { + display: none !important; + } + + .document-editor-container { + float: none !important; + } + + .ai-chat-container { + float: none !important; + width: 100% !important; + } + + .ai-chat-container .e-aiassist-chat { + border-left: 1px solid #ced4da !important; + } + + .e-control.e-dialog.e-smart-editor-dialog { + width: 90% !important; + } + + .e-smart-editor-dialog .e-dlg-header-content .e-toolbar-item.e-tbtn-align { + max-width: 32px !important; + } + + .e-smart-editor-dialog .e-dlg-header-content .e-toolbar-item { + padding: 0 !important; + } + + .e-smart-editor-dialog .e-header-toolbar { + height: 38px !important; + } +} + + +.e-ribbon-group-overflow-ddb .e-ribbon-overflow-target .e-ribbon-overflow-header { + font-size: 12px !important; +} + +.e-ribbon-gallery-popup.e-popup .e-ribbon-gallery-header, +.e-ribbon-gallery-dropdown.e-popup .e-ribbon-gallery-header, +.e-ribbon-group-overflow-ddb .e-ribbon-overflow-target .e-ribbon-overflow-header { + font-weight: 600 !important; +} + +.e-ribbon-gallery-popup.e-popup .e-ribbon-gallery-container .e-ribbon-gallery-item, +.e-ribbon-gallery-dropdown.e-popup .e-ribbon-gallery-container .e-ribbon-gallery-item { + width: 96px !important; +} + +.e-ribbon.e-rbn .e-ribbon-item .e-ribbon-gallery-button { + border-style: outset !important; +} + +.e-switch-wrapper .e-switch-handle { + background-color: #21252940 !important; +} + +.e-switch-wrapper .e-switch-handle.e-switch-active { + background-color: #0D6EFD !important; +} + +#documenteditor_titlebar .e-switch-wrapper.titlebar-ai-switch .e-switch-inner.e-switch-active .e-switch-on, +#documenteditor_titlebar .e-switch-wrapper.titlebar-ai-switch .e-switch-inner .e-switch-off { + background-color: #fff !important; +} + +.e-aiassist-chat .e-view-header .e-tbar-btn-text { + font-weight: 600; + line-height: 20px; +} + +.e-aiassist-chat .e-aiassist-chat-icon::before, +.e-aiassist-chat .e-aiassist-page-icon::before { + content: '\e7b4'; +} + +.ai-chat-container .e-aiassist-chat { + border-left: 0; +} + +.e-aiassist-chat .e-view-content .ai-assist-banner { + display: flex; + flex-direction: column; + height: 348px; + align-items: center; + justify-content: center; +} + +.e-aiassist-chat .e-aiassist-page-icon { + color: #616161; + width: 40px; + height: 40px; + background-color: #e6e6e6; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +} + +.e-aiassist-chat .e-aiassist-page-header { + font-size: 12px; + margin: 12px; + line-height: 16px; +} + +.e-aiassist-chat .e-suggestions { + margin: 10px 0 0; +} + +.e-aiassist-chat .e-suggestions li { + height: 36px; + font-weight: 400 !important; + line-height: 18px !important; + padding: 8px 12px !important; + margin-bottom: 12px !important; +} + +.e-aiassist-chat .e-view-header .e-icons.e-aiassist-chat-header { + display: none; +} + +.e-aiassist-chat .e-view-header .e-icons.e-close, +.e-aiassist-chat .e-aiassist-page-icon::before, +.e-aiassist-chat .e-aiassist-page-header { + font-weight: 400; +} + +.e-aiassist-chat .e-footer, +.e-aiassist-chat .e-views .e-view-container { + width: 96% !important; +} + + +.e-aiassist-chat .e-toolbar .e-close { + width: 30px; + font-weight: 400; + font-size: 15px; + text-align: center; +} + + + + + +.e-bigger .e-aiassist-chat .e-view-header .e-icons.e-close { + font-size: 20px !important; +} + +.e-bigger .e-aiassist-chat .e-view-header .e-tbar-btn-text, +.e-aiassist-chat .e-view-header .e-icons.e-close, +.e-aiassist-chat .e-aiassist-page-icon::before, +.e-bigger .e-aiassist-chat .e-suggestions li { + font-size: 16px !important; +} + +.e-aiassist-chat .e-view-header .e-tbar-btn-text, +.e-aiassist-chat .e-suggestions li, +.e-bigger .e-aiassist-chat .e-aiassist-page-header { + font-size: 14px !important; +} + +.e-bigger .e-aiassist-chat .e-aiassist-page-icon::before { + height: 22px; + width: 22px; + font-size: 22px; +} + +.e-bigger .e-aiassist-chat .e-aiassist-page-icon { + width: 56px; + height: 56px; +} + +.e-bigger .e-aiassist-chat .e-aiassist-page-header { + line-height: 20px; +} + +.e-bigger .e-aiassist-chat .e-suggestions li { + height: 40px; + line-height: 20px !important; + padding: 9px 16px !important; +} + +.e-control.e-dialog { + max-height: 95%; +} + +.e-ribbon-menu.e-menu-wrapper ul .e-menu-item, +#documenteditor-share-popup ul .e-item, +.e-smart-editor-dialog .e-original-word p { + font-size: inherit !important; +} \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..68788b3 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,401 @@ +import React, { useRef, useEffect, useState, useCallback } from 'react'; +import { DocumentEditorContainerComponent, Ribbon } from '@syncfusion/ej2-react-documenteditor'; +import { TitleBar } from './title-bar.jsx'; +import { UploaderComponent } from '@syncfusion/ej2-react-inputs'; +import { ButtonComponent } from '@syncfusion/ej2-react-buttons'; +import { AIAssistViewComponent, ViewsDirective, ViewDirective } from '@syncfusion/ej2-react-interactive-chat'; +import { getDocumentText } from './summarizer'; +import AIPopup from './AIPopup.jsx'; +import './editor-helpers.js'; +import './App.css'; +import { L10n } from "@syncfusion/ej2-base"; +import { getAzureChatAIRequest } from './ai-models'; + +DocumentEditorContainerComponent.Inject(Ribbon); + +L10n.load({ + 'en-US': { + uploader: { + dropFilesHint: "or drop file here" + } + } +}); + +const SERVICE_URL = 'https://document.syncfusion.com/web-services/docx-editor/api/documenteditor/'; +const UPLOADER_SAVE_URL = 'https://services.syncfusion.com/react/production/api/FileUploader/Save'; +const UPLOADER_REMOVE_URL = 'https://services.syncfusion.com/react/production/api/FileUploader/Remove'; + +export const App = () => { + let container = useRef(null); + const uploaderRef = useRef(null); + let titleBar; + let documentName = "New Document"; + const assistInstance = useRef(null); + const [initialized, setInitialized] = useState(false); + const [openChat, setOpenChat] = useState(false); + const [isAIEnabled, setIsAIEnabled] = useState(false); + const [assistBtnPos, setAssistBtnPos] = useState({ + left: 80, + top: 160, + width: 24, + height: 24 + }); + const [aiSuggestions, setAiSuggestions] = useState(['Summarize this document']); + + const onZoomFactorChange = useCallback(() => { + const editor = container.current?.documentEditor; + if (!editor) return; + setTimeout(() => { + editor.focusIn(); + const zoom = editor.zoomFactor; + const pos = window.getAIAssistBtnPosition?.(); + if (pos) { + setAssistBtnPos({ + left: Math.round(pos.x), + top: Math.round(pos.y), + width: Math.round(24 * zoom), + height: Math.round(24 * zoom) + }); + } + window.setAiAssistBtnIconSize?.(Math.round(14 * zoom)); + }, 10); + }, []); + + useEffect(() => { + container.current?.documentEditor?.resize?.(); + container.current?.ribbon?.ribbon?.refreshLayout?.(); + onZoomFactorChange(); + }, [openChat, onZoomFactorChange]); + + useEffect(() => { + if (!isAIEnabled) { + setOpenChat(false); + } + }, [isAIEnabled]); + + const onLoadDefault = () => { + const editor = container.current?.documentEditor; + if (editor) { + editor.documentName = documentName; + if (titleBar) { + titleBar.updateDocumentTitle(); + } + container.current.documentChange = () => { + if (titleBar) { + titleBar.updateDocumentTitle(); + } + editor.focusIn(); + }; + } + }; + + const openNewDocument = useCallback(() => { + setInitialized(true); + documentName = "New Document"; + }, []); + + const onContainerCreated = useCallback(() => { + const editor = container.current?.documentEditor; + if (!editor) return; + try { editor.focusIn(); } catch { } + setTimeout(() => { + try { + const pos = window?.getAIAssistBtnPosition?.(); + if (!pos) return; + setAssistBtnPos(prev => ({ + ...prev, + left: Math.round(pos.x), + top: Math.round(pos.y), + width: 24, + height: 24 + })); + } catch { } + }, 10); + window.onbeforeunload = function () { + return "Want to save your changes?"; + }; + editor.zoomFactorChange = () => { + onZoomFactorChange(); + }; + editor.pageOutline = "#E0E0E0"; + editor.acceptTab = true; + container.current.documentEditorSettings.showRuler = true; + editor.resize(); + titleBar = new TitleBar( + document.getElementById("documenteditor_titlebar"), + editor, + true, + null, + goBackToUploadPage, + (checked) => { + setIsAIEnabled(checked); + } + ); + onLoadDefault(); + let spellChecker = editor.spellChecker; + spellChecker.languageID = 1033; + spellChecker.removeUnderline = false; + spellChecker.allowSpellCheckAndSuggestion = true; + window.addEventListener('resize', onResize); + }, []); + + useEffect(() => { + return () => window.removeEventListener('resize', onResize); + }, []); + + const onResize = () => { + onZoomFactorChange(); + } + + const goBackToUploadPage = useCallback(() => { + const editor = container.current?.documentEditor; + try { + if (editor) { + editor.showRevisions = false; + editor.destroy(); + setIsAIEnabled(false); + } + } catch { } + setInitialized(false); + }, []); + + const onFileSelected = useCallback(async (args) => { + const files = args.filesData || []; + if (!files.length) return; + const file = files[0]?.rawFile; + if (!file) return; + setInitialized(true); + documentName = files[0].name ? files[0].name.split("." + files[0].type)[0] : 'Untitled Document'; + try { + const formData = new FormData(); + formData.append('files', file, file.name); + const response = await fetch(`${SERVICE_URL}Import`, { + method: 'POST', + body: formData + }); + if (!response.ok) { + throw new Error(`Import failed: ${response.statusText}`); + } + const sfdt = await response.text(); + setTimeout(() => { + const editor = container.current?.documentEditor; + if (editor) { + editor.open(sfdt); + } + }, 0); + } catch (err) { + alert('Failed to import and open the document.'); + } + }, []); + + const onUploaderCreated = () => { + const browseBtn = document.querySelector('.e-file-select-wrap .e-btn'); + if (browseBtn && !browseBtn.querySelector('.e-btn-icon')) { + const icon = document.createElement('span'); + icon.className = 'e-btn-icon e-icons e-fe-upload'; + browseBtn.insertBefore(icon, browseBtn.firstChild); + } + }; + + const showChatPane = () => { + setOpenChat(true); + setAiSuggestions(['Summarize this document']); + } + + const chatPanelCreated = () => { + assistInstance.current.toolbarSettings.itemClicked = (args) => { + const itemClass = String(args?.item?.iconCss || ''); + if (itemClass.includes('e-close')) { + setOpenChat(false); + document.querySelector('.e-fab.ai-assist-btn')?.classList.remove('e-hide'); + document.querySelector('.document-editor-container')?.classList.remove('e-hide'); + } + }; + setAiSuggestions(['Summarize this document']); + } + + return ( +
+
+
+ {!initialized ? ( +
+
+ +
+
+ + Create New Document + +
+
+ ) : ( +
+
+
+
+
+
+ + +
+ {openChat && ( +
+ +
+
+
How can I help you?
+
+ } + promptRequest={async (args) => { + const prompt = String(args?.prompt || '').trim(); + if (!prompt) { args.response = ''; return; } + try { + if (prompt === 'Summarize this document') { + await new Promise(resolve => { + requestAnimationFrame(() => setTimeout(resolve, 10)); + }); + const documentContent = await getDocumentText(container); + const options = { + messages: [ + { role: "system", content: `You are a helpful assistant. Your task is to analyze the provided text and generate short summary. Always respond in proper HTML format, but do not include , , or tags.` }, + { role: "user", content: documentContent } + ], + model: "gpt-4", + }; + const summaryHtml = await getAzureChatAIRequest(options); + args.response = summaryHtml || '

No summary available.

'; + assistInstance.current.addPromptResponse(args.response); + const suggestionsRaw = await getAzureChatAIRequest({ + messages: [ + { role: "system", content: `You are a helpful assistant. Your task is to analyze the provided text and generate 3 short diverse questions and each question should not exceed 10 words` }, + { role: "user", content: documentContent } + ], + model: "gpt-4", + }); + if (suggestionsRaw) { + const next = (suggestionsRaw.split(/\d+\.\s*/).filter(x => x.trim() !== "")).map((text, index) => { + return `${index + 1}. ${text.trim()}`; + }); + if (next.length) setAiSuggestions(next); + } + } else { + + await new Promise(resolve => { + requestAnimationFrame(() => setTimeout(resolve, 10)); + }); + + const options = { + messages: [ + { role: "system", content: `You are a helpful assistant. Use the provided context to answer the user question. Always respond in proper HTML format, but do not include , , or tags. Context:` }, + { role: "user", content: prompt } + ], + model: "gpt-4", + }; + const answerHtml = await getAzureChatAIRequest(options); + var response = String(answerHtml ?? '

No answer.

'); + args.response = response; + assistInstance.current.addPromptResponse(response); + } + } catch (e) { + args.response = `

AI error: ${e?.message || e}

`; + } + }} + responseToolbarSettings={{ + items: [ + { iconCss: 'e-icons e-copy', tooltip: 'copy' }, + { iconCss: 'e-btn-icon e-de-ctnr-new', tooltip: 'insert' } + ], + itemClicked: async (e) => { + const idx = typeof e?.dataIndex === 'number' + ? e.dataIndex + : (assistInstance.current?.prompts?.length ?? 1) - 1; + const resHtml = assistInstance.current?.prompts?.[idx]?.response ?? ''; + if (!resHtml) return; + const tmp = document.createElement('div'); + tmp.innerHTML = resHtml; + const plainText = (tmp.innerText || '').trim(); + const tip = (e?.item?.tooltip || '').toLowerCase(); + if (tip === 'copy') { + if (navigator.clipboard && window.ClipboardItem) { + const blobHtml = new Blob([resHtml], { type: 'text/html' }); + const blobText = new Blob([plainText], { type: 'text/plain' }); + await navigator.clipboard.write([ + new ClipboardItem({ + 'text/html': blobHtml, + 'text/plain': blobText + }) + ]); + } else { + await navigator.clipboard?.writeText(plainText); + } + } else if (tip === 'insert') { + const editor = container?.current?.documentEditor?.editor; + if (editor && plainText) { + editor.insertText(plainText); + } + } + } + }} + > + + + +
+
+ )} +
+
+ )} +
+
+
+ ); +} + +export default App; \ No newline at end of file diff --git a/src/ai-models.ts b/src/ai-models.ts new file mode 100644 index 0000000..9c26d98 --- /dev/null +++ b/src/ai-models.ts @@ -0,0 +1,38 @@ +import { generateText } from "ai" +import { createGoogleGenerativeAI } from '@ai-sdk/google'; +import { createAzure } from '@ai-sdk/azure'; +import { createOpenAI } from '@ai-sdk/openai'; + +const google = createGoogleGenerativeAI({ + baseURL: "https://generativelanguage.googleapis.com/v1beta", + apiKey: "API_KEY" +}); +const azure = createAzure({ + resourceName: 'RESOURCE_NAME', + apiKey: 'API_KEY', +}); +const groq = createOpenAI({ + baseURL: 'https://api.groq.com/openai/v1', + apiKey: 'API_KEY', +}); + +const aiModel = azure('MODEL_NAME'); // Update the model here + +export async function getAzureChatAIRequest(options: any) { + try { + const result = await generateText({ + model: aiModel, + messages: options.messages, + topP: options.topP, + temperature: options.temperature, + maxOutputTokens: options.maxTokens, + frequencyPenalty: options.frequencyPenalty, + presencePenalty: options.presencePenalty, + stopSequences: options.stopSequences + }); + return result.text; + } catch (err) { + console.error("Error occurred:", err); + return null; + } +} diff --git a/src/editor-helpers.js b/src/editor-helpers.js new file mode 100644 index 0000000..45dfb78 --- /dev/null +++ b/src/editor-helpers.js @@ -0,0 +1,207 @@ +window.getAIAssistBtnPosition = function () { + var documnetEditor = document.querySelector(".control-section"); + if (!documnetEditor) { + return; + } + var documnetEditorRect = documnetEditor.getBoundingClientRect(); + var documnetEditorTop = documnetEditorRect.top; + var documnetEditorLeft = documnetEditorRect.left; + var viewerContainer = document.querySelector("#document-editor_editor_viewerContainer"); + if (!viewerContainer) { + return; + } + var viewerContainerRect = viewerContainer.getBoundingClientRect(); + var viewerContainerTop = viewerContainerRect.top; + var viewerContainerLeft = viewerContainerRect.left; + var cursor = document.querySelector('.e-de-blink-cursor'); + if (!cursor) { + return; + } + var cursorRect = cursor.getBoundingClientRect(); + var cursorTop = cursor.style.display === 'none' ? parseInt(cursor.style.top.split("px")[0], 10) : cursorRect.top; + var viewercontainer = document.querySelector("#document-editor_editor_viewerContainer .e-de-hRuler .e-de-hRuler"); + if (!viewercontainer) { + return; + } + var rulerLeft = parseInt(viewercontainer.style.marginLeft.split("px")[0], 10); + var viewercontainerWidth = document.querySelector(".e-de-hRuler .e-de-hRuler").offsetWidth; + if (!viewercontainerWidth && viewercontainerWidth !== 0) { + return; + } + var AiButtonPosition = viewercontainerWidth / 20; + var markIndicator = document.querySelector("#document-editor_editor_markIndicator"); + var vRuleIndicator = document.querySelector("#document-editor_editor_vRulerBottom"); + if (!markIndicator || !vRuleIndicator) { + return; + } + var markIndicatorRect = markIndicator.getBoundingClientRect().top; + var vRuleIndicatorRect = vRuleIndicator.getBoundingClientRect().top; + var scrollDifference = markIndicatorRect - vRuleIndicatorRect; + + var y = cursor.style.display === 'none' ? ((viewerContainerTop - documnetEditorTop) + cursorTop) - scrollDifference : (cursorTop - documnetEditorTop); + var x = (viewerContainerLeft - documnetEditorLeft) + rulerLeft + AiButtonPosition; + return { x: x, y: y }; +}; + +window.getAIChatBtnPosition = function () { + var documnetEditor = document.querySelector("#document-editor"); + if (!documnetEditor) { + return; + } + var documnetEditorRect = documnetEditor.getBoundingClientRect(); + var documnetEditorHeight = documnetEditorRect.height; + var documnetEditorWidth = documnetEditorRect.width; + var x = documnetEditorWidth - 87; + var y = documnetEditorHeight - 81; + return { x: x, y: y }; +}; + +window.setAiAssistBtnPosition = function (x, y) { + var el = document.getElementsByClassName('ai-chat-btn')[0]; + if (!el) return; + el.style.position = 'absolute'; + el.style.left = x + 'px'; + el.style.top = y + 'px'; +}; +window.getAIAssistPopupPosition = function () { + var aiButton = document.getElementsByClassName('ai-assist-btn')[0]; + if (!aiButton) return { x: 200, y: 160 }; + var bRect = aiButton.getBoundingClientRect(); + var sampleMargin = 8; + return { x: bRect.left - sampleMargin, y: (bRect.top + bRect.height) - sampleMargin }; +}; + +window.setDialogDivHeight = (mode) => { + var q = document.getElementById('e-de-qus-pane'); + var ans = document.getElementById('e-de-editableDiv'); + if (!ans) return; + if (mode === 'Generate') ans.style.height = '100px'; + else { if (q) q.style.height = '75px'; ans.style.height = '75px'; } +}; +window.getTextContent = () => { + var el = document.getElementById('e-de-editableDiv'); + return el ? (el.textContent || '').trim() : ''; +}; +window.getInputContent = () => { + var el = document.getElementById('e-de-editableDiv'); + return el ? (el.value || '').trim() : ''; +}; +window.getHtmlContent = () => { + var el = document.getElementById('e-de-editableDiv'); + return el ? el.innerHTML : ''; +}; +window.setTextContent = (text) => { + var el = document.getElementById('e-de-editableDiv'); + if (el) el.textContent = text || ''; +}; +window.setHtmlContent = (html) => { + var el = document.getElementById('e-de-editableDiv'); + if (el) el.innerHTML = html || ''; +}; +window.clearDivContent = () => { + var el = document.getElementById('e-de-editableDiv'); + if (el) el.innerHTML = ''; +}; +window.setPlaceholder = (placeholderText) => { + var el = document.getElementById('e-de-editableDiv'); + if (el && (el.innerText || '').trim() === '') { + el.innerText = placeholderText || ''; + el.classList.add('placeHoldr'); + } +}; +window.removePlaceholder = (placeholderText) => { + var el = document.getElementById('e-de-editableDiv'); + if (!el) return; + if (el.innerText === placeholderText) { + el.innerText = ''; + el.classList.remove('placeHoldr'); + } +}; + + +window.getAIButtonPosition = function () { + var aiButton = document.getElementsByClassName('e-control e-btn ai-assist-btn e-fab')[0]; + if (!aiButton) { + return; + } + var aiButtonRect = aiButton.getBoundingClientRect(); + var x = aiButtonRect.left; + var y = aiButtonRect.top; + return { x: x, y: y }; +} + +window.toggleSendIcon = function (isEnabled) { + const sendElement = document.querySelector(".ai-assist-dialog .e-icons.e-send"); + if (sendElement) { + if (isEnabled) { + sendElement.classList.remove('e-disabled'); + } else { + sendElement.classList.add('e-disabled'); + } + } +}; + + +window.getGeneratingDraftPosition = function () { + var aiButton = document.getElementsByClassName('e-control ai-assist-btn e-fab')[0]; + if (!aiButton) { + return; + } + var aiButtonRect = aiButton.getBoundingClientRect(); + var aiButtonLeft = aiButtonRect.left; + var aiButtonTop = aiButtonRect.top; + var documnetEditor = document.querySelector(".control-section"); + if (!documnetEditor) { + return; + } + var documnetEditorRect = documnetEditor.getBoundingClientRect(); + var documnetEditorTop = documnetEditorRect.top; + var sampleMargin = 8; + var x = aiButtonLeft - sampleMargin; + var y = aiButtonTop - documnetEditorTop; + return { x: x, y: y }; +} + +window.setGeneratingDraftPosition = function (x, y) { + var element = document.getElementsByClassName('e-stop-generating-dialog')[0]; + if (element) { + element.style.position = 'absolute'; + element.style.left = x + 'px'; + element.style.top = y + 'px'; + } +}; +window.showGeneratingDraft = function (isShow) { + var stopPopupElement = document.querySelector('.e-stop-generating-dialog'); + if (stopPopupElement) { + if (isShow) { + stopPopupElement.style.display = "block"; + } + else { + stopPopupElement.style.display = "none"; + } + } +} + +window.setAIAssistBtnIconSize = function (AIAssistBtnIconSize) { + var iconElement = document.querySelector(".ai-assist-btn .e-icons.e-ai-assist-btn"); + if (iconElement) { + iconElement.style.fontSize = AIAssistBtnIconSize + "px"; + iconElement.style.height = AIAssistBtnIconSize + "px"; + iconElement.style.width = AIAssistBtnIconSize + "px"; + iconElement.style.lineHeight = (AIAssistBtnIconSize + 1) + "px"; + } +} + +window.getRegeneratePopupPosition = function () { + var statusBar = document.querySelector(".e-de-status-bar"); + if (!statusBar) { + return; + } + var statusBarRect = statusBar.getBoundingClientRect(); + var statusBarTop = statusBarRect.top; + var regeneratePopupHeight = 175; + var sampleMargin = 8; + var x = 130; + var y = (statusBarTop - regeneratePopupHeight) - sampleMargin; + return { x: x, y: y }; +} \ No newline at end of file diff --git a/src/helpers.js b/src/helpers.js new file mode 100644 index 0000000..5275489 --- /dev/null +++ b/src/helpers.js @@ -0,0 +1,86 @@ +export const AiTask = { + Generate: 'Generate', + Rephrase: 'Rephrase', + Translate: 'Translate', + Grammar: 'Grammar', +}; + +export function buildPrompt(task, text, { + tone = 'Professional', + format = 'Paragraph', + length = 'Medium', + fromLang = 'English', + toLang = 'French', + checks = [], + userHint = '', +} = {}) { + const safeText = text || ''; + switch (task) { + case AiTask.Generate: + return `generate ${format.toLowerCase()} in ${tone.toLowerCase()} tone, length ${length.toLowerCase()}: +${safeText} + +Return only an HTML fragment (use

,

,

  • etc. as needed). No explanations.`; + case AiTask.Rephrase: { + const extra = userHint?.trim() ? ` Target suggestion: ${userHint.trim()}` : ''; + return `rephrase to ${tone.toLowerCase()} tone, ${format.toLowerCase()} style, length ${length.toLowerCase()}:${extra} +${safeText} + +Return only an HTML fragment. No preface, no numbering.`; + } + case AiTask.Translate: + return `translate ${fromLang} to ${toLang}: +${safeText} + +Return only the translated HTML fragment. No other text.`; + case AiTask.Grammar: { + const filter = checks && checks.length > 0 ? ` focusing on: ${checks.join(', ')}.` : '.'; + return `fix grammar and clarity${filter} +${safeText} + +Return only the corrected HTML fragment. Do not explain the changes.`; + } + default: + return safeText; + } +} + +export function levenshtein(a, b) { + const dp = Array(a.length + 1).fill(0).map(() => Array(b.length + 1).fill(0)); + for (let i = 0; i <= a.length; i++) dp[i][0] = i; + for (let j = 0; j <= b.length; j++) dp[0][j] = j; + for (let i = 1; i <= a.length; i++) { + for (let j = 1; j <= b.length; j++) { + const cost = a[i - 1] === b[j - 1] ? 0 : 1; + dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost); + } + } + return dp[a.length][b.length]; +} +export function isSimilar(a, b, threshold = 1) { + if (!a || !b) return false; + return levenshtein(a, b) <= threshold; +} +export function highlightDifferences(original, modified) { + const oWords = (original || '').split(/\s+/); + const mWords = (modified || '').split(/\s+/); + const lcs = Array(oWords.length + 1).fill(0).map(() => Array(mWords.length + 1).fill(0)); + for (let i = 0; i < oWords.length; i++) { + for (let j = 0; j < mWords.length; j++) { + if (oWords[i] === mWords[j] || isSimilar(oWords[i], mWords[j])) lcs[i + 1][j + 1] = lcs[i][j] + 1; + else lcs[i + 1][j + 1] = Math.max(lcs[i + 1][j], lcs[i][j + 1]); + } + } + const unchanged = new Set(); + let x = oWords.length, y = mWords.length; + while (x > 0 && y > 0) { + if (oWords[x - 1] === mWords[y - 1] || isSimilar(oWords[x - 1], mWords[y - 1])) { unchanged.add(`${x - 1}|${y - 1}`); x--; y--; } + else if (lcs[x - 1][y] >= lcs[x][y - 1]) x--; + else y--; + } + const highlightedOriginal = oWords.map((w, i) => + [...unchanged].some(k => k.startsWith(`${i}|`)) ? w : `${w}`).join(' '); + const highlightedModified = mWords.map((w, j) => + [...unchanged].some(k => k.endsWith(`|${j}`)) ? w : `${w}`).join(' '); + return { highlightedOriginal, highlightedModified }; +} \ No newline at end of file diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..e69de29 diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..dd81ab6 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './App'; +import { registerLicense } from '@syncfusion/ej2-base'; + +// Registering Syncfusion license key +registerLicense('Replace your generated license key here'); + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); +root.render( + + + +); diff --git a/src/logo.svg b/src/logo.svg new file mode 100644 index 0000000..9dfc1c0 --- /dev/null +++ b/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts new file mode 100644 index 0000000..6431bc5 --- /dev/null +++ b/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/summarizer.js b/src/summarizer.js new file mode 100644 index 0000000..7c41bab --- /dev/null +++ b/src/summarizer.js @@ -0,0 +1,48 @@ +async function run(fullPrompt, options = {}) { + if (!window?.AIBrowser?.generateTextRaw) { + return '

    Browser AI is not available.

    '; + } + const out = await window?.AIBrowser?.generateTextRaw(fullPrompt, options); + return typeof out === 'string' ? out : String(out ?? ''); +} + +export async function getAnswer(systemPrompt, question) { + return await run(systemPrompt, { + max_new_tokens: 256, + temperature: 0.45, + top_k: 50, + top_p: 0.95, + repetition_penalty: 1.1 + }); +} + +export async function getDocumentText(editorRef) { + const de = editorRef.current.documentEditor; + const sel = de.selection; + const start = sel.startOffset; + const end = sel?.endOffset; + de.selection.selectAll(); + const text = de.selection.text; + de.selection.select(start, end); + return text; +} + +export async function getDocumentSummary(editorRef) { + const docuemntText = await getDocumentText(editorRef); + const prompt = + `Summarize: ${docuemntText}`; + return await run(prompt, { + max_new_tokens: 220, + temperature: 0.35 + }); +} + +export async function getSuggestions(editorRef) { + const docuemntText = await getDocumentText(editorRef); + const prompt = + `list 3 short follow-up questions a user might ask about this text, one per line:\n${docuemntText}\nReturn only the 3 lines.`; + return await run(prompt, { + max_new_tokens: 80, + temperature: 0.6 + }); +} \ No newline at end of file diff --git a/src/title-bar.jsx b/src/title-bar.jsx new file mode 100644 index 0000000..f240fe5 --- /dev/null +++ b/src/title-bar.jsx @@ -0,0 +1,386 @@ +import { createElement as sfCreateElement } from '@syncfusion/ej2-base'; +import { Button, Switch } from '@syncfusion/ej2-buttons'; +import { DropDownButton } from '@syncfusion/ej2-splitbuttons'; +import { Dialog } from '@syncfusion/ej2-popups'; + +/** + * Represents document editor title bar. + */ +export class TitleBar { + constructor( + element, + docEditor, + isShareNeeded, + dialogComponent = null, + backToUploadCallback, + onAIToggle + ) { + this.tileBarDiv = element; + this.documentEditor = docEditor; + this.dialogComponent = dialogComponent || null; + this.backToUploadCallback = backToUploadCallback; + this.onAIToggle = onAIToggle; + this.initializeTitleBar(isShareNeeded); + this.initializeConfirmDialog(); + this.wireEvents(); + } + + initializeTitleBar = (isShareNeeded) => { + const downloadText = 'Download'; + const downloadToolTip = 'Download this document'; + const printText = 'Print'; + const printToolTip = 'Print this document (Ctrl+P)'; + const documentTileText = 'Document Name. Click or tap to rename this document.'; + + this.tileBarDiv.innerHTML = ''; + + this.createBackButton(); + this.createTitleElements(documentTileText); + + const btnStyles = this.getButtonStyles(); + + this.addAISwitch(); + + this.print = this.addButton( + 'e-de-icon-Print e-de-padding-right e-icon-left', + printText, + btnStyles, + 'de-print', + printToolTip, + false + ); + + const items = [ + { text: 'Syncfusion Document Text (*.sfdt)', id: 'sfdt' }, + { text: 'Word Document (*.docx)', id: 'word' }, + { text: 'Word Template (*.dotx)', id: 'dotx' }, + { text: 'Plain Text (*.txt)', id: 'txt' }, + ]; + + this.export = this.addButton( + 'e-de-icon-Download e-de-padding-right e-icon-left', + downloadText, + btnStyles, + 'documenteditor-share', + downloadToolTip, + true, + items + ); + + this.configureButtonVisibility(isShareNeeded); + }; + + addAISwitch() { + this.aiSwitchContainer = sfCreateElement('div', { + className: 'de-ai-switch-container', + styles: 'float:right; display:flex; align-items:center; gap:5px; margin:5px 8px;' + }); + + this.aiSwitchLabel = sfCreateElement('span', { + className: 'de-ai-switch-label', + innerHTML: 'AI Enabled', + styles: 'font-size:12px; font-weight:400; user-select:none;' + }); + + this.aiSwitchInput = sfCreateElement('input', { id: 'de-ai-switch', attrs: { type: 'checkbox' } }); + + this.aiSwitchContainer.appendChild(this.aiSwitchLabel); + this.aiSwitchContainer.appendChild(this.aiSwitchInput); + + this.tileBarDiv.appendChild(this.aiSwitchContainer); + + this.aiSwitch = new Switch({ + checked: false, + cssClass: 'titlebar-ai-switch', + change: this.onAISwitchChange + }); + this.aiSwitch.appendTo(this.aiSwitchInput); + } + + onAISwitchChange = (args) => { + const checked = !!args.checked; + if (typeof this.onAIToggle === 'function') { + this.onAIToggle(checked); + } + this.isAIEnabled = checked; + }; + + initializeConfirmDialog() { + this.confirmDialogHost = sfCreateElement('div', { id: 'de-back-confirm-dialog' }); + document.body.appendChild(this.confirmDialogHost); + this.confirmDialog = new Dialog({ + header: 'Are you sure you want to leave this page ?', + content: 'You have unsaved changes. If you leave now, your recent edits will be discarded and you’ll return to the upload page. Do you want to continue?', + isModal: true, + width: '400px', + visible: false, + showCloseIcon: true, + animationSettings: { effect: 'Fade' }, + closeOnEscape: true, + overlayClick: () => { this.confirmDialog.hide(); }, + buttons: [ + { + click: this.onConfirmYes, + buttonModel: { content: 'Leave', isPrimary: true } + }, + { + click: this.onConfirmNo, + buttonModel: { content: 'Stay' } + } + ] + }); + this.confirmDialog.appendTo(this.confirmDialogHost); + } + + onBackClick = () => { + if (this.confirmDialog) { + this.confirmDialog.show(); + } else if (this.backToUploadCallback) { + this.backToUploadCallback(); + } + }; + + onConfirmYes = () => { + this.confirmDialog.hide(); + if (this.backToUploadCallback) { + this.backToUploadCallback(); + } + }; + + onConfirmNo = () => { + this.confirmDialog.hide(); + }; + + createBackButton() { + if (this.backToUploadCallback) { + const backButtonStyles = + 'float:left;background: transparent;box-shadow:none; font-family: inherit;border-color: transparent;' + + 'border-radius: 2px;color:inherit;font-size:12px;text-transform:capitalize;height:28px;font-weight:600;line-height:14px;margin-top: 2px;margin-right: 10px;'; + + this.backButton = this.addButton( + 'e-icons e-arrow-left e-chevron-left e-de-padding-right', + '', + backButtonStyles, + 'de-back-upload', + 'Back to file upload', + false, + undefined, + true + ); + } + } + + createTitleElements(documentTileText) { + this.documentTitle = sfCreateElement('label', { + id: 'documenteditor_title_name', + styles: + 'font-weight:600;text-overflow:ellipsis;white-space:pre;overflow:hidden;user-select:none;cursor:text', + }); + this.documentTitleContentEditor = sfCreateElement('div', { + id: 'documenteditor_title_contentEditor', + className: 'single-line', + }); + this.documentTitleContentEditor.appendChild(this.documentTitle); + this.tileBarDiv.appendChild(this.documentTitleContentEditor); + this.documentTitleContentEditor.setAttribute('title', documentTileText); + } + + getButtonStyles() { + return ( + 'float:right;background: transparent;box-shadow:none; font-family: inherit;border-color: transparent;' + + 'border-radius: 2px;color:inherit;font-size:12px;text-transform:capitalize;height:28px;font-weight:400;margin-top: 2px;' + ); + } + + configureButtonVisibility(isShareNeeded) { + if (!isShareNeeded && this.export) { + this.export.element.style.display = 'none'; + } + } + + wireEvents = () => { + if (this.backButton) { + this.backButton.element.addEventListener('click', this.onBackClick); + } + if (this.documentTitleContentEditor) { + this.documentTitleContentEditor.addEventListener('keydown', this.onTitleKeyDown); + this.documentTitleContentEditor.addEventListener('blur', this.onTitleBlur); + this.documentTitleContentEditor.addEventListener('click', this.updateDocumentEditorTitle); + } + }; + + onPrint = () => { + this.documentEditor.print(); + }; + + onTitleKeyDown = (e) => { + if (e.keyCode === 13) { + e.preventDefault(); + this.documentTitleContentEditor.contentEditable = 'false'; + if (this.documentTitleContentEditor.textContent === '') { + this.documentTitleContentEditor.textContent = 'New Document'; + } + } + }; + + onTitleBlur = () => { + if (this.documentTitleContentEditor.textContent === '') { + this.documentTitleContentEditor.textContent = 'New Document'; + } + this.documentTitleContentEditor.contentEditable = 'false'; + this.documentEditor.documentName = + this.documentTitle.textContent || 'New Document'; + }; + + updateDocumentEditorTitle = () => { + this.documentTitleContentEditor.contentEditable = 'true'; + this.documentTitleContentEditor.focus(); + const selection = window.getSelection(); + if (selection) { + selection.selectAllChildren(this.documentTitleContentEditor); + } + }; + + updateDocumentTitle = () => { + if (this.documentEditor.documentName === '') { + this.documentEditor.documentName = 'Untitled'; + } + this.documentTitle.textContent = this.documentEditor.documentName; + }; + + addButton( + iconClass, + btnText, + styles, + id, + tooltipText, + isDropDown, + items, + isBackButton + ) { + const button = sfCreateElement('button', { id: id, styles: styles }); + + if (isBackButton) { + this.tileBarDiv.insertBefore(button, this.tileBarDiv.firstChild); + } else { + this.tileBarDiv.appendChild(button); + if (btnText === 'Print') { + button.addEventListener('click', this.onPrint); + } + } + + button.setAttribute('title', tooltipText); + + if (isDropDown && items) { + const dropButton = new DropDownButton( + { + select: this.onExportClick, + items: items, + iconCss: iconClass, + cssClass: 'e-caret-hide', + content: '', + open: () => { + this.setTooltipForPopup(); + }, + }, + button + ); + return dropButton; + } else { + const ejButton = new Button( + { + iconCss: iconClass, + content: '', + }, + button + ); + return ejButton; + } + } + + setTooltipForPopup() { + const popup = document.getElementById('documenteditor-share-popup'); + if (!popup) return; + + const tooltips = [ + 'Download a copy of this document to your computer as an SFDT file.', + 'Download a copy of this document to your computer as a DOCX file.', + 'Download a copy of this document to your computer as a DOTX file.', + 'Download a copy of this document to your computer as a TXT file.', + ]; + + const listItems = popup.querySelectorAll('li'); + listItems.forEach((item, index) => { + if (tooltips[index]) { + item.setAttribute('title', tooltips[index]); + } + }); + } + + onExportClick = (args) => { + const value = args.item.id; + switch (value) { + case 'sfdt': + this.save('Sfdt'); + break; + case 'word': + this.save('Docx'); + break; + case 'dotx': + this.save('Dotx'); + break; + case 'txt': + this.save('Txt'); + break; + default: + break; + } + }; + + save = (format) => { + this.documentEditor.save( + this.documentEditor.documentName === '' ? 'New Document' : this.documentEditor.documentName, + format + ); + }; + + showButtons = (show) => { + const displayStyle = show ? 'block' : 'none'; + if (this.print) { + this.print.element.style.display = displayStyle; + } + if (this.export) { + this.export.element.style.display = displayStyle; + } + }; + + destroy() { + if (this.backButton) { + this.backButton.destroy(); + } + if (this.print) { + this.print.destroy(); + } + if (this.export) { + this.export.destroy(); + } + + if (this.aiSwitch) { + this.aiSwitch.destroy(); + this.aiSwitch = null; + } + if (this.aiSwitchContainer && this.aiSwitchContainer.parentNode) { + this.aiSwitchContainer.parentNode.removeChild(this.aiSwitchContainer); + this.aiSwitchContainer = null; + } + + if (this.confirmDialog) { + this.confirmDialog.destroy(); + this.confirmDialog = null; + } + if (this.confirmDialogHost && this.confirmDialogHost.parentNode) { + this.confirmDialogHost.parentNode.removeChild(this.confirmDialogHost); + this.confirmDialogHost = null; + } + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a273b0c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": [ + "src" + ] +}