Случайно накосячили в документе, который вам только что пошарили? Драг-н-дропнули куда-то кусок чужого текста и не знаете, как жить дальше? Угодили курсором в ячейку вашего коллеги и уничтожили его данные? Ctrl+Z не раз спасало наши жизни и репутации добропорядочных коллег, не портящих чужие (и свои) документы.

В последнем выпуске мы добавили возможность сделать Undo в быстром совместном редактировании. Почему его не было раньше, как там всё устроено, и почему случается так, что вы жмете undo до упора, а документ всё равно не остается пустым, вы узнаете из этой статьи.




Undo в быстром режиме совместного редактирования появилось у нас вместе со сносками в версии 4.3. Больше о релизе вы можете узнать из нашей прошлой статьи (из этой статьи вы узнаете только об undo).

Быстрый, строгий, злой


Короткая справка о том, чем вообще отличаются режимы коэдитинга:

  • Быстрый. Вы видите, что печатает ваш соавтор. («Что за чепуху ты печатаешь, Николай?!»)
  • Строгий. Вы спокойно работаете над своим куском текста, а правки соавтора видите только после сохранения. («Что за чепуху ты напечатал, Геннадий?!»)

Быстрый режим совместного редактирования включен в редакторах ONLYOFFICE по умолчанию. Раньше в нём не было undo.

Почему раньше в быстром режиме не было undo


Мы думали, что это опасно для документа. Допустим, Николай вставляет автофигуру, а Геннадий перетаскивает в нее часть текста из документа. Затем Николай отменяет действие, в результате чего удаляется автофигура. А с ней и часть текста, которую уже никак не вернуть. Геннадий и Николай грустно идут пить чай, обсуждая, как восстановить утраченный текст.

Тем не менее, мы пришли к выводу, что даже в быстром режиме возможность отменить последнее действие нужна. Это просто удобно. Например, в ситуации, описанной в начале этого текста, когда вы, выделяя текст для более внимательного чтения, случайно перетащили его мышкой, а в это время документ редактировал кто-то еще. Или даже не редактировал, а просто забыл закрыть его. Или это вы сами забыли закрыть этот же документ в соседней вкладке. В общем, нужна возможно автоматически откатить последнее действие.

К undo, конечно же, нужно относиться осторожно (но на самом деле, вы рискуете уже просто когда шарите на кого-то документ с возможностью редактирования). Но мы расскажем вам, как всё устроено, чтобы минимизировать риски.

Google и мы


Нас постоянно спрашивают (на самом деле, спрашивают): Вы сделали это как у Google Docs? Нет.

Да, у них есть совместное редактирование и возможность сделать undo, и у нас эти вещи тоже есть. Но это не значит, что у нас внутри всё одинаково.

Как у Google. Документ находится на сервере, туда же отправляются все правки, там же собирается финальная версия документа. Например: Николай выделил какое-то слово жирненьким, а Геннадий курсивом. Оба два отправляют свои изменения на сервер, который уже разруливает этот конфуз: в частности, сообщает Николаю добавить курсив, а Геннадию сделать свой курсив жирным. В такой схеме ситуация, в которой один и тот же документ выглядит на разных клиентах по-разному, возможна (на очень короткий промежуток времени, конечно же).

Как у ONLYOFFICE. Если у Google ядро документа редактируется на сервере, то у нас всё это происходит на каждом клиенте. Сервер используется как база данных, в которой хранятся изменения. Как в такой схеме должны лечь изменения в ситуации, когда Николай выделил какое-то слово жирненьким, а Геннадий курсивом? Изменения лягут последовательно, при чем последовательность будет одна и та же на каждом клиенте. Ситуация, в которых у Николая и Геннадия документы выглядят по-разному в какой-то момент времени, невозможна.

Как это вообще связано с undo, спросите вы (на самом деле, мы понятия не имеем, спросите вы или нет, но представим как будто вы спросили). Из следующего пункта нашей истории вы поймете как.

Что происходит, когда вы нажимаете Ctrl+Z


Как мы уже сказали, в ONLYOFFICE все самые важные вещи происходят непосредственно на клиенте. Там же хранится список действий — не только своих, но и чужих. Свои собственные действия помечаются, чтобы потом была возможность их откатить.

И вот тут-то мы переходим к тому, как на самом деле происходит undo. Когда человек редактирует документ в одиночестве всё, в принципе, понятно. В блоке изменений хранятся действия одного человека и в случае необходимости мы просто берем последнее действие и производим антидействие. Блок действий при этом сокращается на единицу.

В совместном редактировании схема сложнее просто потому, что в списке изменений хранятся действия нескольких человек.

Рассмотрим простой пример. В документе содержится такой текст:

абв

Николай добавил в конце букву д и получил текст:

абвд

Увидев результат, Николай передумал и захотел отменить ввод буквы д. В это же время придирчивый Геннадий удалил букву б и в документе остался такой текст:

авд

Николай нажимает undo. Удаление и добавление текста происходит по позициям. Своим первым действием Николай добавил букву в позицию 3. В обычном редактировании undo сработало бы просто как удаление буквы из третьей позиции. Но ведь Геннадий уже постарался и удалил букву б из позиции 2 и буква д переместилась на её место. Таким образом в третьей позиции теперь просто пусто и удалять оттуда ну совсем нечего.

Это означает, что простая схема не прокатывает и перед тем, как отменить действия Николая, нужно их перегнать в самый конец блока, так как будто бы они были последними (после всех чужих действий).

Если перенести действие Николая по добавлению буквы д в самый конец блока, то есть после того, как Геннадий злодейски удалил букву б из второй позиции, то получится, что Николай добавил букву Д в позицию 2. Делаем обратное действие — удаление из позиции 2. Undo проходит.

Таким образом основной принцип таков: мы берем пачку действий клиента, который хочет сделать undo, коммутируем их (перестанавливаем через чужие действия по определенным правилам) и формируем новый блок обратных действий, как в обычном undo.

У клиента Николая, сделавшего undo, применяется антидействие, а всем остальным рассылается просто пачка его действий, то есть для них нет никакой разницы сделал он undo или просто удалил букву руками.

Ещё с undo, конечно, разные интересные проблемы, например

Неоткатываемые действия


Еще один простой пример:

1. Николай добавляет текст 12.
2. Геннадий удаляет 2.
3. Николай делает undo 2 раза.
4. Геннадий делает undo.



Итак, оба пользователя откатили все свои действия и у обоих не осталось ни одного undo в рукаве. Однако изначально пустой документ после отмены всех совершенных в нём действий пустым не остался. В нём гордо красуется цифра 2. Откатить её с помощью undo невозможно.

Исчезающие элементы


Рассмотрим ещё раз случай произошедший с Геннадием и Никлаем в начале статьи:

1. Николай добавляет автофигуру.
2. Геннадий набирает текст в автофигуре.
3. Нестабильный Николай делает undo автофигуры.
4. Вместе с автофигурой пропадает и текст, набранный Геннадием внутри неё. Окончательно.

Любая проблема с undo в быстром совместном редактировании объясняется тем, что последовательность реальных действий в документе и последовательность, в которой нажимается undo, не совпадают.

Это не недостатки нашей реализации undo. Оно работает правильно. Оно будет идеальным, если вы с коллегами будете одновременно работать над разными объектами. И вот вам наше напутственное слово: будьте, как это сейчас модно говорить, mindful, работая с документами. Переключайтесь между режимами совместного редактирования и используйте undo с умом.
Поделиться с друзьями
-->

Комментарии (27)


  1. sheknitrtch
    28.04.2017 17:55
    +1

    Получается, в вашей реализации пользователь не может сделать Undo действиям другого пользователя?
    А если Николай весь день писал документацию к проекту, а вечером Геннадий зашёл в этот документ и удалил весь текст, то Николай не может сделать Undo этой операции?

    Мне кажется более естественным, если действия всех пользователей складываются в один стек и Undo отменяет последнее по времени действие независимо от того, кто это действие совершил.


    1. EndUser
      28.04.2017 21:19

      Заминка в редактировании разных абзацев или страниц. Вы хотите отменить свой абзац, но вы хотите сначала грохнуть абзац коллеги.


    1. xkorolx
      29.04.2017 09:05

      Так делать точно нельзя, иначе, ошибившись в слове на 10ой странице, я отменю действия другого на первой, даже не зная об этом. Если Вы дали Геннадию доступ на редактирование документа, то должны понимать, что он может все удалить


      1. DaylightIsBurning
        29.04.2017 14:33

        на этот случай гугл даёт возможность открыть историю версий и восстановить текст оттуда.


        1. DaylightIsBurning
          29.04.2017 14:43

          сорри, не туда ответил. Имелось ввиду, что если кто-то удалил весь текст, то это можно было бы восстановить из истории версий, как у гугла сделано.


          1. trofim24
            02.05.2017 12:01

            У нас есть история версий из которой можно восстановить все данные.


  1. MaM
    28.04.2017 19:45
    -7

    Я вот одного не понимаю, почему бы в универе не учить git, почему бы не засунуть поддержку гита, маны, зачем все эти костыли? Имхо не стоить строить велосипед, если нет действительно важной причины его строить.


    1. Antelle
      29.04.2017 00:04
      +2

      Это вы сейчас вместо ctrl-z в текстовом редакторе git revert использовать предлагаете?


      1. MaM
        29.04.2017 11:42
        -3

        А почему нет? Все полу решения все равно рано или поздно разрастуться в систему контроля версий.


    1. xkorolx
      29.04.2017 09:06
      +2

      Наверное вы промахнулись статьей.


  1. EndUser
    28.04.2017 21:14

    То есть read uncommitted и read committed…
    А что Redo?


    1. EndUser
      28.04.2017 21:27

      /me думает… 37% complete
      Возможно ли представить док как цепочку токенов (предложений и фигур), внутри которых undo/redo stream будет общим? То есть владельцем потока действий будет фраза, а не люди?
      В таком случае, конечно, надо придумать как сделать ctrl-z до разборки компа, ведь неочевидно какой токен имеет «хронологически предыдущий» токен, что делать при уничтожении или перемещении токена.
      Но Ctrl-Z для «деинсталляции Винды и разборки компа» редко используется, поэтому на таком уровне логики может прокатить. И может оказаться интуитивным — будет предельно ясно как работать с конфликтом внутри одного предложения.


    1. xkorolx
      29.04.2017 09:06

      Redo пока нет.


  1. khim
    29.04.2017 00:35
    +2

    На самом деле у вас всё сделано примерно как было сделано в Google Docs — много лет назад. До того, как они получили гордую приставку Google.

    А потом, после нескольких лет возни, всё это было выкинуто и заменено тем, что есть сейчас. Потому что рассказы про «Это не недостатки нашей реализации undo. Оно работает правильно.» пользователями не воспринимаются. Если я не могу открыть документ и нажав несколько раз Undo получить то, что в нём было вчера — то что это за «Undo» такое?

    Посмотрим как ваши пользователи к этому отнесутся…


    1. xkorolx
      29.04.2017 09:10
      +1

      На самом деле у всех слишком большое доверие названию Google. Поредактируйте совместно (двумя пользователями) документ в Гугл и поотменяйте одновременно. И посмотрите, как документ вернется в первоначальное состояние). И тогда наши разговоры может воспримутся чуть лучше.


    1. xkorolx
      02.05.2017 17:19

      Хотелось бы добавить по поводу Google. Все выше приведенные примеры на тему «недостатки нашей реализации», повторяются и сейчас в Google документах. Вы можете сами проверить это прямо сейчас. Т.е. в этом плане между нами и Google никакой разницы нет. Поэтому странно слышать про старую реализацию в Google Docs, поскольку у них в этом плане ничего не поменялось. Все эти проблемы, которые есть у нас, есть и у них.


  1. Fortisa
    29.04.2017 10:55
    +1

    Онлиофис — большие молодцы. Первое, чем молодцы — это качеством отображения и возможностью работы с гигантскими документами. Второе — совместным редактированием и остальными фичами порталов. Третье — тем, что постоянно продакшен делают и не боятся обкатывать продукт на клиентах, в отличие от распиаренного российского офиса, который в глаза-то толком мало кто видел из простых смертных. Развивайтесь, парни!


    1. xkorolx
      29.04.2017 14:46

      Бальзам на душу). Спасибо!


    1. AbstractGaze
      01.05.2017 14:36

      Они просто берегут наши глаза.


  1. kiff86
    29.04.2017 13:18

    Прошу прощения, возможно уже есть такая реализация, но (прочитав комментарии, пришла мысль — делюсь)


    Если есть история изменений документа (в виде визуального стека) у пользователей, то человек может попробовать сделать undo. Если при этом в стеке есть действия других пользователей, то отправляется запрос пользователям на разрешение этого undo. Все это верно, если затрагивается блок с общей информацией. Если блок создал и изменял один пользователю, то отмена будет без подтверждений.


    Также по поводу:


    А потом, после нескольких лет возни, всё это было выкинуто и заменено тем, что есть сейчас. Потому что рассказы про «Это не недостатки нашей реализации undo. Оно работает правильно.» пользователями не воспринимаются. Если я не могу открыть документ и нажав несколько раз Undo получить то, что в нём было вчера — то что это за «Undo» такое? ( пользователя khim)

    Согласен, можно было бы добавить историю документа read-only. То есть можно откатить вид документа по истории внесения изменений (того же стека). При этом добавить возможность печати документа и, например копирования его, чтобы его можно было редактировать уже как отдельный документ.


    И все это действительно отчасти начинает напоминать git...


    1. xkorolx
      02.05.2017 17:26

      Если есть история изменений документа (в виде визуального стека) у пользователей, то человек может попробовать сделать undo. Если при этом в стеке есть действия других пользователей, то отправляется запрос пользователям на разрешение этого undo. Все это верно, если затрагивается блок с общей информацией. Если блок создал и изменял один пользователю, то отмена будет без подтверждений.


      С точки зрения интерфейса и взаимодействия пользователей так делать нежелательно. Почти все пользователи всегда уверены в изменениях, которые вносят сами, и если им придет предложение «Разрешить внести изменения в свои правки ради Undo у другого пользователя», то мы почти уверены, что в 99% случаев все будут запрещать это делать и Undo как такового не будет вообще. Кроме этого, такое поведение сильно усложняет совместный набор документа.

      Согласен, можно было бы добавить историю документа read-only. То есть можно откатить вид документа по истории внесения изменений (того же стека). При этом добавить возможность печати документа и, например копирования его, чтобы его можно было редактировать уже как отдельный документ.

      История версий у нас есть.


  1. develop7
    29.04.2017 16:52

    Правильно ли я понимаю, что вам удалось смоделировать документ, используя CRDT?


    1. xkorolx
      02.05.2017 17:15
      +3

      В некотором смысле да. Отдельные части документа, представленные в виде массивов, у нас смоделированы по схеме CmRDT, т.е. по коммутативному варианту, когда другим пользователям мы не пересылаем целиком состояние всего массива, а только изменения связанные с ним, и этим изменения мы коммутируем между собой, чтобы у всех пользователей состояния этих массивов были идентичны.


  1. Deosis
    29.04.2017 17:53

    В примере с неоткатываемыми действиями, Николай первой отменой сделал что?
    Отменил вставку символа, которого уже нет. То есть никак не модифицировал документ.
    Получилась отмена Шрёдингера.
    Её можно было бы оставить в стеке, а после отмены действия Геннадия, сделать доступной.


    1. xkorolx
      02.05.2017 17:07

      Во-первых, здесь приведен сильно упрощенный случай из возможных. Хорошо, когда все изменения между собой не связаны, а если бы это изменение, связанное с другим, то так как вы предлагаете сделать уже нельзя. Другими словами, из блока своих действий вырывать какие-то действия и не делать к ним Undo — неправильно, и может привести к еще более худшим последствиям.
      Во-вторых, как это будет выглядеть для первого пользователя? Вот он откатил все свои действия до конца (кнопка Undo неактивна), и тут неожиданно после действий другого пользователя его кнопка Undo станет снова активной. Так неправильно, он же ничего не делал, значит и кнопка Undo не должна становится активной для него.


      1. Deosis
        03.05.2017 07:12

        Во-вторых, как это будет выглядеть для первого пользователя?

        Он сделал два действия, но отменил только одно. Это произошло из-за конфликта с действиями другого пользователя. Если конфликт разрешился, то действия снова могут быть отменены.
        Так неправильно, он же ничего не делал

        В совместном редактировании документа. Значит он должен был учитывать, что на состояние влияют другие люди. Рассчитывать на другое, все равно что рассчитывать, что содержимое Интернета не изменится за ночь.


        1. xkorolx
          03.05.2017 10:47

          Он сделал два действия, но отменил только одно. Это произошло из-за конфликта с действиями другого пользователя. Если конфликт разрешился, то действия снова могут быть отменены.

          Никакого конфликта нет. Он вводил текст и отменил свой ввод. Второй пользователь удалил текст и отменил удаление. При неправильном порядке Undo появляется удаленный текст. Это нормально, тут нет конфликтов, поэтому и нет смысла не давать пользователю не делать Undo. На самом деле ввод двух символов, хоть это и 2 разных действия, но можно их трактовать как одно (например, так делает Google Docs), и на ввод двух символов у вас будет ровно одно Undo, а не 2. В такой ситуации Undo запрещать нельзя, т.к. один из символов все еще есть в тексте.
          В совместном редактировании документа. Значит он должен был учитывать, что на состояние влияют другие люди. Рассчитывать на другое, все равно что рассчитывать, что содержимое Интернета не изменится за ночь.

          С такой логикой пенять на «неправильную» работу Undo вообще нет смысла. Т.к. пользователь жмет Undo и надеется увидеть предыдущее состояние, но в это время кто-то правит этот же кусок текста (возможно правки вносятся с помощью опять-таки Undo второго пользователя, но первому-то какая разница как) и предыдущего состояния первый пользователь не увидит уже никогда.