﻿# Пользовательский модуль для просмотра и редактирования файлов

> [HTML Version](external_viewer_intergation.html)

В ELMA365 с помощью модулей расширения вы можете реализовать просмотр и редактирование типов файлов, которые не поддерживаются в системе по умолчанию.

Например, на странице оформления заказа вы хотите разместить не только текстовое описание, но и схемы монтажа оборудования в формате **.djvu**. По умолчанию в системе нельзя просматривать файлы такого типа, но вы можете создать [пользовательский модуль](extentions.md), который позволит это делать.

````
начало внимание

````
Создавать и настраивать модули могут только пользователи, включённые в группу [Администраторы](360006871932.md#administrators).

````
конец внимание

## ````
Архитектура

Модуль для просмотра и редактирования файлов состоит из следующих основных частей: параметры конфигурации, виджет типа [Страница предпросмотра](extension-zones.md#preview-page) и [методы API](extention-api.md).

Параметры конфигурации определяют поведение модуля в целом. Это могут быть, например, поддерживаемые форматы файлов, адрес сервера просмотра, язык интерфейса и т. д. 

Логика просмотра и редактирования реализована в виджете типа **Страница предпросмотра** с помощью клиент‑серверных [скриптов](client_server_scripts.md).

Методы API в составе модуля обеспечивают обмен данными, если в рамках модуля настроена интеграция с внешним приложением.

## Создание и настройка модуля

Чтобы создать новый модуль, перейдите в **Администрирование > Модули**, нажмите кнопку **+ Модуль**. В открывшемся окне выберите опцию **Создать**. Заполните информацию о модуле и нажмите кнопку **Создать**. Подробнее об этом читайте в статье [«Создание модуля»](create-extention.md). 

### Добавление параметров для подключения модуля  

На вкладке **Настройки** можно создавать, редактировать и удалять [общие параметры](extention-settings.md) модуля, которые заполняются при его подключении.

Параметры являются глобальными — они применяются ко всем файлам в системе, которые просматриваются или редактируются с помощью модуля.

Для создания параметра перейдите на вкладку **Настройки** и нажмите кнопку **+ Добавить**.

**(preview-module-4.png)**

В открывшемся окне заполните поля и нажмите кнопку **Создать**.

Обязательным параметром для модуля просмотра и редактирования файлов является свойство \[OBJECT\]. В нём хранятся поддерживаемые форматы файлов. Задайте для свойства следующие настройки:

**(preview-module-6.png)**



- **Отображаемое имя\*** — укажите название поля, которое будет отображаться на странице подключения модуля. Может быть любым, например, **Поддерживаемые форматы**;

- **Имя свойства\*** — укажите значение \[OBJECT\]. Данное имя свойства обязательно. По нему производится поиск модуля, с помощью которого будет открыт загруженный файл с определённым расширением;

- **Тип\*** — [Категория](360009707032.md#category). Чтобы модуль поддерживал несколько файловых расширений, выберите тип **Несколько**;

- **Значения** — добавьте форматы файлов, которые будут открываться с помощью модуля, например, **doc**, **docx** и **odp**.

Также вы можете добавить на страницу настроек модуля поля для переключения режима открытия файлов, указания адреса стороннего сервера и т. д.

Все добавленные свойства отображаются на странице подключения модуля в разделе **Администрирование**.

**(preview-module-3.PNG)**

При необходимости вы можете изменить стандартную форму представления настроек, например, скрыть часть полей. Для этого на вкладке **Настройки** нажмите кнопку **Изменить форму**. В открывшемся дизайнере интерфейсов вы можете редактировать страницу подключения модуля. Подробнее об этом читайте в статье [«Настройки модуля»](extention-settings.md#settings). 

### Методы API

На вкладке **Методы API** добавляются [методы](extention-api.md) обмена данными с внешним сервером. Если в создаваемом модуле не предусматривается возможность обмена данными, добавлять методы не нужно. 

Например, вы можете создать POST-запрос \[OBJECT\], который при редактировании документа будет получать с внешнего сервера текущий статус этого документа и добавлять его новую версию в ELMA365. Исходный код метода, который можно добавить, приведён в подразделе [«Пример интеграции с внешним средством просмотра и редактирования файлов»](#api). 

### Виджет просмотра и редактирования

Виджет типа **Страница предпросмотра** является главным компонентом модуля. В нём осуществляется обмен данными между ELMA365 и внешним приложением. Чтобы создать виджет:

1. Перейдите в настройках модуля на вкладку **Виджеты**.

2. Нажмите кнопку **+ Виджет**.

3. В открывшемся окне задайте следующие параметры:

- **Название\*** — укажите название виджета;

- **Код виджета\*** — задайте идентификатор для виджета;  

- **Расширение** — выберите группу** Предпросмотр файлов**. Укажите тип **Страница предпросмотра**.

(preview-module-1.PNG)

4. Нажмите кнопку **Сохранить**. 

После этого откроется дизайнер интерфейсов, в котором можно настроить виджет. 

В контексте виджета по умолчанию созданы следующие свойства:  

- **File** — свойство типа [Файл](360009707032.md#file_type). Документ, который открывается модулем просмотра и редактирования;

- **ForFile** — свойство типа [Файлы](360009707032.md#file_type). Используется при сохранении результата сравнения документа из поля **File** с другой его версией или другим файлом. Применяется, например, если документ ещё не сохранён в системе или версию нужно добавить к другому файлу. Свойство содержит документ, для которого создаётся версия с результатом сравнения;

- **Action** — свойство типа [Строка](360009707032.md#string). Режим открытия документа: только просмотр, просмотр и редактирование. Может принимать значения **view** и **edit**; 

- **Extension** — свойство типа [Строка](360009707032.md#string). Расширение файла из свойства **File**.

Эти переменные отображаются на вкладке **Шаблон** дизайнера интерфейсов на правой панели в блоке **Свойства**.

Рассмотрим пример использования контекста виджета. Вы можете создать метод, который будет проверять в клиентском скрипте, поддерживается ли модулем просмотр файла определённого типа. Для этого получите расширение файла из контекстной переменной **Extension** виджета:

````
function getFileExtension() \{  
    if (Context.data.extension)\{  
        return (Context.data.extension\[0\]);  
    \}  
    else \{  
        return "";  
    \}  
\}

````
Если формат файла поддерживается, функция будет возвращать \[OBJECT\], в противном случае — \[OBJECT\]. В примере ниже разрешена обработка только файлов с расширением **.xlsx**.

````
async function canRender() \{  
    let fileType:string = getFileExtension();     
    if (fileType == "xlsx")  
    \{  
        return  true;  
    \}  
    else   
    \{  
        return  false;  
    \}  
\}

````
**Важно**: чтобы получить данные документа, используйте серверные скрипты виджета просмотра и редактирования. Использование клиентских скриптов не рекомендуется, так как у пользователя может быть недостаточно прав на работу с элементом или файлом.

## Пример создания модуля просмотра без интеграции со сторонним сервисом 

Рассмотрим пример создания модуля, который позволит воспроизводить файлы формата **.mp3** в интерфейсе ELMA365. Для создания такого модуля будут использоваться стандартные HTML-элементы без настройки интеграции со сторонним сервисом. 

1. Перейдите на страницу **Администрирование > Модули**. В правом верхнем углу нажмите кнопку **+ Модуль**, а затем **Создать**. Укажите название модуля и краткое описание.

2. Перейдите на вкладку **Настройки **и добавьте обязательное свойство для указания формата файла. Укажите произвольное отображаемое название. В качестве имени свойства используйте значение \[OBJECT\], выберите тип **Категория** **>** **Один**, добавьте значение **mp3**. 

3. Создайте виджет, который будет воспроизводить файл формата **.mp3**. Для этого на вкладке **Виджеты** нажмите **+ Виджет**. Задайте произвольные значения для полей **Название** и **Код виджета**, например, **mp3 preview**. В поле **Расширение** выберите **Предпросмотр файлов > Страница предпросмотра**. Нажмите кнопку **Сохранить**. 

4. В открывшемся [дизайнере интерфейсов](interface_designer.md) перейдите на вкладку **Контекст**. 

5. Создайте переменную, которая будет хранить ссылку на обрабатываемый файл. Для этого нажмите **+ Добавить** и задайте следующие значения:  

- **Отображаемое имя\*** — **Ссылка на файл**; 

- **Имя свойства\*** — \[OBJECT\]; 

- **Тип\*** — **Строка**. 

6. Перейдите на вкладку **Шаблон**. Добавьте на поле для моделирования виджет [Код](code_widget.md). В открывшемся окне введите следующий скрипт: 

````
<% if (Context.data.link) \{ %>  
    <audio controls>  
        <source src="<%= Context.data.link%>" type="audio/mp3">  
        Ваш браузер не поддерживает встроенное аудио. Попробуйте скачать его  
        <a href="<%= Context.data.link%>" download>по ссылке</a>.  
    </audio>  
<% \} %>

````
В указанном скрипте с помощью стандартных HTML-элементов создаётся окно, содержащее плеер с файлом. В качестве источника для файла служит ссылка, созданная на шаге 5. 

После сохранения скрипта на поле для моделирования отобразится созданный плеер:

(preview-module-5.png)

7. Перейдите на вкладку **Скрипты**. В правом верхнем углу нажмите **Клиент**. Добавьте код, который будет производить инициализацию виджета: 

````
/\*\* Событие, выполняющееся при инициализации виджета \*/  
async function onInit() \{  
    // Получить ссылку на загрузку файла из контекстной переменной \`file\`  
    const file\_link = await Context.data.file?.getDownloadUrl();  
    // Если ссылки нет — завершить работу  
    if (\!file\_link) \{  
        return;  
    \}  
    // Присвоить контекстной переменной \`link\` значение ссылки на загрузку файла  
    Context.data.link = file\_link;  
\}

8. ````
В левом верхнем углу нажмите кнопку **Сохранить**, а затем **Проверить**, чтобы убедиться, что виджет составлен верно. Затем нажмите **Опубликовать**. 

После этого при включённом модуле вы сможете воспроизводить файлы формата **.mp3** в интерфейсе ELMA365, добавив их в поле типа [Файлы](360009707032.md#file_type) в карточку любого элемента приложения. 

## Пример интеграции с внешним средством просмотра и редактирования файлов 

Рассмотрим интеграцию модуля просмотра и редактирования с внешними приложениями на примере сервиса просмотра Р7-Офис. В этой интеграции c помощью скрипта на языке Javascript реализованы отправка документа и параметров его просмотра на внешний сервер, получение и отображение ответа в ELMA365. 

Для создания модуля выполните следующие действия: 

1. Перейдите на страницу **Администрирование > Модули**. В правом верхнем углу нажмите кнопку **+ Модуль**, а затем **Создать**. Укажите название модуля и краткое описание.

2. Перейдите на вкладку **Настройки **и добавьте обязательное свойство для указания формата файла. Укажите произвольное отображаемое название. В качестве имени свойства используйте значение \[OBJECT\], выберите тип **Категория** **>** **Несколько**, в поле **Значения** добавьте поддерживаемые модулем форматы: **doc**, **docx**, **odt**, **txt**, **xls**, **xlsx**, **ods**, **csv**, **ppt**, **pptx**, **odp**. 

3. Аналогично создайте два свойства, с помощью которых будет происходить подключение к серверу внешнего средства просмотра файлов, со следующими параметрами:

- **Отображаемое имя\*** — задайте названия для свойств: **Адрес сервера ELMA365** и **Адрес сервера Р7-Офис**; 

- **Имя свойства\*** — укажите уникальные имена для свойств: \[OBJECT\] и \[OBJECT\]; 

- **Тип\*** — у обоих свойств установите тип **Строка**. 

4. Создайте виджет, который будет открывать файлы указанных форматов. Для этого на вкладке **Виджеты** нажмите **+ Виджет**. Задайте произвольные значения для полей **Название** и **Код виджета**, например, **doc preview**. В поле **Расширение** выберите **Предпросмотр файлов > Страница предпросмотра**. 

5. В открывшемся [дизайнере интерфейсов](interface_designer.md) перейдите на вкладку **Контекст**. 

6. Создайте контекстную переменную, которая будет использоваться для передачи параметров с сервера на клиент. Для этого нажмите **+ Добавить** и задайте следующие значения:  

- **Отображаемое имя\*** — **Дополнительные параметры**; 

- **Имя свойства\*** — \[OBJECT\]; 

- **Тип\*** — **Строка**. 

7. Перейдите на вкладку **Шаблон**. Добавьте на поле для моделирования виджет [Код](code_widget.md). В открывшемся окне введите скрипт, приведённый ниже. 

Скрипт 

````
<div style="height: 100%;width: 100%;display: flex;">  
    <script type="text/javascript" src="<%= getServerAddress() %>"></script>  
     
    <div id="<%= getrandname() %>" style="height: 100%">      
    </div>  
    <script type="text/javascript">  
        // Функция создания окна редактирования Р7-Офис  
        (function() \{ function createEditor(data) \{   
            // Инициализация окна редактирования                 
            var docEditor = new DocsAPI.DocEditor("<%= getrandname() %>",  
                \{  
                    "document": \{  
                            "fileType": data.fileExtension,  
                            "key": data.fileKey,  
                            "title": data.fileName,  
                            "url": data.fileUrl,  
                            "permissions": \{  
                                "download": false,  
                                "edit": true,  
                                "review": true,  
                                "reviewGroups": \[""\]  
                            \}  
                        \},  
                    "documentType": data.fileType,                  
                    "height": "100%",  
                    "width": "100%",  
                    "editorConfig": \{  
                        "customization": \{  
                            "chat": data.chat,  
                            "comments": data.comments,  
                            "macros": data.macros,  
                            "macrosMode": data.macros ? "warn" : "disable",  
                            "plugins": data.plugins  
                        \},  
                        "callbackUrl": data.callbackUrl,  
                        "mode": data.mode,  
                        "lang": data.language,  
                        "user": \{  
                                "id": data.userId,  
                                "name": data.userName  
                            \}                                                                          
                        \}  
                    \});  
                \}  
  
          
        // Получение файла, создание сессии просмотра/редактирования  
        let connections\_ch = 3;  
        function waitForDocsApi() \{  
            if (window.DocsAPI) \{  
                let data = \{  
                    serverAddress: '<%= getServerAddress() %>',  
                    callbackUrl: '<%= getCallbackUrl() %>',  
                    userId: '<%= getUserId() %>',  
                    userName: '<%= getUserName() %>',  
                    fileId: '<%= getFileId() %>',  
                    fileKey: '<%= getFileKey() %>',  
                    fileUrl: '<%= getFileUrl() %>',  
                    fileName: '<%= getFileName() %>',  
                    fileExtension: '<%= getFileExtension() %>',  
                    fileType: '<%= getFileType() %>',  
                    mode: '<%= getMode() %>',  
                    language: '<%= getLanguage() %>',  
                    chat: <%= isChat() %>,  
                    comments: <%= isComments() %>,  
                    macros: <%= isMacros() %>,  
                    plugins: <%= isPlugins() %>,  
                    type: '<%= getViewType() %>',  
                    reviewDisplay: '<%= getReviewDisplay() %>',   
                    trackChanges: <%= isTrackChanges() %>,                  
                \};                    
                                      
                // Проверяем корректность сохранённых в переменной свойств документа  
                if (data.fileUrl == "") \{  
                    throw new Error('Путь к файлу не найден');  
                \} else if (data.fileType == "") \{  
                    throw new Error('Данный тип файла не поддерживается Р7 Office');  
                \} else if (data.mode == "") \{  
                    throw new Error('Отсутствует параметр режима просмотра/редактирования')  
                \}  
                else \{                          
                    let mode = checkDocAppEdit();  
                    if (data.mode\!="edit" \&\& mode \!== "") \{  
                        data.mode = mode;  
                    \}  
                    // Создаём окно просмотра/редактирования документа  
                    createEditor(data);  
                \}  
            \} else if(connections\_ch > 0) \{  
                connections\_ch --;  
                setTimeout(waitForDocsApi,500);  
                  
            \}  
            else \{  
                throw new Error('Ошибка ответа API сервера Р7 Office')  
            \}  
        \}  
          
        function checkDocAppEdit() \{  
            var url = window.location.href;  
            var isEdit = url.includes('/edit)');  
            if ('<%= isLegacyEnabled() %>' \!== 'false') \{  
                if (isEdit) \{  
                    return 'edit';  
                \} else \{  
                    return 'view';  
                \}  
            \} else \{  
                return "";  
            \}  
        \}  
  
        setTimeout(waitForDocsApi,1000);  \})();  
  
          
    </script>  
</div>
````

8. ````
Приведённый выше скрипт содержит функции, работу которых необходимо описать на вкладке **Скрипты**. Перейдите на эту вкладку и в правом верхнем углу нажмите **Клиент**. Добавьте скрипт, приведённый ниже. 

Скрипт 

````
/\* window webAPI \*/  
declare const window: any;  
  
/\* Client scripts module \*/  
let fileUrl: string = "";  
let file : FileItem;  
let req: string;  
let user: UserItem;  
/\* Extension settings \*/  
let r7OfficeServerAddress: any;  
let elmaServerAddress: any;  
let enabledFileTypes: any;  
let isLegacyMode: any;  
let isChatEnabled: boolean;  
let isCommentsEnabled: boolean;  
let isMacrosEnabled: boolean;  
let isPluginsEnabled: boolean;  
let isTrackChangesEnabled: boolean;  
let language: string;  
let reviewDisplay: string;  
let randname:string;  
// Признак того, что файл временный и отображается в момент создания элемента приложения типа Документ  
let isNewFilePreview: boolean = false;  
  
function getrandname() \{  
   if(randname)  
   \{  
       return randname;  
   \}  
   else  
   \{  
       let id = getFileId().replace('-','');  
       let key = new Date().valueOf().toString();  
       randname = "placeholder\_"+id+key;  
       return randname;  
   \}  
\}  
  
async function onInit() \{  
    if (Context) \{  
        fileUrl = await getFileUrlInternal() as string;  
        file = await getFileInternal() as FileItem;      
        user = await getUserInternal();  
  
        // Проверка корректности разрешения файла  
        const fileExtension = getFileExtension().toLowerCase();  
        const availableFileExtensions: Array<string> = \["doc","docx","odt","txt", "rtf", "xls","xlsx","ods","csv", "ppt","pptx","odp"\];  
        if (availableFileExtensions.indexOf(fileExtension) <= -1) \{  
            throw new Error('Данный тип файла не поддерживается Р7 Office');  
        \}  
          
        await Server.rpc.writeParametersToContext();  
        readParametersFromContext();  
    \}  
\}  
  
function readParametersFromContext() \{  
    if (Context.data.ExtensionParameters) \{  
        let parameters = JSON.parse(Context.data.ExtensionParameters);  
  
        isLegacyMode = parameters.isLegacy;  
        isChatEnabled = parameters.isChatEnabled ?? false;  
        isCommentsEnabled = parameters.isCommentsEnabled ?? false;  
        isMacrosEnabled = parameters.isMacrosEnabled;  
        isPluginsEnabled = parameters.isPluginsEnabled;  
        isTrackChangesEnabled = parameters.isTrackChangesEnabled;  
        enabledFileTypes = parameters.enabledTypes;  
        elmaServerAddress = parameters.elmaAddress;  
        r7OfficeServerAddress = parameters.r7OfficeAddress;  
        language = parameters.language as string;  
        reviewDisplay = parameters.reviewDisplay as string;  
    \}  
\}  
  
async function canRender() \{  
    const fileType = getFileType();  
      
    if (fileType \!== "") \{  
        return true;  
    \}  
    else \{  
        return false;  
    \}  
\}  
  
/\* Получение адреса сервера из настроек расширения \*/  
  
function getServerAddress() \{  
    const apiPath = "/web-apps/apps/api/documents/api.js";  
      
    if (r7OfficeServerAddress \!== "") \{  
        if (r7OfficeServerAddress.includes(apiPath)) \{  
            return r7OfficeServerAddress;  
        \}  
        else \{  
            return r7OfficeServerAddress + apiPath;  
        \}  
    \}  
    else \{  
          
    \}  
\}  
  
function getCallbackUrl() \{  
    const callbackUrl = "/api/extensions/00e82811-87d6-4d46-a5d4-e444ed8a857a/script/callback";  
      
    return elmaServerAddress+callbackUrl;  
\}  
  
/\* Получение ссылки на скачивание файла с cdn \*/  
  
function getFileUrl(): any \{  
    return fileUrl;  
\}  
  
async function getFileUrlInternal () \{  
     if (Context.data.file) \{           
         const url = await Context.data.file.getDownloadUrl();  
           
         return url;      
     \}  
\}  
  
/\* Получение файла, его расширения и типа \*/  
  
async function getFileInternal() \{      
    if (Context.data.file) \{  
        try \{  
            const file = await Context.data.file.fetch();  
            return file;  
        \} catch (ex) \{  
            isNewFilePreview = true;  
            return undefined;  
        \}  
    \}  
\}  
  
function getFileName() \{  
    if (file \&\& \!isNewFilePreview) \{  
        return file.data.\_\_name;  
    \}  
    else \{  
        return " "  
    \}  
\}  
  
function getFileExtension() \{  
    if (file \&\& \!isNewFilePreview) \{  
        const fileExtension = file.data.\_\_name.substr(file.data.\_\_name.lastIndexOf('.') + 1);  
        return fileExtension;  
    \}  
    else if (Context.data.extension) \{  
        return Context.data.extension\[0\];  
    \}  
    else \{  
        return "";  
    \}  
\}  
  
function getEnabled() \{  
    return enabledFileTypes;  
\}  
  
function getLanguage() \{  
    if (language \&\& language \!= "") \{  
        return language;  
    \} else \{  
        return "ru";  
    \}  
\}  
  
function getFileType() \{  
        let type: string = "";  
  
        const fileExtension = getFileExtension().toLowerCase();  
        const documents: Array<string> = \["doc","docx","odt","txt", "rtf"\];  
        const spreadsheets: Array<string> = \["xls","xlsx","ods","csv"\];  
        const presentations: Array<string> = \["ppt","pptx","odp"\]  
  
        if(typeof enabledFileTypes === "undefined") \{  
            return type;  
        \}  
  
        if (enabledFileTypes.findIndex((i: \{ code: string; \}) => i.code == fileExtension) \!== -1) \{  
            if (documents.indexOf(fileExtension) > -1) \{  
                type = "word";  
            \} else if (spreadsheets.indexOf(fileExtension) > -1) \{  
                type = "cell"  
            \} else if (presentations.indexOf(fileExtension) > -1) \{  
                type = "slide"  
            \}  
        \}  
  
        return type;  
    \}  
  
function getFileId(): any \{  
    if (Context.data.file \!== undefined) \{  
        if (file \!== undefined) \{  
            return file.data.\_\_id;  
        \}  
        else \{  
            return Context.data.file.id;  
        \}  
    \}  
\}  
  
function getFileKey(): any \{  
    let key: string = "";  
    if (Context.data.file \!== undefined) \{  
        if (file \!== undefined) \{  
            let lastUpdatedUnix = file.data.\_\_updatedAt.format("X");  
            key = getFileId() + "." + lastUpdatedUnix;  
        \}  
        else \{  
            key = new Date().valueOf().toString();  
        \}  
    \}  
    return key;  
\}  
  
function getMode(): any \{  
    if (Context.data.action == "edit" || Context.data.action == "editnew") \{  
        return "edit";  
    \} else \{  
        return "view";  
    \}  
\}  
  
async function getUserInternal() \{  
    const user = await System.users.getCurrentUser();  
  
    return user;  
\}  
  
function getUserName(): string \{  
    let userName = user.data.\_\_name.replace('\\'','');  
      
    return userName;  
\}  
  
function getUserId(): string \{  
    return user.data.\_\_id;  
\}  
  
function getViewType(): string \{  
    if (window.innerWidth <= 991) \{  
        return "mobile";  
    \}  
    return "desktop";  
\}  
  
function isLegacyEnabled(): boolean \{  
    return isLegacyMode;  
\}  
  
function isChat(): boolean \{  
    return isChatEnabled;  
\}  
  
function isComments(): boolean \{  
    return isCommentsEnabled;  
\}  
  
function isMacros(): boolean \{  
    return isMacrosEnabled;  
\}  
  
function isPlugins(): boolean \{  
    return isPluginsEnabled;  
\}  
  
function getReviewDisplay(): string \{  
    return reviewDisplay  
\}  
  
function isTrackChanges(): boolean \{  
    return isTrackChangesEnabled;  
\}
````

9. ````
На вкладке **Скрипты** в правом верхнем углу нажмите кнопку **Сервер**. Добавьте скрипт, приведённый ниже.

Скрипт 

````
/\* Server scripts module \*/  
async function writeParametersToContext(): Promise<void> \{  
    let elmaAddress = Namespace.params.data.Elma365ServerAddress;  
    let onlyOfficeAddress = Namespace.params.data.r7OfficeServerAddress;  
    let enabledTypes = Namespace.params.data.SupportedFileTypes;  
  
    let isLegacy = Namespace.params.data.LegacyModeDetectionEnabled;  
    let isChatEnabled = Namespace.params.data.ChatEnabled;  
    let isCommentsEnabled = Namespace.params.data.CommentsEnabled;  
    let isMacrosEnabled = Namespace.params.data.MacrosEnabled;  
    let isPluginsEnabled = Namespace.params.data.PluginsEnabled;  
    let isTrackChangesEnabled = Namespace.params.data.TrackChangesEnabled;  
  
    let interfaceLanguage = Namespace.params.data.InterfaceLanguage;  
    let language = interfaceLanguage.code;  
    let reviewDisplayCategory = Namespace.params.data.ReviewDisplay;  
    let reviewDisplay = reviewDisplayCategory?.code || "original";  
  
    Context.data.ExtensionParameters = JSON.stringify(\{isLegacy, elmaAddress, onlyOfficeAddress, enabledTypes, language, isChatEnabled, isCommentsEnabled, isMacrosEnabled, isPluginsEnabled, isTrackChangesEnabled, reviewDisplay\});  
\}
````

10. ````
В левом верхнем углу нажмите кнопку **Сохранить**, а затем **Проверить**, чтобы убедиться, что виджет составлен верно. Затем нажмите **Опубликовать**. 

11. Закройте дизайнер интерфейсов. В настройках модуля перейдите на вкладку **Методы API**. Создайте [новый метод](extention-api.md). Добавьте скрипт, размещённый ниже. 

Скрипт 

````
async function callback(req: FetchRequest): Promise<HttpResponse | void> \{  
    let responseCode = 0;  
    if (\!req.body) \{  
        throw new Error('Empty request body');  
    \}      
  
    // Получаем тело запроса от Р7-Офис  
    let requestText = String(req.body);  
  
    if (\!requestText) \{  
        throw new Error('Wrong request body');  
    \}  
    // Преобразуем в объект  
    let parsedData = JSON.parse(requestText);  
    // Статус 2 означает, что документ был изменён и закрыт, а значит его надо пересохранить — добавить ему версию в ELMA365  
    if (parsedData.status == 2) \{  
        let fileId = parsedData.key.split('.').shift();  
        let url = parsedData.url;  
        let file = await System.files.search().where(f => f.\_\_id.eq(fileId)).first();  
  
        if (file \&\& url) \{  
            const fetchedFile = await fetch(url);  
            if (\!fetchedFile) \{  
                responseCode = 1;  
            \} else \{  
                const buf = await fetchedFile.arrayBuffer();  
                file.addVersion(file.data.\_\_name, buf);  
            \}  
        \}  
    \}  
  
    const resp = new HttpResponse();  
    resp.json(\{"error":responseCode\})  
  
    return resp;  
\}
````

````
После этого при включённом модуле вы сможете просматривать файлы с форматами, указанными в настройках этого модуля, в интерфейсе ELMA365. 