Files
crm.clientright.ru/layouts/v7/lib/nextcloud-editor-v3.js
Fedor 840acca51a feat(documents): дедупликация documents_meta и исправление field_label
- Исправлен N8N_CODE_PROCESS_UPLOADED_FILES_FIXED.js: использовать uploads_field_labels[0] вместо [grp]
- Создан SQL_CLAIMSAVE_FIXED_NEW_FLOW_DEDUP.sql с дедупликацией documents_meta
- Создан SQL_CLEANUP_DOCUMENTS_META_DUPLICATES.sql для очистки существующих дубликатов
- Создан полный уникальный индекс idx_document_texts_hash_unique на document_texts(file_hash)
- Добавлен SESSION_LOG_2025-11-28_documents_dedup.md с описанием всех изменений

Fixes:
- field_label теперь корректно отображает 'Переписка' вместо 'group-2'
- documents_meta не накапливает дубликаты при повторных сохранениях
- ON CONFLICT (file_hash) теперь работает для document_texts
2025-11-28 18:16:53 +03:00

474 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Nextcloud Editor Integration JavaScript
* JavaScript для интеграции редактора документов Nextcloud
*/
/**
* Открытие папки проекта в Nextcloud
*/
function openProjectFolder(projectId, projectName) {
console.log('📁 Opening project folder in Nextcloud:', projectId, projectName);
// Нормализуем имя проекта (убираем пробелы и кавычки)
if (projectName) {
// Убираем кавычки (заменяем на подчёркивание)
projectName = projectName.replace(/"/g, '_');
// Заменяем ВСЕ пробелы на подчёркивания
projectName = projectName.replace(/\s+/g, '_');
// Заменяем множественные подчёркивания на одинарное
projectName = projectName.replace(/_+/g, '_');
// Убираем подчёркивания в начале и конце
projectName = projectName.replace(/^_+|_+$/g, '');
}
// Формируем URL для папки проекта в Nextcloud
const folderName = projectName ? `${projectName}_${projectId}` : `project_${projectId}`;
const encodedFolderName = encodeURIComponent(folderName);
const nextcloudUrl = 'https://office.clientright.ru:8443';
// URL для папки проекта в Nextcloud External Storage (со структурой Project/)
const folderUrl = `${nextcloudUrl}/apps/files/?dir=/crm/crm2/CRM_Active_Files/Documents/Project/${encodedFolderName}`;
console.log('🔗 Folder URL:', folderUrl);
// Открываем папку в новом окне
window.open(folderUrl, 'nextcloud_folder', 'width=1200,height=800,scrollbars=yes,resizable=yes');
}
/**
* Открытие редактора Nextcloud для документа
*/
function openNextcloudEditor(recordId, fileName) {
console.log('🚀 NEXTCLOUD EDITOR v3 - COLLABORA: Function called!', recordId, fileName);
// COLLABORA через Nextcloud (работает!)
const redirectUrl = `/crm_extensions/file_storage/api/open_file_v3_collabora.php?recordId=${recordId}&fileName=${encodeURIComponent(fileName)}&v=${Date.now()}`;
console.log('🎯 Opening via Collabora v3:', redirectUrl);
console.log('⚠️ Если открывается OnlyOffice - очисти кеш браузера (Ctrl+Shift+R)!');
// Открываем в новом окне
const win = window.open(redirectUrl, 'nextcloud_editor_' + Date.now(), 'width=1400,height=900,scrollbars=yes,resizable=yes');
if (win) {
console.log('✅ Collabora editor opened successfully');
} else {
console.log('❌ Failed to open editor window - popup blocked');
alert('❌ Не удалось открыть редактор. Проверьте блокировку всплывающих окон.');
}
}
function testSimpleAPI(recordId, fileName) {
console.log('🧪 Testing simple API...', recordId, fileName);
// Пропускаем простой API и сразу идем к основному
console.log('🎯 Skipping simple API, going to main API...');
callMainAPI(recordId, fileName);
}
function createEditUrls(baseEditUrl, recordId, fileName, fileId = 662) {
console.log('🔗 Creating edit URLs in JavaScript...', { fileId, fileName });
// Извлекаем базовый URL из базовой ссылки
const baseUrl = 'https://office.clientright.ru:8443';
const encodedFileName = encodeURIComponent(fileName);
const filePath = `/crm/crm2/CRM_Active_Files/Documents/${recordId}/${encodedFileName}`;
// Токен для RichDocuments (из настроек Nextcloud)
const richDocumentsToken = '1sanuq71b3n4fm1ldkbb';
const urls = {
// ЛУЧШИЙ СПОСОБ! - редирект через нашу промежуточную страницу (БЕЗ CSRF!)
'redirect_to_nextcloud': fileId ? `/crm_extensions/file_storage/api/open_file.php?fileId=${fileId}&fileName=${encodedFileName}&recordId=${recordId}` : null,
// РАБОЧИЙ СПОСОБ! - прямая ссылка на Nextcloud
'files_editing_auto': fileId ? `${baseUrl}/apps/files/files/${fileId}?dir=/&editing=true&openfile=true` : null,
// Вариант без автоматического редактирования
'files_editing': fileId ? `${baseUrl}/apps/files/files/${fileId}?dir=/&editing=false&openfile=true` : null,
// Collabora Editor
'collabora_editor': fileId ? `${baseUrl}/index.php/apps/richdocuments/index?fileId=${fileId}` : null,
// OnlyOffice Editor
'onlyoffice_editor': fileId ? `${baseUrl}/apps/onlyoffice?fileId=${fileId}` : null,
// Прямое открытие файла
'files_direct': fileId ? `${baseUrl}/apps/files/files/${fileId}` : `${baseUrl}/apps/files/?dir=/&openfile=${encodedFileName}`,
// Файловый менеджер
'files_manager': `${baseUrl}/apps/files/?dir=/&openfile=${encodedFileName}`
};
// Убираем null значения
Object.keys(urls).forEach(key => {
if (urls[key] === null) {
delete urls[key];
}
});
return {
all: urls,
// РЕДИРЕКТ через нашу страницу - лучший способ (обходит CSRF)
recommended: urls.redirect_to_nextcloud || urls.files_editing_auto || urls.files_editing || urls.collabora_editor
};
}
function getEditUrls(recordId, fileName, simpleData) {
console.log('🔗 Getting edit URLs...');
$.ajax({
url: '/crm_extensions/file_storage/api/get_edit_urls.php',
method: 'GET',
data: {
record: recordId,
fileName: fileName
},
dataType: 'json',
success: function(response) {
console.log('✅ Edit URLs received:', response);
if (response.success) {
// Пробуем открыть рекомендуемый URL
const recommendedUrl = response.data.urls[response.data.recommended];
console.log('🎯 Trying recommended URL:', recommendedUrl);
openEditor(recommendedUrl, {
...simpleData,
urls: response.data.urls,
recommended: response.data.recommended
});
} else {
// Если не получилось, используем простой API
openEditor(simpleData.edit_url, simpleData);
}
},
error: function(xhr, status, error) {
console.error('❌ Failed to get edit URLs:', error);
// Если не получилось, используем простой API
openEditor(simpleData.edit_url, simpleData);
}
});
}
function callMainAPI(recordId, fileName) {
console.log('🎯 Calling main API...', recordId, fileName);
// Показываем прогресс
if (typeof app !== 'undefined' && app.helper && app.helper.showProgress) {
app.helper.showProgress({
message: 'Подготовка файла к редактированию...'
});
}
// Вызываем ПРАВИЛЬНЫЙ API для подготовки файла
$.ajax({
url: '/index.php?module=Documents&action=NcPrepareEdit',
method: 'GET',
data: {
record: recordId,
fileName: fileName
},
dataType: 'json',
success: function(response) {
console.log('📡 API Response:', response);
// Скрываем прогресс
if (typeof app !== 'undefined' && app.helper && app.helper.hideProgress) {
app.helper.hideProgress();
}
if (response.success) {
console.log('✅ File prepared successfully');
// Открываем редактор
openEditor(response.data.edit_url, response.data);
} else {
console.error('❌ API Error:', response.error);
showError('Ошибка подготовки файла: ' + response.error);
}
},
error: function(xhr, status, error) {
console.error('❌ AJAX Error:', error);
console.error('❌ Status:', status);
console.error('❌ Response:', xhr.responseText);
console.error('❌ Status Code:', xhr.status);
// Скрываем прогресс
if (typeof app !== 'undefined' && app.helper && app.helper.hideProgress) {
app.helper.hideProgress();
}
// Показываем подробную ошибку
let errorMessage = 'Ошибка подключения к серверу: ' + error;
if (xhr.responseText) {
errorMessage += '\n\nОтвет сервера:\n' + xhr.responseText;
}
showError(errorMessage);
}
});
}
// Функция для открытия редактора
function openEditor(editUrl, fileData) {
console.log('🎯 Opening editor with URL:', editUrl);
console.log('📋 All available URLs:', fileData.urls);
// Открываем редактор в новом окне
var win = window.open(editUrl, 'nextcloud_editor', 'width=1200,height=800,scrollbars=yes,resizable=yes');
if (win) {
console.log('✅ Editor opened successfully');
// Показываем уведомление об успехе
if (typeof app !== 'undefined' && app.helper && app.helper.showSuccessNotification) {
app.helper.showSuccessNotification({
message: 'Редактор открыт! Файл: ' + fileData.file_name
});
} else {
// alert('✅ Редактор открыт! Файл: ' + fileData.file_name);
}
// Показываем информацию о файле
showFileInfo(fileData);
} else {
console.log('❌ Failed to open editor window - popup blocked or error');
console.log('📋 Showing modal with alternative URLs');
// Показываем модальное окно с альтернативными вариантами
showModalWithUrls(editUrl, fileData);
}
}
// Функция для отображения информации о файле
function showFileInfo(fileData) {
// Проверяем, есть ли нужные поля в fileData
var location = 'Неизвестно';
var nextcloudPath = 'Не указан';
var status = 'Не указан';
if (fileData.file_location && fileData.file_location.type) {
location = fileData.file_location.type === 's3' ? 'S3 хранилище' : 'Локальное хранилище';
}
if (fileData.nextcloud_path) {
nextcloudPath = fileData.nextcloud_path;
}
if (fileData.message) {
status = fileData.message;
}
var infoHtml = `
<div class="alert alert-info" style="margin: 10px 0;">
<h5><i class="fa fa-info-circle"></i> Информация о файле</h5>
<p><strong>Файл:</strong> ${fileData.file_name}</p>
<p><strong>Расположение:</strong> ${location}</p>
<p><strong>Путь в Nextcloud:</strong> ${nextcloudPath}</p>
<p><strong>Статус:</strong> ${status}</p>
</div>
`;
// Если есть альтернативные URL, добавляем их
if (fileData.urls) {
infoHtml += `
<div class="alert alert-warning" style="margin: 10px 0;">
<h6><i class="fa fa-link"></i> Альтернативные способы открытия:</h6>
<div class="btn-group-vertical" style="width: 100%;">
`;
Object.keys(fileData.urls).forEach(function(key) {
const label = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
const isRecommended = key === fileData.recommended;
const btnClass = isRecommended ? 'btn-success' : 'btn-default';
const icon = isRecommended ? 'fa-star' : 'fa-external-link';
infoHtml += `
<a href="${fileData.urls[key]}" target="_blank" class="btn ${btnClass} btn-sm" style="margin: 2px; text-align: left;">
<i class="fa ${icon}"></i> ${label}${isRecommended ? ' (рекомендуется)' : ''}
</a>
`;
});
infoHtml += `
</div>
</div>
`;
}
// Добавляем информацию в модальное окно или показываем отдельно
if ($('#nextcloudEditModal').length) {
$('#nextcloudEditModal .modal-body').prepend(infoHtml);
} else {
// Создаём временное уведомление
var notification = $('<div class="alert alert-info" style="position: fixed; top: 20px; right: 20px; z-index: 9999; max-width: 400px;">' + infoHtml + '</div>');
$('body').append(notification);
// Автоматически скрываем через 5 секунд
setTimeout(function() {
notification.fadeOut(function() {
notification.remove();
});
}, 5000);
}
}
// Функция для отображения ошибок
function showError(message) {
console.error('🚨 Error:', message);
if (typeof app !== 'undefined' && app.helper && app.helper.showErrorNotification) {
app.helper.showErrorNotification({
message: message
});
} else {
alert('❌ Ошибка: ' + message);
}
}
// Функция для показа модального окна с URL
function showModalWithUrls(editUrl, fileData) {
console.log('📋 Showing modal with URLs for file:', fileData.file_name);
if (fileData.urls) {
// Используем существующую функцию showEditOptions
showEditOptions(fileData.urls, fileData.file_name, fileData.record_id);
} else {
// Если нет альтернативных URL, показываем ошибку
showError('Не удалось открыть редактор. Попробуйте открыть файл вручную в Nextcloud.');
}
}
/**
* Показ вариантов открытия файла
*/
function showEditOptions(urls, fileName, recordId) {
console.log('🎯 Showing edit options for:', fileName);
console.log('📋 Available URLs:', urls);
var buttonsHtml = '';
// Если urls - это объект (новый формат)
if (typeof urls === 'object' && !Array.isArray(urls)) {
Object.keys(urls).forEach(function(key, index) {
const label = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
const url = urls[key];
const isRecommended = key === 'correct_path';
const btnClass = isRecommended ? 'btn-success' : 'btn-primary';
const icon = isRecommended ? 'fa-star' : 'fa-external-link';
buttonsHtml += `
<a href="${url}" target="_blank" class="btn ${btnClass} btn-sm" style="margin: 3px; display: block;">
<i class="fa ${icon}"></i> ${label}${isRecommended ? ' (рекомендуется)' : ''}
</a>
`;
});
} else {
// Старый формат - массив URL
var labels = [
'С параметром openfile',
'С action=edit',
'С edit=true',
'Через RichDocuments',
'Через OnlyOffice'
];
var icons = ['fa-file', 'fa-edit', 'fa-pencil', 'fa-file-text', 'fa-file-word-o'];
var colors = ['btn-primary', 'btn-success', 'btn-info', 'btn-warning', 'btn-danger'];
urls.forEach(function(url, index) {
buttonsHtml += `
<a href="${url}" target="_blank" class="btn ${colors[index]} btn-sm" style="margin: 3px; display: block;">
<i class="fa ${icons[index]}"></i> ${labels[index]}
</a>
`;
});
}
var modalHtml = `
<div class="modal fade" id="nextcloudEditModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">
<i class="fa fa-edit"></i> Варианты открытия файла
</h4>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body">
<p><strong>Файл:</strong> ${fileName}</p>
<p>Попробуйте эти варианты для прямого открытия в редакторе:</p>
<div class="btn-group-vertical" style="width: 100%;">
${buttonsHtml}
</div>
<div class="alert alert-info" style="margin-top: 15px;">
<small><strong>💡 Совет:</strong> Попробуйте варианты сверху вниз. Один из них должен открыть файл сразу в редакторе!</small>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Закрыть</button>
</div>
</div>
</div>
</div>
`;
// Удаляем старое модальное окно
$('#nextcloudEditModal').remove();
// Добавляем новое
$('body').append(modalHtml);
$('#nextcloudEditModal').modal('show');
}
/**
* Синхронизация изменений файла (заглушка)
*/
function syncFileChanges(recordId, fileName) {
console.log('Syncing file changes for:', recordId, fileName);
if (typeof app !== 'undefined' && app.helper && app.helper.showSuccessNotification) {
app.helper.showSuccessNotification({
message: 'Изменения синхронизированы!'
});
} else {
alert('✅ Изменения синхронизированы!');
}
}
/**
* Открытие редактора в новом окне
*/
function openInNewWindow(editUrl) {
window.open(editUrl, 'nextcloud_editor', 'width=1200,height=800,scrollbars=yes,resizable=yes,toolbar=no,location=no');
console.log('Opened Nextcloud editor in new window');
}
// Алиас функции для обратной совместимости
function editInNextcloud(recordId, fileName) {
console.log('📝 editInNextcloud called (alias)');
return openNextcloudEditor(recordId, fileName);
}
// Автоматическое подключение при загрузке страницы
$(document).ready(function() {
console.log('Nextcloud Editor integration loaded');
// Добавляем CSS стили для модального окна
if (!$('#nextcloud-editor-styles').length) {
$('<style id="nextcloud-editor-styles">')
.html(`
.nextcloud-edit-btn {
background: #0082c9;
border-color: #0082c9;
}
.nextcloud-edit-btn:hover {
background: #006ba6;
border-color: #006ba6;
}
`)
.appendTo('head');
}
});