|
@@ -61,9 +61,10 @@
|
|
|
}
|
|
}
|
|
|
.eks-inline-translate:hover { opacity: 1 !important; }
|
|
.eks-inline-translate:hover { opacity: 1 !important; }
|
|
|
.eks-composer-translate {
|
|
.eks-composer-translate {
|
|
|
- border: 0 !important; border-radius: 999px !important; padding: 5px 9px !important;
|
|
|
|
|
- background: #1f6feb !important; color: white !important; font: 600 12px system-ui, sans-serif !important;
|
|
|
|
|
- cursor: pointer !important; margin: 4px !important; box-shadow: 0 2px 8px rgba(15,23,42,.16) !important;
|
|
|
|
|
|
|
+ border: 0 !important; border-radius: 999px !important; padding: 4px 8px !important;
|
|
|
|
|
+ background: #1f6feb !important; color: white !important; font: 600 11px system-ui, sans-serif !important;
|
|
|
|
|
+ cursor: pointer !important; margin: 0 4px !important; box-shadow: 0 2px 8px rgba(15,23,42,.16) !important;
|
|
|
|
|
+ display: inline-flex !important; align-items: center !important; min-height: 22px !important;
|
|
|
}
|
|
}
|
|
|
.eks-composer-status { display: inline-block; margin-left: 6px; color: #64748b; font: 12px system-ui, sans-serif; }
|
|
.eks-composer-status { display: inline-block; margin-left: 6px; color: #64748b; font: 12px system-ui, sans-serif; }
|
|
|
`;
|
|
`;
|
|
@@ -119,7 +120,7 @@
|
|
|
<textarea data-eks="text" placeholder="Zaznacz tekst wiadomości albo wklej go tutaj"></textarea>
|
|
<textarea data-eks="text" placeholder="Zaznacz tekst wiadomości albo wklej go tutaj"></textarea>
|
|
|
<div class="eks-row" style="margin-top:8px">
|
|
<div class="eks-row" style="margin-top:8px">
|
|
|
<button data-eks="selected" type="button">Tłumacz wiadomość na PL</button>
|
|
<button data-eks="selected" type="button">Tłumacz wiadomość na PL</button>
|
|
|
- <button data-eks="draft" type="button">Draft PL → język klienta</button>
|
|
|
|
|
|
|
+ <button data-eks="draft" type="button">Tekst z okna PL → język klienta</button>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="eks-row" style="margin-top:8px">
|
|
<div class="eks-row" style="margin-top:8px">
|
|
|
<button data-eks="fill-selected" class="secondary" type="button">Wstaw zaznaczenie</button>
|
|
<button data-eks="fill-selected" class="secondary" type="button">Wstaw zaznaczenie</button>
|
|
@@ -157,14 +158,45 @@
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function getDraftText() {
|
|
function getDraftText() {
|
|
|
- const el = getDraftElement();
|
|
|
|
|
|
|
+ const el = getReplyEditorElement();
|
|
|
|
|
+ return el ? readEditableText(el) : '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function readEditableText(el) {
|
|
|
return el ? (el.value || el.innerText || el.textContent || '') : '';
|
|
return el ? (el.value || el.innerText || el.textContent || '') : '';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ function getReplyEditorElement() {
|
|
|
|
|
+ const active = document.activeElement;
|
|
|
|
|
+ if (active && isReplyEditor(active)) return active;
|
|
|
|
|
+
|
|
|
|
|
+ const explicit = document.querySelector('.reply-box__top .ProseMirror[contenteditable="true"], .reply-box__top [contenteditable="true"].ProseMirror, .reply-box__top [contenteditable="true"][translate="no"]');
|
|
|
|
|
+ if (explicit) return explicit;
|
|
|
|
|
+
|
|
|
|
|
+ const replyBox = document.querySelector('.reply-box__top');
|
|
|
|
|
+ if (replyBox) {
|
|
|
|
|
+ const editor = replyBox.querySelector('.ProseMirror[contenteditable="true"], [contenteditable="true"][role="textbox"], [contenteditable="true"]');
|
|
|
|
|
+ if (editor) return editor;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const editors = [...document.querySelectorAll('.ProseMirror[contenteditable="true"], [contenteditable="true"][role="textbox"]')];
|
|
|
|
|
+ return editors.find((el) => !el.closest(`#${PANEL_ID}, #${POPOVER_ID}`) && isLikelyReplyEditor(el)) || null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function isReplyEditor(el) {
|
|
|
|
|
+ return Boolean(el && !el.closest(`#${PANEL_ID}, #${POPOVER_ID}`) && el.closest('.reply-box__top') && (el.isContentEditable || el.classList?.contains('ProseMirror')));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function isLikelyReplyEditor(el) {
|
|
|
|
|
+ return Boolean(el && !el.closest('.message, .message-bubble, [data-message-id]') && (el.classList?.contains('ProseMirror') || el.isContentEditable));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function getDraftElement() {
|
|
function getDraftElement() {
|
|
|
|
|
+ const replyEditor = getReplyEditorElement();
|
|
|
|
|
+ if (replyEditor) return replyEditor;
|
|
|
const active = document.activeElement;
|
|
const active = document.activeElement;
|
|
|
- if (active && (active.tagName === 'TEXTAREA' || active.tagName === 'INPUT' || active.isContentEditable)) return active;
|
|
|
|
|
- const editors = [...document.querySelectorAll('[contenteditable="true"][role="textbox"], textarea')];
|
|
|
|
|
|
|
+ if (active && (active.tagName === 'TEXTAREA' || active.isContentEditable) && !active.closest(`#${PANEL_ID}, #${POPOVER_ID}`)) return active;
|
|
|
|
|
+ const editors = [...document.querySelectorAll('.ProseMirror[contenteditable="true"], [contenteditable="true"][role="textbox"], textarea')];
|
|
|
return editors.find((el) => !el.closest(`#${PANEL_ID}`)) || null;
|
|
return editors.find((el) => !el.closest(`#${PANEL_ID}`)) || null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -179,25 +211,56 @@
|
|
|
}
|
|
}
|
|
|
if (el.isContentEditable) {
|
|
if (el.isContentEditable) {
|
|
|
el.focus();
|
|
el.focus();
|
|
|
- el.textContent = text;
|
|
|
|
|
- el.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertText', data: text }));
|
|
|
|
|
|
|
+ replaceContentEditableText(el, text);
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ function replaceContentEditableText(el, text) {
|
|
|
|
|
+ el.focus();
|
|
|
|
|
+ const selection = window.getSelection();
|
|
|
|
|
+ const range = document.createRange();
|
|
|
|
|
+ range.selectNodeContents(el);
|
|
|
|
|
+ selection.removeAllRanges();
|
|
|
|
|
+ selection.addRange(range);
|
|
|
|
|
+
|
|
|
|
|
+ let inserted = false;
|
|
|
|
|
+ try {
|
|
|
|
|
+ inserted = document.execCommand && document.execCommand('insertText', false, text);
|
|
|
|
|
+ } catch (_) {
|
|
|
|
|
+ inserted = false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!inserted) {
|
|
|
|
|
+ const normalised = text.split(/\n{2,}/).map((block) => block.trim()).filter(Boolean);
|
|
|
|
|
+ el.innerHTML = normalised.length
|
|
|
|
|
+ ? normalised.map((block) => `<p>${escapeHtml(block).replace(/\n/g, '<br>')}</p>`).join('')
|
|
|
|
|
+ : '<p></p>';
|
|
|
|
|
+ el.dispatchEvent(new InputEvent('beforeinput', { bubbles: true, inputType: 'insertText', data: text }));
|
|
|
|
|
+ el.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertText', data: text }));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function escapeHtml(text) {
|
|
|
|
|
+ return text.replace(/[&<>"]/g, (ch) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[ch]));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
async function translateAndReplaceDraft(button) {
|
|
async function translateAndReplaceDraft(button) {
|
|
|
- const el = getDraftElement();
|
|
|
|
|
- const text = (el ? (el.value || el.innerText || el.textContent || '') : '').trim();
|
|
|
|
|
|
|
+ const el = getReplyEditorElement();
|
|
|
|
|
+ const text = readEditableText(el).trim();
|
|
|
const token = getToken();
|
|
const token = getToken();
|
|
|
if (!token) return showPanelError('Brak Agent UI token.');
|
|
if (!token) return showPanelError('Brak Agent UI token.');
|
|
|
- if (!text) return showComposerStatus(button, 'Najpierw wpisz odpowiedź po polsku.', true);
|
|
|
|
|
|
|
+ if (!el) return showComposerStatus(button, 'Nie widzę okna odpowiedzi Chatwoot.', true);
|
|
|
|
|
+ if (!text) return showComposerStatus(button, 'Najpierw wpisz tekst po polsku w oknie odpowiedzi.', true);
|
|
|
|
|
|
|
|
const panel = document.getElementById(PANEL_ID);
|
|
const panel = document.getElementById(PANEL_ID);
|
|
|
panel.hidden = false;
|
|
panel.hidden = false;
|
|
|
panelField('source').value = 'pl';
|
|
panelField('source').value = 'pl';
|
|
|
panelField('text').value = text;
|
|
panelField('text').value = text;
|
|
|
- panelField('status').textContent = 'Wykrywam język klienta z pierwszej wiadomości i tłumaczę draft…';
|
|
|
|
|
|
|
+ panelField('status').textContent = 'Wykrywam język klienta z pierwszej wiadomości i tłumaczę tekst z okna odpowiedzi…';
|
|
|
showComposerStatus(button, 'Tłumaczę…');
|
|
showComposerStatus(button, 'Tłumaczę…');
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
@@ -341,7 +404,7 @@
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function addComposerButtons() {
|
|
function addComposerButtons() {
|
|
|
- const editors = document.querySelectorAll('[contenteditable="true"][role="textbox"], textarea');
|
|
|
|
|
|
|
+ const editors = document.querySelectorAll('.reply-box__top .ProseMirror[contenteditable="true"], .reply-box__top [contenteditable="true"].ProseMirror, .reply-box__top [contenteditable="true"][translate="no"]');
|
|
|
editors.forEach((el) => {
|
|
editors.forEach((el) => {
|
|
|
if (el.dataset?.eksComposerBound === '1') return;
|
|
if (el.dataset?.eksComposerBound === '1') return;
|
|
|
if (el.closest(`#${PANEL_ID}, #${POPOVER_ID}`)) return;
|
|
if (el.closest(`#${PANEL_ID}, #${POPOVER_ID}`)) return;
|
|
@@ -355,7 +418,8 @@
|
|
|
e.preventDefault(); e.stopPropagation();
|
|
e.preventDefault(); e.stopPropagation();
|
|
|
void translateAndReplaceDraft(btn);
|
|
void translateAndReplaceDraft(btn);
|
|
|
});
|
|
});
|
|
|
- const host = el.parentElement;
|
|
|
|
|
|
|
+ const menubar = el.closest('.ProseMirror-menubar-wrapper')?.querySelector('.ProseMirror-menubar');
|
|
|
|
|
+ const host = menubar || el.parentElement;
|
|
|
if (host) host.appendChild(btn);
|
|
if (host) host.appendChild(btn);
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|