Несколько клиентов обратились к нам и спросили, как они могут создать веб-приложение, подобное Google Docs, с помощью наших API. Документы Google – это текстовый процессор, который позволяет пользователям создавать и редактировать файлы в Интернете, а также сотрудничать с другими пользователями в режиме реального времени.

В этом сообщении в блоге объясняется, как легко создать облегченную версию Документов Google со следующими функциями:

  • Редактирование расширенного текста (изменение шрифта текста, размера, цвета, стиля (полужирный, курсив), выравнивание и т. д.).
  • Совместное редактирование одного и того же документа в режиме реального времени. Несколько пользователей могут одновременно получать доступ к документу и изменять его.
  • Загрузите содержимое существующего документа Word в редактор.
  • Сохраните текст в редакторе как документ MS Word, PDF, TXT или HTML.

Наш конечный продукт будет выглядеть следующим образом:

Документы Google как интерфейс приложения

Инструменты и технологии — создавайте Google Docs как приложение

Мы разработаем Google Docs как веб-приложение в ASP.NET Core и будем использовать следующие две библиотеки:

  • Пожарная площадка is an open-source, collaborative text editor. It uses the Firebase Realtime Database as a backend so it requires no server-side code and can be added to any web app simply by including the JavaScript files.
  • GroupDocs.Editor для .NET gives us an ability to edit most popular document formats using any WYSIWYG editor without any additional applications. We will load document via GroupDocs.Editor into Firepad, edit document in a way we want and save it back to original document format.

Я использовал Visual Studio для Mac в качестве IDE. Однако вы можете загрузить бесплатную версию Visual Studio для сообщества, в зависимости от вашей платформы, здесь. Давайте начнем.

Создайте новый проект веб-приложения ASP.NET Core и назовите его «GoogleDocsLite».

Создайте новое основное веб-приложение ASP.NET.

Запустите приложение, чтобы убедиться, что все настроено правильно.

Интеграция Firepad

Мы можем добавить Firepad в наше веб-приложение, включив следующие файлы JavaScript в раздел Layout.cshtml.

<!-- Firebase -->
<script src="https://www.gstatic.com/firebasejs/7.13.2/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.13.2/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.13.2/firebase-database.js"></script>

<!-- CodeMirror -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.17.0/codemirror.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.17.0/codemirror.css" />

<!-- Firepad -->
<link rel="stylesheet" href="https://firepad.io/releases/v1.5.9/firepad.css" />
<script src="https://firepad.io/releases/v1.5.9/firepad.min.js"></script>

<!-- userlist -->
<script src="~/js/firepad-userlist.js"></script>
<link rel="stylesheet" href="~/css/firepad-userlist.css" />

Чтобы создать Firepad, мы инициализируем Firebase, CodeMirror, а затем Firepad. Добавьте следующий скрипт и код HTML в Index.cshtml.

<script>
    function init() {
        // Initialize Firebase.
        // TODO: replace with your Firebase project configuration.
        var config = {
            apiKey: '',
            authDomain: "",
            databaseURL: ""
        };
        firebase.initializeApp(config);
        
        // Get Firebase Database reference.
        var firepadRef = getExampleRef();
        
        // Create CodeMirror (with lineWrapping on).
        var codeMirror = CodeMirror(document.getElementById('firepad'), { lineWrapping: true });

        // Create a random ID to use as our user ID (we must give this to firepad and FirepadUserList).
        var userId = Math.floor(Math.random() * 9999999999).toString();

        // Create Firepad (with rich text features and our desired userId).
        var firepad = Firepad.fromCodeMirror(firepadRef, codeMirror,
                    { richTextToolbar: true, richTextShortcuts: true, userId: userId });

        // Create FirepadUserList (with our desired userId).
        var firepadUserList = FirepadUserList.fromDiv(firepadRef.child('users'),
        document.getElementById('userlist'), userId);
    }
    
    // Helper to get hash from end of URL or generate a random one.
    function getExampleRef() {
        var ref = firebase.database().ref();
        var hash = window.location.hash.replace(/#/g, '');
        if (hash) {
            ref = ref.child(hash);
        } else {
            ref = ref.push(); // generate unique location.
            window.location = window.location + '#' + ref.key; // add it as a hash to the URL.
        }
        if (typeof console !== 'undefined') {
            console.log('Firebase data: ', ref.toString());
        }
        return ref;
    }
</script>

<div id="userlist"></div>
<div id="firepad"></div>

Пожалуйста, замените содержимое config конфигурацией вашего собственного проекта Firebase.

Мы хотим, чтобы приведенный выше скрипт выполнялся после того, как веб-страница полностью загрузит весь контент (файлы скриптов, файлы CSS и т. д.). Итак, вызовите функцию init() из атрибута события onLoad элемент в Layout.cshtml.

<body onload="init()">

Ваш <body> должен выглядеть следующим образом. Если он содержит ненужные теги, такие как <header> , <footer> , пожалуйста, удалите их.

Элемент тела

Если вы запустите проект, вы заметите, что firepad и список пользователей не выровнены должным образом. Пожалуйста, используйте следующий код CSS, чтобы настроить размер/положение панели управления и списка пользователей. Вы можете добавить следующий код в элемент Layout.cshtml.

<style>
    html {
        height: 100%;
    }

    body {
        margin: 0;
        height: 100%;
    }

    /* We make the user list 175px and firepad fill the rest of the page. */
    #userlist {
        position: absolute;
        left: 0;
        top: 50px;
        bottom: 0;
        height: auto;
        width: 175px;
    }

    #firepad {
        position: absolute;
        left: 175px;
        top: 50px;
        bottom: 0;
        right: 0;
        height: auto;
    }
</style>

Firepad успешно настроен.

Загрузить содержимое существующего документа Word в редактор

Теперь мы хотим дать нашим пользователям возможность загружать содержимое существующего документа Word в текстовом редакторе. На фронтенде мы добавляем элемент типа файл, который позволяет пользователю выбрать документ Word на своем локальном компьютере. На серверной части мы используем библиотеку GroupDocs.Editor для извлечения содержимого документа Word в виде строки HTML. Наконец, мы используем метод setHtml() Firepad для отображения контента в текстовом редакторе.

Добавьте следующее элемент в файле Index.cshtml перед ярлык.

<form method="post" enctype="multipart/form-data" id="uploadForm">
    <input asp-for="UploadedDocument" />
    <input type="submit" value="Upload Document" class="btn btn-primary" asp-page-handler="UploadDocument" />
</form>

В файле Index.cshtml.cs определите соответствующее свойство.

[BindProperty]
public IFormFile UploadedDocument { get;  set; }

Запустите проект и нажмите кнопку «Выбрать файл». Выберите документ Word, который вы хотите загрузить, и нажмите кнопку «Загрузить документ». Ничего не произойдет, потому что мы еще не определили обработчик в Index.cshtml.cs. Прежде чем мы это сделаем, давайте сначала добавим в наш проект библиотеку GroupDocs.Editor.

Интегрировать GroupDocs.Editor

GroupDocs.Editor доступен в виде пакета NuGet, поэтому мы можем легко добавить его в наш проект. Щелкните проект правой кнопкой мыши и выберите параметр «Управление пакетами NuGet». Откроется окно «Управление пакетами NuGet», выберите вкладку «Обзор» и введите GroupDocs.Editor в поле поиска. GroupDocs.Editor должен появиться в качестве первого результата, выберите его и нажмите кнопку «Добавить пакет».

Добавьте GroupDocs.Editor через диспетчер пакетов NuGet.

Когда пакет будет успешно добавлен, он появится в подпапке NuGet в папке Dependencies.

Обработка данных формы

Теперь пишем обработчик (метод OnPostUploadDocument()), который будет вызываться, когда пользователь нажимает кнопку «Загрузить документ». Объект UploadedDocument (типа IFormFile) содержит содержимое загруженного документа. Сначала мы сохраняем документ на сервере, а затем используем библиотеку GroupDocs.Editor, чтобы получить его содержимое в виде строки HTML. Добавьте следующий код в файл Index.cshtml.cs.

private readonly IWebHostEnvironment _hostingEnvironment;

public string DocumentContent { get; set; }

public IndexModel(IWebHostEnvironment hostingEnvironment)
{
    _hostingEnvironment = hostingEnvironment;
}

public void OnPostUploadDocument()
{
    var projectRootPath = Path.Combine(_hostingEnvironment.ContentRootPath, "UploadedDocuments");
    var filePath = Path.Combine(projectRootPath, UploadedDocument.FileName);
    UploadedDocument.CopyTo(new FileStream(filePath, FileMode.Create));
    ShowDocumentContentInTextEditor(filePath);
}

private void ShowDocumentContentInTextEditor(string filePath)
{
    WordProcessingLoadOptions loadOptions = new WordProcessingLoadOptions();
    Editor editor = new Editor(filePath, delegate { return loadOptions; }); //passing path and load options (via delegate) to the constructor
    EditableDocument document = editor.Edit(new WordProcessingEditOptions()); //opening document for editing with format-specific edit options

    DocumentContent = document.GetContent();
}

Firepad предоставляет два события для прослушивания. Один из них «готов», который срабатывает, как только Firepad получает начальное содержимое редактора. Мы присоединяем обратный вызов к этому типу события, и в обратном вызове мы передаем строку DocumentContent в качестве аргумента методу setHtml() объекта firepad. Добавьте следующий код в функцию init() в Index.cshtml.

firepad.on('ready', function () {
    if (firepad.isHistoryEmpty()) {
        var documentContent = '@Model.DocumentContent';
        if (documentContent.length != 0) {   
            firepad.setHtml(htmlDecode(documentContent));
        } else {
            firepad.setText("Welcome to your own private pad! Share the URL above and collaborate with your friends.");
        }
    }
});

Возможно, вы заметили, что мы передали строку documentContent сначала методу htmlDecode(), прежде чем перейти к методу setHtml(). Это замена символьных объектов, таких как <, >, знаками (< и >). Метод htmlDecode() выглядит следующим образом.

function htmlDecode(input) {
    var e = document.createElement('div');
    e.innerHTML = input;
    return e.childNodes[0].nodeValue;
}

Запустите проект, теперь вы сможете загрузить содержимое документа Word в редактор.

В части II этого поста я объяснил, как мы можем расширить наше приложение, включив в него следующие функции:

  • Загрузите содержимое редактора в виде документа MS Word, PDF, TXT или HTML.
  • Поделитесь URL-адресом с друзьями, чтобы они могли одновременно редактировать документ.

Пожалуйста, проверьте.

Полный исходный код проекта доступен на GitHub.

Смотрите также