Сделать автосохранение в Эгее
Я хочу сделать во всех формах Эгеи автосохранение. Ниже — мои рассуждения на этот счёт. А в конце я позову вас на помощь.
Сейчас в редакторе заметок можно нажать ⌘S в любой момент, и изменения сохранятся на сервере без перезагрузки страницы. Это удобно, но иногда люди, особенно после Гугль-дока, забывают это сделать. Если что-нибудь сломается, текст пропадёт.
Нетрудно сделать автоматическое сохранение раз в несколько секунд. Но такое нельзя делать с опубликованными заметками, ведь изменения будут сразу видны всем, включая промежуточные состояния по ходу внесения правок. Можно, конечно, поддерживать это только в черновиках, но это криво и модально. Хочется, чтобы как-нибудь работало везде.
Для опубликованных заметок можно предусмотреть в базе на сервере поле «неопубликованная версия текста», чтобы все такие вот автосохранённые изменения писались туда, а не в настоящее поле, а публиковались только когда человек руками жмёт кнопку «Сохранить». Но, во-первых, тогда такие дублирующие поля придётся заводить на всё — заголовок, теги, урл. Неэлегантно. Во-вторых, это вызывает вопрос о том, какую версию заметки открывать в редакторе: настоящую или вот такую промежуточную. Если человек забыл о том, что начинал редактировать заметку, и открыл её спустя месяц, а в редакторе показывают не то, что опубликовано, это взорвёт мозг.
Конечно, можно напридумывать интерфейса, чтобы это разрулить: показывать где-то, что открыта несохранённая версия, дать возможность сравнить с сохранённой, откатить изменения... Но это всё какая-то жесть. Я не хочу ничего такого ни реализовывать, ни видеть как пользователь. Я хочу, чтобы просто ничего не терялось.
Дальше. Если уж всем этим заморачиваться вообще, то нужно сохранять не только на сервер, но и локально. Чтобы можно было надёжно редактировать заметки без интернета. То есть сложность вырастает ещё сильнее. Возникает аж три версии у каждой заметки: опубликованная на сервере, сохранённая неопубликованная на сервере и сохранённая локально. Это не считая того, что локально может быть разная на каждом клиенте.
В общем, я прихожу к выводу, что единственный способ сделать эту фичу действительно хорошо и без компромиссов — полноценно реализовать collaborative editing как в Гугль-доке. То есть для меня как пользователя не должно быть вообще никакой разницы между клиентом и сервером. Я должен видеть всё в самом свежем виде, на каком устройстве бы ни открыл редактор заметки. А если интернета нет, то после его появления всё должно смёрджиться максимально чисто.
Проблема с тем, что пользователь может не опубликовать изменения, но при этом хотеть их синхронизации между всеми устройствами остаётся, но я, кажется, придумал, как её решить, не усложняя интерфейс.
Я не представляю, насколько трудно реализовать надёжное одновременное редактирование с нескольких устройств, но знаю словосочетание operational transformation, погуглив которое можно найти всяких статей на эту тему. Например, Collaborative Editing in JavaScript: An Intro to Operational Transformation (очень клёво объяснён сам принцип).
Я надеялся найти какую-нибудь популярную опенсорсную реализацию этого, но что-то ничего не нашёл. Если покурить Стек-оверфлоу, то можно нарыть десяток разных заброшенных библиотек, но ничего, что выглядело бы обнадёживающим.
Ещё есть Фаерпад, но там только клиентская часть, и он, как я понял, работает только с собственным сервером. А мне нужно сделать сервер частью Эгеи, причём я бы предпочёл, чтобы сервер был как можно тупее, а вся умная логика была реализована в Джаваскрипте.
Внимание, вопрос: что вы посоветуете? Или, может, кто-то из вас захотел бы сделать такой продукт — библиотеку для одновременного редактирования — под моим внимательным взглядом со стороны? А может, вы вообще думаете, что в моих рассуждениях ошибка и надо всё делать иначе? Расскажите в комментариях, пожалуйста.
Как насчет кардинального решения проблемы — вообще убрать из интерфейса возможность редактировать сообщения?
Существует замечательная система github которой очень многие умеют пользоваться. Если бы в качестве «базы» эгея использовала репозиторий на github, то для того чтобы поправить пост не нужно было бы заходить в админку эгеи. Можно изменять файлы через веб интерфейс самого гитхаб, а можно загрузить все файлы себе на компьютер и для работы с ними использовать свой любимый текстовый редактор. Т. е. вместо того чтобы писать текст в textarea в админке эгее нужно просто создать файл в master ветке git. И все вопросы про совместную работу в git уже давно решены.
Вот это часть звучит сомнительно. Это сработает, если есть две версии и одна явно старше другой, тогда слияние тривиально. А вот если первое устройство отредактировало заметку в офлайне, а потом второе в онлайне, то влить чисто изменения первого получится не всегда. Думаю, что именно поэтому гугл доки не работают в офлайне.
Вместо копий всех полей, можно хранить только дельту между текущим и отредактированным состоянием. Например, в виде набора операций.
Да, идею с гитхабом поддерживаю. Решение несколько гиковское, но зато не требует хостинга и возни с PHP.
Может поинтересоваться у команды Workflowy? У них в мобильном приложении (которое на самом деле офлайн-сайт) это изящно реализовано и при возвращении интернета текст прекрасно мерджится.
Возможно, тут прогрессивные веб-приложения замешаны.
Другая идея (не связана с первой)
Как пользователь, я бы хотел, чтобы измененный пост просто сохранялся в черновиках. А вместо кнопки «Опубликовать», написать «Обновить запись». По нажатию, пост из черновиков становится основным. В черновиках может храниться только одна копия поста. А там уже сколько угодно можно сохранения делать.
С гитом, конечно, идея интересная, но он совершенно не решает поставленную задачу: сохранение будет явным, merge очень тупой, и в целом интерфейс далеко не user-friendly. Это не то, что хотелось бы видеть в Эгее, для этого есть другие движки.
Лучше всего не переусложнять. Для черновиков есть сохранение и публикация, для опубликованных — публикация и сохранение в черновик. Пока не сохранили или не опубликовали — копия в localstorage. Никаких мерджей и синхронизаций между клиентами, от них проблем больше, чем без них. Если кому-то нужна функциональность Фаерпада, пусть готовит материал в Фаерпаде, а потом публикует результаты в Эгее.
Автосохранение черновиков достаточно. Вообще можно просто повторить как сделано в Gmail с черновиками писем.
Если есть конфликт с автосохранёнными черновиками, то их всех можно сохранить и пользователь решит какой лучше.
Библиотека swarmjs — http://swarmjs.github.io/
Работает в оффлайне и при плохом интернете, автоматически синхронизируется со всех устройств.
Пример туду листа http://ppyr.us
Есть слак канал разработчиков, где можно позадавать вопросы https://swarm-js.slack.com,
Что-то там node.js :-( А у меня бэкенд на ПХП.
Не знал, что когда редактирую опубликованную заметку и нажимаю ⌘S, то изменения сразу всем видны :-)
Есть ещё библиотека https://github.com/josephg/ShareJS. Когда-то она была лучше swarm.
А теперь 404.
Повторить логику гуглдоков — самый заманчивый вариант. Они к стати спрашивают какую версию оставить если есть конфликт версий.
Для контроля изменений у гугла есть очень подробная история. Это упрощает основной интерфейс, открывает много возможностей для манипуляций, того же мержа. Не нужно делать по 10 черновиков, это всё заменяет история.
Хорошо хранить только один черновик для редактирования опубликованной заметки, сохранять автоматом, а для обновления публикации кнопку «Опубликовать» менять на «Опубликовать изменения», и визуально выделить. А на возможность посмотреть оригинал пока забить. Если очень нужно можно так глянуть, это будет не самая востребованная функция, редко опубликованный текст глобпльно меняют.
Для упрощения, текст отличный от опубликованного можно подсветить другтм цветом, а при ховере показывать оригинал в тултипе, но это уже моя отбредятина пошла :-)
Когда-то смотрел разные реализации OT, и все они требовали постоянно висящего на сервере процесса. Не каждый хостинг это позволит. Как пользователь, меньше всего хотел бы, чтобы сайт автоматически мержил изменения.
Комментарий про гитхаб натолкнул на мысль использовать google drive api. Готовых решений навскидку не находится.
Вот например, https://developers.google.com/sheets/guides/values — чтение и запись в ячейки.
Примерно тоже у gist
https://developer.github.com/v3/gists/#create-a-gist
А в какой формате у вас хранятся записи?
Цитата:
Тут Илья перебивает:
— Что-то ты усложняешь, пусть кнопка просто всегда доступна, пофиг на даты.
Что? Машины же может не быть в эти даты.
— Да плевать, — говорит Илья, — клиент готов денег заплатить, а ты ему не даёшь, это же бред. Пусть заплатит, а мы как-нибудь разрулим.
Идея сделать аналог автосохранения, как у Гугль-дока, хорошая. Возможно, решение будет сложным, как серверная так и клиентская части.
Как вариант, отказ от админки, переход к редактированию текста прямо на сайте. Черновики видны только автору, остальное видят все.
В Гугль-доке нет админ панели редактирования, есть доступ к документу. Процесс изменения текста, видят все, ни кого это не беспокоит.
Свести в Эгее показ текста и его редактирование на одну страницу. Сразу отпадут несколько «если».
Хочется: автоматически сохранять онлайн и офлайн.
Не хочется: сильно менять интерфейс и писать код.
Во-первых, нужно отказаться от автоматической публикации по cmd + S. В интерфейсе, в таком случае, придётся только сделать ещё одну кнопку.
Во-вторых, можно не заводить дублирующие поля, а изменить формат хранения полей. Хранить сериализованный PHP-объект, в котором будет опубликованная и черновая версия.
В-третьих, хранить только последний черновик (как на сервере, так и у пользователя), и, если он есть, показывать пользователю небольшое уведомление с кнопкой, нажатие на которую подменяет информацию в полях на эту самую черновую версию. Создание нового черновика всегда затирает старый. И на сервере, и локально.
Итого получаем: сохраняем онлайн и офлайн одну черновую версию, в интерфейсе добавляем кнопку и уведомление, кода пишем чуть-чуть.
Мне кажется, что не нужно пытаться накручивать вокруг Эгеи какой-то комбайн. KISS и всё такое.
Для полноценного collaborative editing, а не пародии на него (как в википедии) потребуется сделать что-то вроде вышеупомянутого Swarm. Его аналог можно написать и на php, но все равно это будет полноценный демон с вебсокетами и блекджеком, который потребуется запускать на сервере. Это вряд ли совместимо с нынешней концепцией, в соответствии с которой достаточно простого shared-хостинга с загрузкой файлов по ftp. Изобразить нечто отдаленно похожее на поллинге теоретически возможно, но, как говорится, уже сейчас понятно, что все это будет глючить и тормозить: с редким поллингом будет похоже на управление марсоходом, а частый поллинг создаст огромную нагрузку на сервер.
Тут либо менять концепцию и делать другой продукт, требующий хотя бы виртуального выделенного сервера, либо забить и сделать тупой вариант с полями id + is_draft в каждой таблице (или наоборот is_public, если угодно). Он не такой уж и сложный — можно все редактируемое всегда сохранять с is_draft = 1 (а если сделать в таблицах is_draft DEFAULT 1, возможно, не потребуется менять код вообще), а публикация делается весьма просто — SQL-запросом вида INSERT INTO t SELECT id, 0 as is_draft, ... FROM t WHERE id=? AND is_draft=1 ON DUPLICATE KEY UPDATE... для каждой из таблиц.
Вдогонку: да, PRIMARY KEY (id, is_draft). Необычно, но ничего страшного.
Илья, вам сложности с модальностью не избежать никак потому, что есть уже 2 разных режима заметки: черновик и опубликованная версия. Это 2 разных состояния заметки и каждого есть уникальное поведение (например доступность комментирования). Вы же не видите в этом проблемы? Аналогично я предлагаю перестать видеть проблему различного поведения у автосохранения.
Пока пост редактируется клиентский скрипт сам заботится о его автосохранении. Интерфейс для истории не нужен. Есть понятный шоркат для отката — ctrl + z. Но поведение у черновика и публикации будет разным для автосохранения. Здесь я вижу 3 сценария:
1) Редактируем черновик. Здесь всё просто. Всегда автосейв. Открываем всегда последнюю сохранённую версию;
2) Редактируем опубликованную заметку. Решение о публикации принимает человек, не машина. Поэтому переложить на машину задачу решать что в итоге делать с данными не получится. Пусть человек сам решает. Здесь мы храним историю изменений (автоматически конечно). Но если человек уходит из редактирования не нажав «Опубликовать изменения», то машина уточняет — нужно ли их публиковать? Если человек отвечает нет, то удалить эти изменения к чертовой матери потому, что ваш продукт блог, а не редактор коллаборативный. Причём блог так называемый standalone. Не социальная платформа;
3) Редактируем опубликованную заметку с аварией (ведь наша задача не терять данные). В этом сценарии у нас есть опубликованная заметка и сохранённые, но не опубликованные изменения. Как так получилось не важно. Человек тупо закрыл браузер никак не ответив на вопрос о том что делать с изменениями. Или свет выключили. Или батарея села. Или инет накрылся. По сути это тот же сценарий, что и предыдущий. Только задом наперёд — заметка открывается, а не закрывается. И решение здесь такое же. Сообщаем человеку, что были несохраненные изменения и предлагаем решить что открыть: опубликованный оригинал или эти изменения (которые с помощью ctrl + z, при желании, можно откатить до опубликованного оригинала).
Как часто нужно много и долго редактировать уже опубликованную заметку? Мне кажется, что в этом случае обычно требуется только быстро подправить какие-то всплывшие неточности или исправить опечатку.
Если допустить, что это так, то можно обойтись без автоматического сохранения опубликованных заметок. При изменении опубликованной заметки ненавязчиво показывать, что заметка отредактирована, но не сохранена. Например, подкрасить кромку красноватым. При сохранении выдавать предупреждение, что заметка будет сразу опубликована.
В таком случае задача заметно упрощается: черновики сохраняются автоматически, а опубликованные записи — только вручную.
Когда уже записал, понял, что в таком случае нарушается принцип неприкосновенности вводимых данных.
Илья, схема Гугла не ляжет на ваш блог, потому что Гугл удалил фазу редактирования. Любое изменение вступает в силу немедленно, а Гугл делает снапшоты и называет их версиями.
Между сервером и клиентом постоянная связь. На сервер уходит тысяча мелких изменений от пользователей, их разрулить проще, чем два крупных. Гугл может позволить себе это удовольствие в силу бюджета. Это частное решение, которое принимает во внимание огромное количество мелочей. По этой причине нет ничего внятного в опенсорс-библиотеках.
В вашем случае подойдет версионирование документов. В таблицу posts добавляете поле version. Когда редактируете статью, каждые 30 секунд на сервер уходит аякс-запрос, аналогичный нажатию на кнопку «опубликовать», только с особым параметром. На сервере создается новый пост с таким же айдишником, но другой версией. При окончательной публикации удаляете старые версии.
Если связи нет, записывайте данные в localStorage, потом проверяйте, есть там что-то или нет. Если да, шлете на сервер.
Если начинаете редактировать заметку, для которой есть неопубликованная версия, можно показать выбор — редактировать этот документ или продолжить работу с неопубликованной версией. В будущем можете прикрутить анализатор разницы, который красиво покажет расхождение.
Прикручивать такую сложную систему для блога, рассчитанного на одного автора, мне кажется оверхедом. Чтобы текст не пропал в процессе набора, пишите либо в Саблайме, у него автосохранение. Потому что то же самое касается не только постов, но и комментариев.
Еще такой совет: сделайте для начала автосохранение форм. Простой js-модуль, который дампит форму в localStorage каждые N секунд, а при сабмите очищает. В-первых, это легко, куча библиотек, во-вторых — полезно. Я как-то писал вам комментарий на 4 абзаца, закрыл вкладку и все потерял. В третьих, появится понимание, как должна выглядеть ваша первоначальная задумка.
Дальше-то что с этим делать? Вот есть оно в localStorage. А на сервере другое. Что брать?
При сабмите формы сторадж очищается. Значит, если в сторадже что-то есть, имел место аварийный случай, когда чел закрыл вкладку или свет мигнул. Можно спросить — подгрузить ли локальные данные или нет.
Можно на сервер периодически отправлять изменения, при начале локального редактирования — ставить блокировку (надеясь, что интернет на момент начала редактирования есть, а это почти наверняка правда — мы же как-то загрузили страницу редактирования), чтобы при попытке редактирования с другого компьютера показывало что-то типа «У вас есть несинхронизированные изменения с другого компьютера». При этом должна быть кнопка «Отбросить те изменения, всё равно начать редактирование с тем, что есть». В таком случае пользователь явно понимает, что и где у него сохранено, и что он теряет.
Можно после возвращения первого компьютера в интернет попробовать замёржить локальные изменения с тем, что творится на сервере, но, как мне кажется, это уже будет лишняя сложность. Хотя пользователю приятно будет, что его изменения всё равно сохранились.
Ну и в любом случае это всё не должно публиковаться без явного разрешения пользователя.
Илья, есть очень простая идея которая лежит на поверхности — сохранять значения форм в localStorage пользователю и сверять их с сервером. Для синхронизации можно так-же хранить время. По факту при любом изменении формы — сохраняешь их в localStorage вместе со временем. При сохранении — удаляешь. Если пользователь зашел на сайт, а у него в хранилище есть версия которая свежее (смотрим по дате), то предлагаешь через попап восстановить её.
Ах да, еще было-бы круто чтобы после того как я оставил комментарий, страница не просто перезагрузилась, а доскролилось до моего комментария и я был уверен что он опубликовался и ничего не лагануло.
И комментарии редактировать тоже было бы приятно.
Перезаписывать поверх, но сделать историю, чтобы важную мысль случайно не потерять.
Кто последний, тот и записал финальный вариант. В 90% случаев, полагаю, конфликтов не будет.
Историю сделать через slowly changing dimensions, но это очень непростая серверная часть. Зато открывается возможность «актом публикации» считать назначение определенной версии для показа. Пользователей сложность сервера не сильно касается.
Пример из жизни: в Тостере сохраняют версии заданного вопроса.
Есть опубликованный пост.
Заходим в режим редактирования, начинаем редактировать и в этот момент где-то справа появляется надпись — статус: не опубликованная версия или просто — не опубликовано. Плюс внизу кнопка — Опубликовать, можно еще кнопочку — Вернуть.
При редактировании черновик сохраняется в localStorage и в базу на сервере. И там и там добавить время черновика.
При любых конфликтах проверяем где у нас версия черновика новее, на сервере или в localStorage. Пользователю для редактирования выводим самый новый черновик.
Вот еще нашел.
Сохранение
https://www.dropbox.com/developers-v1/core/docs#files_put
Ревизии
https://www.dropbox.com/developers-v1/core/docs#revisions
Где-нибудь в настройках привязать dropbox пользователя, получить API ключ. И места на дропбоксе не должно много занимать, как я понимаю после публикации вам они больше не нужны. Такой middleware получится между базой эгеи.
А при отсутствии интернета — в localstorage. А если ограничить слегка пользователя — indexedDb, есть на Webkit. Удобнее будет хранить ревизии документа.
И еще я вспомнил, что ревизии у документов есть у WordPress, по-крайней мере были.
Конечно, нужно подумать что считать ревизией, какое количество изменений.
localStorage, сохранять после любого локального изменения (onKeypress), очищать после сохранения на сервере, загружать если было что-то сохранено ранее
Не знаю поможет это вам или нет, но в наших проектах реализован следующий принцип:
В итоге кнопок «Сохранить» нет нигде, как нет и индикаторов сохранения (кроме текстового редактора — там всё медленно, потому надо).
Помочь в реализации не могу — всё делаем на node.js, а у вас PHP.
Для collaborative editing можно еще посмотреть Google Realtime API:
https://developers.google.com/google-apps/realtime/overview
Главное, если и делать мердж, то надо бы и визуальный дифф изменений предлагать, мало ли что там могло сохраниться или наоборот не сохраниться.
Мне кажется, OT слишком сложен в реализации. Чаще всего, он нужен в гугл докс и похожих сервисах, где удобство совместного редактирования оправдывает усложнение системы. В этой концепции у нас документ это не набор символов, а набор операций над текстом (вставить «я» в конец текста, удалить символ «и» с позиции №3).
Я бы предложил отойти от концепции, когда редактируемая и опубликованная статья — одна и та же сущность. Предлагаю разделить ее на две части: редактируемый черновик, который видит автор. Также появляется слепок, практически кэш, снятый в момент публикации, который однозначно привязан к черновику. Структура слепков один в один повторяет оригинал, но также дополнительно имеет указатель на черновик. Таким образом:
Илья, спустись, пожалуйста, на землю. Какой гуглодок и коллаборейтив эдитинг? Достаточно сделать автосохранение черновиков — сразу на сервер, без локалсториджа, хотя бы каждые 10 секунд. Экономящие трафик могут подкрутить интервал в конфиге.
Если у пользователя пропал черновик — это трагедия. Писать больше не хочется. Если же пропали небольшие изменения в уже опубликованном посте — это печально, но не страшно.
Жизненная история: я использую Эгею без году неделю. Написал четыре поста. Все — с мобильника. Из них смог опубликовать только два. Два других пропали из-за моей невнимательности. Автосохранение черновиков решило бы проблему.
Ну а автосохранение заметок я бы решил созданием черновика в момент перехода к редактированию. Черновик привязан к заметке через её адрес. Если пользователь решает опубликовать черновик под тем же адресом, он перезапишет заметку и «уничтожится». Если пользователь изменил адрес заметки — создаст новую заметку по новому адресу (и тоже «уничтожится»). Если пользователь ничего не делал, то заметка останется в черновиках. Её потом легко удалить, невелика проблема. Эгея — не коллективная редакторская платформа, ну не будет там 100500 черновиков одной и той же заметки.
А кто хочет писать в офлайне, пусть использует ноутпад или фаерпад.
Илья, мне сложно что-то добавить про аналог Гугла, но расскажу чего лично мне в нынешней версии.
Не хватает статуса о сохранности. Если нажать cmd+s, то в левой колонке подсказка превратиться в текст «Сохранение», а потом снова в подсказку. Но не бывает такого, что там написано «Сохранено». Давишь, бывает хоткей, и непонятно отработалось или нет. А еще эта подсказка хоткея скрывается из вида, когда пишешь длинную заметку. Было бы она залипала, как карандашик.
А еще не в тему, но тоже про редактирование. Сейчас текст просто текст и в длинной заметке в режиме редактирования сложно цепляться взглядом за заголовки. Прикольное решение придумали в приложении Ulysses (http://soultest.ulyssesapp.com). Ставишь решетку — текст становится жирным, ссылки подсвечиваются синим, у цитат появляется рамка и так далее. Это ненавязчиво, но ориентироваться становиться проще.
Может быть это прозвучит странно, но есть например Wordpress и там есть автосохранение, черновики и разные версии страницы.
И написан он, сюрприз, на php. Может прямо посмотреть, как там сделано?
Кстати, прикольно было бы сделать тему для вордпресса на базе оформления Эгеи.
Кирилл Иванский +1
Или тему для https://jekyllrb.com/ чтобы на https://pages.github.com/ хостить
Сегодня хотел прицепит к записи zip архивчик по типу как цепляются картинки и ничего не получилось :(