Делаем свой текстовый редактор с автосохранением
Это не так сложно, как звучит.
Сделать собственный текстовый редактор гораздо проще, чем кажется. Сейчас мы соорудим такой, и вы сами в этом убедитесь.
В этой статье мы соберем текстовый редактор, который будет работать в браузере. В помощь нам три технологии:
Общая идея
У нас будет HTML-страница, на ней будет блок, похожий на лист бумаги. У него будет включен content editable, то есть внутри этого блока можно будет что-то писать. После каждого нажатия клавиши содержимое этого блока будет записываться во внутреннюю память браузера.
Вторая часть алгоритма — при загрузке страницы взять из памяти тот текст, что там был раньше, и показать его в нашей текстовой области. Страницу можно обновлять как угодно и даже выключать компьютер — текст всё равно останется в памяти.
Если записать алгоритм кратко, то он будет выглядеть так:
Пункты 3 и 4 выполняются непрерывно до тех пор, пока вы не закроете страницу.
Готовим каркас
Пойдём уже привычным способом и создадим новый HTML-файл, в котором будут прописаны все нужные блоки. Мы так уже делали в статьях про генератор паролей и спортивный таймер.
Сохраняем как html-файл, открываем его в браузере и видим пустой экран. Это нормально, сейчас будем наполнять.
Расставляем содержимое
Нам нужен только заголовок, который объяснит нам, где мы находимся, и большое пространство для ввода текста. За текстовое поле будет отвечать блок
Разместим это в разделе :
Настраиваем стили
Стили задают внешний вид страницы и любых элементов на ней. Сделаем наш заголовок опрятнее:
Сохраняем, обновляем и смотрим на результат:
Пишем скрипт
Теперь нужно научить редактор сначала извлекать из памяти прошлый текст, а потом снова запоминать каждое нажатие клавиши. Всё будем делать через localStorage, как с ним работать — рассказываем в статье про список задач.
Прокачиваем собственный текстовый редактор
Добавляем поддержку неограниченного числа документов, новое оформление и безграничные возможности настройки.
Настало время доделать наш текстовый редактор и добавить в него возможность работы с несколькими документами. Если вы не знаете, о чём идёт речь — прочитайте наши статьи о том, как сделать редактор с автосохранением текста, и как улучшить его внешний вид.
Алгоритм будет такой:
Чтобы не тратить время и силы зря, используем для списка документов уже готовый код из статьи про список задач. Мы объединим эти две программы в одну — программисты часто пользуются этим подходом, чтобы быстро получить нужный результат.
Добавляем список документов
Сделаем наш список документов слева, но настроим его поведение таким образом, чтобы на маленьких экранах он занимал всю ширину браузера. Используем для этого Бутстрап, с которым мы уже познакомились, — он сам за нас сделает всю работу по адаптивной вёрстке. Сначала подключим его:
Теперь изменим код заголовка страницы — положим его в отдельный контейнер и зададим размеры:
Затем сделаем список документов. Для этого мы воспользуемся своим же готовым кодом из статьи про список задач и адаптируем его под наши цели — меняем текст, оборачиваем код в новый контейнер и задаём размер блоков для каждого размера экрана. Ниже тоже делаем отдельный блок с окном редактора, у которого размеры свои:
Как видите, мы взяли наш старый рабочий код, немного поменяли текст в заголовках, добавили фреймворк и использовали в новой программе. Быстро и удобно.
Настраиваем внешний вид
Раз мы добавили новые теги и стили, нужно их прописать в разделе
Пишем свой текстовый редактор на C: часть 1
Привет, Хабр! Представляю вашему вниманию перевод статьи «Build Your Own Text Editor» автора Джереми Рутена.
Свой текстовый редактор!
Привет! Это вольный перевод о том, как написать свой никому не нужный текстовый редактор на C.
Итак, что сможет делать наш редактор?
Еще чуть-чуть о реализации редактора:
Всего туториал состоит из 184 шагов. Каждый шаг вы будете добавлять, изменять или удалять разные строки кода. На большинстве шагов вы сможете наблюдать за внесенными изменениями просто скомпилировав и запустив программу.
Я попробую объяснить каждый шаг на этом пути, иногда очень подробно. Не бойтесь пропускать теорию, потому что суть в самом написании кода и то, что вы вообще решились на это! Всё, что вы узнаете в процессе — это бонус, будет чему научится просто вводя изменения в код и наблюдая за результатами.
Настройки
Первый шаг, новый файл и всё всё всё с чистого листа…
Для начала убедитесь в том, что ваша среда корректно настроена для вашего ЯП (в нашем случае — для C), а так же вы хотя бы примерно понимаете, как компилировать и запускать вашу программу в этой среде.
К нашему счастью, редактор, который мы напишем не зависит от каких-либо внешних библиотек. Единственное, что нам понадобится, так это компилятор и стандартная библиотека языка C, с которой он и поставляется. Мы так же будем использовать make-скрипт для упрощения команд компиляции. Удостоверьтесь, что у вас есть как и компилятор для C, так и make.
Компилятор языка C
Так как C компилируемый язык, нам нужен, как не странно, компилятор, если у вас его нет, его обязательно нужно установить, как и make. Например, в Ubuntu, это можно сделать так:
Функция main()
Итак, вот, с чего начинается наш путь! Создайте файл kilo.c и напишите в нем базовую main() функцию.
Шаг 1
(не обязательно, что бы ваш редактор назывался так же, как и в статье, вы вполне можете назвать свой редактор «uber_mega_super_redaktor1337» другим эстетичным названием)
Упрощение компилирования с make
Создайте новый Makefile с подобным содержимым:
Шаг 2
Первая линия Makefile-а говорит нам о том, что мы хотим скомпилировать и то, что нам для этого потребуется. Вторая линия определяет команду, которую будет выполнять make скрипт. $(CC) обычно ссылается на команду cc.
Что это за магические слова появились?
Это флаги, а именно:
Теперь, когда Makefile настроен, попробуйте написать команду make в ваш терминал для компиляции программы. Запустить её можно так же, как и всегда, ./kilo.
Продолжение следует.
Создаем текстовый редактор на React.js
Введение
Привет! Меня зовут Данила, и я фронтенд-разработчик в KTS.
Однажды в одном из своих проектов мне потребовалось реализовать кастомный текстовый редактор на React.js. Задача показалась мне довольно интересной, и я решил рассказать о своем опыте. В статье я поэтапно покажу, как можно создать текстовый редактор с базовыми функциями.
Надеюсь, информация будет полезной и сэкономит кому-то время и силы.
Что будет в статье:
1. Описание задачи
Сформируем список требований для нашего редактора. Он должен:
Иметь предустановленные стили элементов — заголовки, списки и т.д
Форматировать стили текста — жирность, курсив и т.д
Поддерживать интерактивные элементы — например, ссылки
Работать с сочетанием клавиш
Импортировать/экспортировать контент в HTML
Я понимал, что реализовывать все с нуля сложно и долго, так как браузеры могут по-разному обрабатывать нативные события работы с текстом.
Поэтому я начал искать информацию о том, как другие разработчики решали похожие задачи, и с какими подводными камнями они сталкивались. Выяснилось, что одним из популярных решений является пакет Draft.js.
2. Что такое Draft.js?
В 2016 году инженеры Facebook представили пакет для работы с текстом на React Conf. Draft.js — это фреймворк для работы с текстом на React.js. Он позволяет создать состояние редактора, которое будет хранить всю информацию о контенте, о положении курсора и многом другом. А также предоставляет кроссбраузерные функции для удобного изменения этого состояния. Draft.js работает с имутабельными данными при помощи immutable.js. Это означает, что при изменении состояния мы полностью перезаписываем его новой версией.
Контент в редакторе на Draft.js строится из блоков. Блок — структура данных, в которой хранится информация о тексте внутри него. Каждому блоку можно задавать свои уникальные данные и настройки его рендера: задавать тег, стили, атрибуты или просто указать React-компонент. К любому фрагменту текста блока можно применить inline-стили (например, сделать жирным). А для создания более сложных и интерактивных элементов в Draft.js есть системы Entities и Decorators. С их помощью можно рендерить произвольные React-компоненты и связывать их с фрагментами текста.
Более подробную информацию можно найти на официальном сайте фреймворка.
3. Архитектура
Наш компонент будет состоять из нескольких частей:
Весь API нашего редактора вынесем в отдельный React-hook useEditor.
Дизайн редактора может быть произвольным, и элементы управления могут находится далеко от самого окна редактора. Для того, чтобы не пробрасывать API редактора через props-ы множества компонентов, воспользуемся React Context. C его помощью компоненты смогут легко получить нужные данные и функции.
Таким образом, схема редактора будет выглядеть так:
4. Начинаем кодить
Чтобы не тратить время на создание и настойку сборки, воспользуемся Create React App. Код будет написан на Typescript, поэтому создадим проект с соответствующим шаблоном:
Установим необходимые зависимости:
draft-js — фреймворк Draft.js
draft-convert — библиотека для удобного импортирования и экспортирования данных из Draft.js. Более подробно о ней поговорим в блоке «Экспорт-импорт html в редактор».
Установим типы для вышеописанных библиотек:
Создадим файл useEditor.tsx. Данный файл будет содержать React-hook, в котором мы будем описывать всю логику нашего редактора:
С помощью метода EditorState.createEmpty() мы создаем пустое имутабельное состояние нашего редактора и сохраняем его в локальном состоянии. Постепенно мы будем добавлять в него функции и логику.
Создадим React Context, с помощью которого будем прокидывать API редактора:
Теперь в любом месте нашего приложения мы можем получить доступ к функциям редактора с помощью хука useEditorApi:
Добавим провайдер на страницу:
Создадим компонент окна редактора TextEditor:
Мы подключили базовый компонент Editor из пакета Draft.js. Именно он создаст редактируемое поле и будет управлять содержимым. Связываем его c ранее созданным API редактора.
Создадим компонент панели инструментов:
5. Создание простых блоков
Мы создали редактор, но пока он ни чем не отличается от простого textarea. Добавим возможность создания блоков.
По умолчанию Draft.js содержит настройки основных типов блоков. Полный список можно найти тут.
Создадим файл конфигурации config.ts:
Мы описали enum с названиями типов блоков, а также добавили новый блок сноски cite. Но пока редактор ничего не знает о том, как обрабатывать новые типы блоков. Для того, чтобы добавить произвольные блоки в Draft.js, необходимо создать имутабельную карту блоков. Воспользуемся методом Immutable.Map из пакета immutable (он устанавливается вместе с Draft.js). Мы описали название нашего нового блока (cite) и указали название тега, с которым он должен выводиться в DOM. Для того, чтобы не описывать стандартные блоки, объединим карту блоков по умолчанию с нашей при помощи метода DefaultDraftBlockRenderMap.merge:
Далее укажем редактору новую конфигурацию блоков:
Теперь наш редактор умеет обрабатывать типы блоков. Для создания элементов управления типом блоков, нам необходимо добавить в хук useEditor два поля:
toggleBlockType — функция переключения типа блока;
currentBlockType — переменная со значением текущего типа блока, с помощью которой можно будет добавить элементу активное состояние.
Draft.js содержит класс RichUtils со вспомогательными методами для редактирования текста. Для реализации toggleBlockType воспользуемся методом RichUtils.toggleBlockType, чтобы применить определенный тип блока к текущему состоянию редактора:
Реализация currentBlockType будет выглядеть следующим образом:
Разберем код подробнее.
Шаг 2: получаем карту контента нашего редактора.
Шаг 3: по ключу находим блок, в котором сейчас находимся. Ключ — это просто уникальный хеш, который сгенерировал Draft.js.
Шаг 4: получаем тип найденного блока.
Теперь у нас есть все необходимое, чтобы создать элементы управления типами блоков.
6. Inline-cтили текста
Теперь создадим функции применения стилей к выделенному тексту.
Draft.js содержит встроенные типы стилей для inline-cтилей. Воспользуемся ими и добавим свой произвольный тип. Наш стиль будет менять цвет фона и цвет шрифта текста.
Для начала опишем в нашем конфиге enum для inline-cтилей:
Так как у нас появился произвольный стиль, нам нужно описать, какие стили он будет применять. Для этого создадим карту:
И теперь нам необходимо подключить карту в Editor:
Теперь редактор знает, как обрабатывать наши стили. Далее нужно реализовать кнопки управления inline-cтилями:
toggleInlineStyle — функция включения/выключения inline-cтиля;
hasInlineStyle — функция, которая укажет, применен ли конкретный стиль для выделенного текста.
Для реализации этой задачи мы снова можем воспользоваться RichUtils:
Теперь мы легко можем добавить кнопки управления inline-cтилями:
7. Создание интерактивных элементов
Рассмотрим процесс создания интерактивных элементов на примере вставки ссылок. Для этого мы воспользуемся Entities. Entity — объект, который хранит мета-данные для определенного фрагмента текста. У него есть три свойства:
type — название типа Entity
mutability — тип привязки к тексту (подробнее об этом будет ниже)
Создадим в конфиге перечисление типов Entity:
Далее создадим React-компонент Link, именно он будет отображаться на месте ссылок:
Стоит отметить, что при создании каждой Entity присваивается уникальный хеш-ключ. С помощью него мы можем получить доступ к сохраненным мета-данным, в данном случае к — url. Его мы будем задавать при создании Entity-ссылки.
Для того чтобы Draft.js понимал, к какому фрагменту текста привязана Entity, существует система Decorators.
Декоратор состоит из трех частей:
strategy — функция поиска фрагмента текста на месте которого нужно отобразить компонент
component — компонент, который нужно отобразить
props — пропсы которые нужно передать компоненту.
Создадим декоратор для привязки ссылок:
Теперь мы можем подключить созданный декоратор к нашему редактору:
Отлично! Теперь наш редактор умеет обрабатывать ссылки. Теперь научим его редактировать и добавлять их.
Так как в редакторе может быть несколько типов Entity, cоздадим общую функцию для добавления Entity. Она будет доступна только внутри хука и не будет доступна в компонентах. Для добавления типа ссылки создадим отдельную функцию addLink, она будет доступна в API редактора:
Стоит отметить, что Entity имеет три режима привязки к тексту:
MUTABLE — текст может быть изменен
IMMUTABLE — при изменении текста Entity будет удален
SEGMENTED — работает так же как IMMUTABLE, с той лишь разницей, что, если текст состоит из нескольких слов, то при удалении символа, удаляется слово, но оставшиеся слова остаются привязанными к Entity. (Пример: «Маша мы|ла раму» → [backspace] → «Маша раму»)
Мы выбрали MUTABLE, так как текст ссылки может быть редактируемым.
Теперь мы легко можем реализовать кнопку добавления ссылки. Чтобы указать адрес ссылки, воспользуемся prompt:
Ссылки добавляются. Давайте доработаем редактор, чтобы можно было редактировать url уже созданной ссылки.
Для упрощения примера сделаем так, чтобы при клике на ссылку в редакторе снова появлялся prompt с предзаполненным url.
Обработаем клик в компоненте:
И создадим функцию, с помощью которой мы сможем перезаписывать данные Entity:
Стоит отметить, что существует несколько типов изменения состояния редактора. Каждый из них нужен, чтобы Draft.js правильно определял, как изменение влияет на некоторые функции: например, повтор и отмена.
Вот и все. Теперь мы можем легко добавлять и редактировать произвольные Entity в нашем редакторе.
8. Обработка сочетаний клавиш
Во многих текстовых редакторах можно редактировать контент с помощью сочетания клавиш. В Draft.js есть механизм для реализации такого поведения. Сочетание клавиш должно выполнять определенное действие — команду. Создадим функцию обработки этих команд.
Функция принимает на вход название команды и текущее состояние редактора, и должна вернуть одно из двух значений:
handled — команда применена
not-handled — команда не применена.
Во избежание гонки обновлений состояния всегда нужно стараться работать с последней актуальной версией. Подробнее об этом можно прочитать тут.
С помощью RichUtils.handleKeyCommand мы добавим обработку стандартных сочетаний клавиш. Например, ctrl + I или cmd + I для применения ранее созданного стиля ITALIC.
Далее необходимо прокинуть нашу функцию в компонент редактора:
Отлично, у нас есть обработка стандартных сочетаний клавиш. Но мы пойдем дальше и добавим свою комбинацию для нашего произвольного стиля текста ACCENT. Для начала расширим тип стандартных команд, чтобы ts на нас не ругался:
Далее создадим функцию для обработки нажатия клавиш:
Обратите внимание, что мы используем функцию getDefaultKeyBinding. Именно она содержит логику по стандартным сочетаниям клавиш. Еще мы воспользовались утилитой KeyBindingUtil.hasCommandModifier для того, чтобы определять, когда пользователь зажал командную клавишу: ctrl или cmd. Таким образом, при нажатии клавиш q + ctrl или q + cmd будет выполнена команда accent. Теперь нужно ее обработать в handleKeyCommand:
Так мы можем добавлять произвольные сочетания клавиш.
9. Экспорт/ Импорт HTML в редактор
Неотъемлемой частью текстового редактора является возможность экспорта и импорта данных. В Draft.js есть функция convertToRaw, которая позволяет выгрузить json с данными о всем контенте.
На основе этих данных можно сформировать HTML. Побродив по просторам сети и npm, я нашел несколько пакетов, которые позволяли подробно указать, как конвертировать контент в HTML и из HTML. Взвесив все за и против, я остановился на пакете draft-convert (который мы установили при сетапе проекта), так как он позволял удобно реализовать желаемые возможности.
Создадим файл convert.tsx — в нем мы объявим функции для конвертирования состояния редактора в HTML и обратно.
Создадим функцию конвертирования состояния редактора в HTML. В ней мы опишем правила, по которым хотим конвертировать данные редактора в разметку:
Чтобы воспользоваться нашим конвертером, создадим функцию toHtml:
И для примера добавим кнопку, которая будет выводить сгенерированный HTML в консоль:
Отлично, теперь мы можем экспортировать HTML из нашего редактора. Теперь добавим возможность импорта первоначального контента. Опишем правила конвертации HTML в данные редактора:
Теперь подключим конвертер в HTML к редактору. Для этого воспользуемся стандартным методом EditorState.createWithContent и доработаем EditorApi:
Теперь нам остается только передать HTML и ура! Наш редактор умеет загружаться с предустановленным контентом.
10. Вместо заключения
Прогресс не стоит на месте, с каждым днем появляются все более новые и навороченные api и библиотеки. В статье я постарался показать, как в свое время я решал данную задачку. Правда, в моем случае на проекте был подключен Mobx, и все возможности хука useEditor были реализованы в виде класса. Но, так как задача довольно обширная, я решил не перегружать статью и использовать стандартные и всеми известные хуки React.js.
Полный код из статьи можно найти по этой ссылке.
Если вы тоже решали подобные задачи, поделитесь в комментариях — какими инструментами вы пользовались?
Простое Windows-приложение — текстовый редактор
Рассмотрим пример проектирования стандартного оконного приложения. Простейшая последовательность действий:
1) визуальное проектирование интерфейса (перенос на форму с Панели элементов необходимых визуальных и не визуальных элементов);
2) генерация заготовок методов обработки событий, связанных с элементами управления;
3) программирование методов обработки событий.
Постановка задачи
Создать текстовый редактор с обязательными функциями работы с файлами «Открыть» и «Сохранить как», а также функциями редактирования текста. Выбор действий с файлами осуществлять через главное меню.
Реализация
Разместим на форме визуальный элемент textBox1 класса TextBox. Размер элемента сделайте чуть меньше размера формы, сместив его вниз от заголовка на 30-40 пикселей. Задайте свойство textBox1.MultiLine = true (для редактирования текста в несколько строк).
Для выбора имен файлов для их чтения и записи перетащим на эту же панель элементы openFileDialog1 (класс OpenFileDialog) и saveFileDialog1 (класс SaveFileDialog).
Кликнув по кнопке «Введите здесь», введите имя раздела меню «Файл» и добавьте ниже следующие пункты меню работы с файлами «Открыть», «Сохранить как» и «Выход». Ваша форма (вместе с панелью невизуальных элементов) будет выглядеть примерно так:
Примечание: для наглядности изменено свойство формы BackColor = Color.Peru. Первая группа действий закончена.
Вторая группа действий обеспечивает генерацию заголовков методов обработки событий, связанных к кнопками меню. Для этого дважды нажмите каждую из трех позиций меню, а также событию Load формы Form1 на закладке «События» панели «Свойства» поставьте в соответствие метод Form1_Load (двойной клик справа от в строке Load).
Откроем форму в режиме Кода (файл Form1.cs):
Перейдем к третьей группе действий — написанию кода для этих четырех методов.
Метод Form1_Load( ) используем для очистки поля компонента textBox1, для задания форматов файловых диалогов и имени файла — контрольного примера при его открытии:
Комментарий. При загрузке формы мы задаем свойство FileName объекта openFileDialog1 указанием имени файла для открытия, а также задаем фильтры для диалогов открытия и сохранения файлов. Сравните работу программы без использования этого метода.
В методе открытьToolStripMenuItem_Click( ) используется компонент openFileDialog1 для выбора имени файла для чтения. Если имя не выбрано (FileName = String.Empty), то работа метода завершается. Иначе создается новый экземпляр класса System.IO.StreamReader (var Читатель) с указанием имени файла и кодировки, данные из текстового файла переносятся в textBox1, объект Читатель закрывается. Добавлена обработка исключений, которые могут возникнуть при открытии файла:
Аналогично выполняется запись (сохранение) текстового файла:
Последний метод — закрытие формы — реализуется одним оператором:
Подготовим в блокноте текстовый файл Text2.txt и разместим его в подкаталоге data папки, где будет размещено ваше приложение. Запустим программу на выполнение. Добавьте в окне редактора несколько строк, сохраните файл с другим именем. Откройте новый файл и удалите часть текста в окне редактора.
Заметим, что работают стандартные операции выделения текста, копирования, вставки и удаления части текста с использованием мыши и комбинаций управляющих клавиш, используемых в известных текстовых редакторах.
ВЫВОД: В первом приближении поставленная задача решена. Данный пример приведен всего лишь для понимания:
во-первых, алгоритма действий разработчика визуального приложения «интерфейс, события-методы, реализация»;
во-вторых, удобства использования готовых компонентов (нам не пришлось программировать операции редактирования текста — стандартные события уже привязаны к компоненту textBox1);
в-третьих, целесообразности использования готовых компонентов (файловых диалогов) с точки зрения стандартизации интерфейса пользователя и программы.
Напомним, что в статье про классы мы отмечали, что в языке C# предусмотрено несколько разновидностей данных-членов и функций-членов. Пока мы подробно рассмотрели только поля и константы — как данные-члены, а также методы — как функции-члены класса. В следующих статьях мы рассмотрим события, как данные-члены класса и трехзвенную цепочку события-делегаты-методы. После чего вернемся к член-функциям класса: свойствам, конструкторам, финализаторам, операциям и индексаторам.