Самый маленький офисный пакет в мире

Автор оригинала: Serge Zaitsev
  • Перевод


Всем нам знаком традиционный офисный пакет — текстовый редактор, электронные таблицы, программа для создания презентаций, возможно, приложение для создания диаграмм или заметок. Всё это мы видим в Microsoft Office и в Google Docs. Все эти программы мощны и объёмны. Но каким будет минимальное количество кода, требуемое для создания офисного пакета?

Платформа


Очевидно, что наш офисный пакет не будет десктопным приложением с GUI — для создания чего-то подобного требуется много кода и труда. То же самое относится и к нативным мобильным приложениям. Мы можем рассмотреть возможность создания консольного (терминального) приложения, и на самом деле существуют абсурдно маленькие текстовые редакторы или электронные таблицы, но будет гораздо проще, если мы выберем в качестве платформы браузер.

В браузерах уже есть вполне достойный редактор текста с форматированием (contenteditable) и они являются очень хорошими (хоть и небезопасными) вычислителями математических выражений.

Насколько же малым мы сможем сделать пакет?

Текстовый редактор


На самом деле, это «приложение», которым я пользуюсь уже многие годы:

<html contenteditable>

Да, и это всё. Более того, это можно превратить в самодостаточный URL, и именно так я его использую, когда мне требуется черновик, чтобы быстро набросать заметки:

data:text/html,<html contenteditable>

Попробуйте вставить эту строку в поле ввода URL. Если браузер вам позволит, то вы сможете использовать Ctrl+B или Ctrl+I, чтобы делать текст жирным или курсивным.

Мы можем немного улучшить это приложение, добавив стилей (да, я считаю, что небольшие типографические улучшения важны):

data:text/html,<body contenteditable style="line-height:1.5;font-size:20px;">

Я добавил эту строку в закладки и теперь для доступа к моему невесомому редактору достаточно нажать на несколько клавиш. Можно также использовать его как временный буфер обмена для вставки текста или даже изображений.

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

Сохранение текста выполняется сохранением всего HTML в файл или печатью его на бумаге.

Презентации


Несколько лет назад я уже написал простой инструмент для создания презентаций, который представляет собой самостоятельный файл HTML. Его можно редактировать (наподобие markdown-текста) и он будет рендериться как разноцветная презентация в стиле Takahashi.

На этот раз, поскольку мы продолжаем говорить contenteditable, давайте напишем WYSYWIG-редактор слайдов. Во-первых, создадим несколько пустых слайдов с возможностью редактирования:

<body><script>
for (let i=0; i<50; i++) {
  document.body.innerHTML += `
    <div style="position:relative;width:90%;padding-top:60%;margin:5%;border:1px solid silver;page-break-after:always;">
      <div contenteditable style="outline:none;position:absolute;right:10%;bottom:10%;left:10%;top:10%;font-size:5vmin;">
      </div>
    </div>`;
}
</script>

Число 50 выбрано произвольно, но не забывайте, что большего количества слайдов использовать не стоит. Каждый внешний div — это слайд с тонкой обводкой. Трюк с width и padding-top нужен для сохранения соотношения сторон слайда. Попробуйте изменять значения, чтобы увидеть, как это влияет на структуру. Каждый внутренний div — это простой редактор форматированного текста с достаточно большим шрифтом для чтения с экрана проектора.

Вполне неплохо. Но ведь нам нужны на слайдах заголовки и списки, правда?

Давайте добавим горячие клавиши:

document.querySelectorAll("div>div").forEach(el => {
  el.onkeydown = e=> {
    // `code` will be false if Ctrl or Alt are not pressed
    // `code` will be 0..8 for numeric keys 1..9
    // `code` will be some other numeric value if another key is pressed
    // with Ctrl+Alt hold.
    const code = e.ctrlKey && e.altKey && e.keyCode-49;
    // Find the suitable rich text command, or undefined if the key
    // is out of range
    x = ["formatBlock", "formatBlock", "justifyLeft", "justifyCenter", 
         "justifyRight", "outdent", "indent", "insertUnorderedList"][n];
    // Find command parameter (only for 1 and 2)
    y = ["<h1>", "<div>"][n];
    // Send the command and the parameter (if any) to the editor
    if (x) {
      document.execCommand(x, false, y);
    }
  };
});

Теперь при нажатии Ctrl+Alt+1 внутри слайда мы сделаем выбранный текст заголовком. А если нажать Ctrl+Alt+2, то он снова станет обычным. Ctrl+Alt+3-Ctrl+Alt+5 меняют выравнивание, а 6 и 7 — отступ. 8 начинает список. 9 оставлена под ваши собственные нужды, можете настроить эту комбинацию. Полный список операций contenteditable можно найти на MDN.

Немного ужав показанный выше код и превратив его в data URL, мы получим следующий редактор слайдов с форматированием размером около 600 байт:

data:text/html,<style>@page{size: 6in 8in landscape;}</style><body><script>d=document;for(i=0;i<50;i++)d.body.innerHTML+='<div style="position:relative;width:90%;padding-top:60%;margin:5%;border:1px solid silver;page-break-after:always;"><div contenteditable style="outline:none;position:absolute;right:10%;bottom:10%;left:10%;top:10%;font-size:5vmin;"></div></div>';d.querySelectorAll("div>div").forEach(e=>e.onkeydown=e=>{n=e.ctrlKey&&e.altKey&&e.keyCode-49,x=["formatBlock","formatBlock","justifyLeft","justifyCenter","justifyRight","outdent","indent","insertUnorderedList"][n],y=["<h1>","<div>"][n],x&&document.execCommand(x,!1,y)})</script>

Слайды можно экспортировать в PDF при помощи печати в файл, а после этого демонстрировать на любом компьютере.

Простое рисование


Однажды я создал https://onthesamepage.online для быстрого создания черновиков идей совместно с другими людьми, но несмотря на его простоту, он всё равно больше, чем мы сделаем в этой статье.

В качестве абсолютного минимума мы можем только отрисовывать линии на canvas. Нам нужны элементы <canvas>, несколько обработчиков мыши/касаний и флаг, обозначающий, что движения мыши действительно должны рисовать линию при нажатии клавиши мыши.

Здесь стоит упомянуть, что доступ к элементам с id можно получать через window[id] или window.id. Любопытно, что долгое время это не было стандартизировано и являлось хаком из IE, а теперь стало стандартом.

Кроме того, я переместил обработку положения курсора мыши в отдельные короткие функции, чтобы использовать её ещё раз для обработчиков mousedown и mousemove. Также я сбросил границы элементов body, чтобы canvas был полноэкранным.

Минифицированный код занимает примерно 400 байт и позволяет только рисовать мышью:

<canvas id="v">
<script>
d=document, // shortcut for document
d.body.style.margin=0, // reset style
f=0, // mouse-down flag
c=v.getContext("2d"), // canvas context
v.width=innerWidth, // make canvas element fullscreen
v.height=innerHeight,
c.lineWidth=2, // make lines a bit wider
x=e=>e.clientX||e.touches[0].clientX, // get X position from mouse/touch
y=e=>e.clientY||e.touches[0].clientY, // get Y position from mouse/touch
d.onmousedown=d.ontouchstart=e=>{f=1,e.preventDefault(),c.moveTo(x(e),y(e)),c.beginPath()},
d.onmousemove=d.ontouchmove=e=>{f&&(c.lineTo(x(e),y(e)),c.stroke())},
d.onmouseup=d.ontouchend=e=>f=0
</script>

А вот как выглядит однострочная закладка:

data:text/html,<canvas id="v"><script>d=document,d.body.style.margin=0,f=0,c=v.getContext("2d"),v.width=innerWidth,v.height=innerHeight,c.lineWidth=2,x=e=>e.clientX||e.touches[0].clientX,y=e=>e.clientY||e.touches[0].clientY,d.onmousedown=d.ontouchstart=e=>{f=1,e.preventDefault(),c.moveTo(x(e),y(e)),c.beginPath()},d.onmousemove=d.ontouchmove=e=>{f&&(c.lineTo(x(e),y(e)),c.stroke())},d.onmouseup=d.ontouchend=e=>f=0</script>

Редактор электронных таблиц


Наверно, это приложение будет самым сложным и большим, но мы попытаемся не превышать лимит в 1 КБ на приложение.

Структура будет простой. В HTML есть таблицы, так почему бы не использовать их? Так как ячейки электронной таблицы обычно адресуются по «букве» + «числу», то ограничим таблицу размером 26x100 ячеек. Логично будет создавать строки и ячейки динамически, в цикле. Простая стилизация сделает электронную таблицу красивее:

<table id="t">

t.style.borderCollapse="collapse"; // remove gaps between cells
for (let i = 0; i < 101; i++) {
  const row = t.insertRow(-1);
  for (let j = 0; j < 27; j++) {
    // convert column index j to a letter (char code of "A" is 65)
    const letter = String.fromCharCode(65+j-1); // 1=A, 2=B, 3=C etc
    const cell = row.insertCell(-1);
    cell.style.border = "1px solid silver"; // make thin grey border
    cell.style.textAlign = "right";         // right-align, like excel
    if (i > 0 && j > 0) {
      // add identifiable input field, this is where formula is entered
      const field = document.createElement('input');
      field.id = letter + i; // i.e, "B3"
      cell.appendChild(field);
    } else if (i > 0) {
      // Row numbers
      cell.innerHTML = i;
    } else if (j > 0) {
      // Column letters
      cell.innerHTML = letter;
    }
  }
}

Теперь у нас есть большая сетка из ячеек со столбцами и строками. Далее нужно добавить вычислитель выражений. Мы можем написать грязный, но вполне работающий вычислитель на трёх массивах — массиве всех полей ввода (для получения их введённых значений, чисел и формул), массиве с умным геттером, вызывающим eval(), если требуется переменная и она связана формулой с полем ввода, и кэше последних введённых значений для каждого поля:

inputs = []; // assume that we did inputs.push(field) for each field in the loop above
data = {}; // smart data accessing object
cache = {}; // cache

// Re-calculate all fields
const calc = () => {
  inputs.map(field => {
    try {
      field.value = D[field.id];
    } catch (e) { /* ignore */}
  });
};

// We also need to customize our field initialization code:
field.onfocus = () => {
  // When element is focused - replace its calculated value with its formula
  field.value = cache[field.id] || "";
};
field.onblur = () => {
  // When element loses focus - put formula in cache, and re-calculate everything
  cache[field.id] = field.value;
  calc();
};
// Smart getter for a field, evaluates formula if needed
const get = () => {
  let value = cache[field.id] || "";
  if(value.chatAt(0) == "=") {
    // evaluate the formula using "with" hack:
    with(data) return eval(value.substring(1));
  } else {
    // return value as it is, convert to number if possible:
    return isNaN(parseFloat(value)) ? value : parseFloat(value);
  }
};
// Add smart getter to the data array for both upper and lower case variants:
Object.defineProperty(data, field.id, {get}),
Object.defineProperty(data, field.id.toLowerCase(), {get})

Теперь электронная таблица должна работать — если мы введём, например, «42» в A1 и "=A1+3" в A2, то переключении фокуса с A2 мы должны увидеть 45.

После аккуратной минимизации кода мы получаем работающую электронную таблицу размером примерно 800 байт:

data:text/html,<table id="t"><script>for(I=[],D={},C={},calc=()=>I.forEach(e=>{try{e.value=D[e.id]}catch(e){}}),t.style.borderCollapse="collapse",i=0;i<101;i++)for(r=t.insertRow(-1),j=0;j<27;j++)c=String.fromCharCode(65+j-1),d=r.insertCell(-1),d.style.border="1px solid #ccc",d.style.textAlign="right",d.innerHTML=i?j?"":i:c,i*j&&I.push(d.appendChild((f=>(f.id=c+i,f.style.border="none",f.style.width="4rem",f.style.textAlign="right",f.onfocus=e=>f.value=C[f.id]||"",f.onblur=e=>{C[f.id]=f.value,calc()},get=()=>{let v=C[f.id]||"";if("="!=v.charAt(0))return isNaN(parseFloat(v))?v:parseFloat(v);with(D)return eval(v.substring(1))},Object.defineProperty(D,f.id,{get:get}),Object.defineProperty(D,f.id.toLowerCase(),{get:get}),f))(document.createElement("input"))))</script>

Вы серьёзно?


Да, разумеется, всё это никак не может стать настоящей заменой офисного пакета. Однако это хороший пример минимализма и небольшого объёма кода. Все эти приложения эфемерны, они теряют своё состояние, если обновить страницу, и, похоже, никак нельзя сделать так, чтобы data URL сохраняли их состояние. Но они могут быть полезны как удобные закладки, если вам нужно вычислить пару выражений или накидать черновик заметки, не открывая тяжёлые «реальные» офисные приложения. В качестве бонуса: все эти крошечные приложения чрезвычайно уважительно относятся к конфиденциальности и не передают ваши данные (а также не хранят их).

Поэтому да, это больше похоже на шутку, чем на серьёзное приложение, однако я создал репозиторий для таких крошечных приложений на случай, если кому-нибудь захочется их использовать или настроить под свои нужды: http://github.com/zserge/awfice. Приветствуются пулл-реквесты и дальнейшие усовершенствования!



На правах рекламы


Встречайте! Впервые в России — эпичные серверы!
Мощные VDS на базе новейших процессоров AMD EPYC. Частота процессора до 3.4 GHz. Максимальная конфигурация — 128 ядер CPU, 512 ГБ RAM, 4000 ГБ NVMe!

VDSina.ru хостинг серверов
Серверы в Москве и Амстердаме

Комментарии 26

    +2

    В коде для электронной таблицы отсутствует кусок кода после script:
    "var z=Object.defineProperty,p=parseFloat;"

      +2
      Очуметь! Но таблица не работает в Firefox. (Еще в Firefox Ctrl+B, Ctrl+I это свои сочетания, но это такое)
        +1

        Возьмите с github автора.
        Автору (перевода) нужно поправить статью.

        +8

        Ну как же тут без 100 мегабайт фреймворков?..

          +9
          Не переживайте! Браузер добавляет недостающий вес. Я на примере Я.Бровзера (вынужденно импортозамещаемся): browser.dll — 158 мегабайт
          остальная мелочовка еще мег на 80, итого приближаемся к 240 мегабайтам.
          Mission Accomplished.
            +1

            А если ещё добавить все эти библиотеки операционной системы, драйвера чтоб все это попало на экран и пр., вообще десятки гигабайт выйдет!!!

              +4
              если ещё добавить все эти библиотеки операционной системы

              Либо мы рассматриваем приложения (автономные .exe файлы под Windows, например), а это 20 строк автора + 25 000 000 строк на 36 языках программирования Google Chrome,


              … или давайте сделаем офисный пакет в 0 строк, работающий на основе LibreOffice


              вообще десятки гигабайт выйдет!!!

              FreeDOS + Windows 3.11 + Office 1.0 вряд ли больше 10-20 МБ будет, вместе с кодом работы с самым железом

          +2
          Зачем такие сложности, когда в емаксе есть org-mode?! И весит меньше броузера и работает быстрее и без js
            +1
            А в чем тут, собственно, сложность?)
            +2

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

              +2
              Win+Shift+S и вставка. Ну или PrintScreen тот же.
              Заголовок спойлера

                0
                У меня от Win+Shift+S комп завис, аккуратнее с этой комбинацией)
              0
              Решение интересное!
              Как альтернатива для «набросать заметки» в Vivaldi существуют Заметки.
                +1
                Альтернатив много. Можно просто docs.new, sheets.new или slides.new в новой вкладке набрать, например.
                  0
                  Для этого нужно входить в аккаунт Google.
                    0

                    Конечно. Этот способ не без недостатков. Как и описанный в статье и в комментарии выше. Я лишь хотел сказать, что способов много. У автора просто интересный подход.

                +2
                Не связывайтесь с contenteditable, если можно. Потому что сложные вещи типа манипуляции селекцией, работа с буфером обмена — все это очень глючное, плохо документировано и не переносимо между браузерами.
                  +1
                  Напомнило Microsoft Works!

                  Помню его ещё на Windows 3.0 (кажется, он целиком на две дискеты помещался).
                    0
                    Такую бы красоту добавить в онлайн редактор, типа такого notepad.pw
                      0

                      А мне для презентаций вот такая штука понравилась: https://github.com/vinayak-mehta/present

                        0
                        Довольно неожиданно, что автор русский, но это всё равно перевод.
                          0

                          "Слово и дело" эпохи DOS. 64 килобайта мудрости, обеспечивающей WYSIWYG форматирование разными шрифтами, вставку картинок, резиновые таблицы в тексте, базовые табличные функции типа суммирования, поддержку печати на матричные и лазерные принтеры, макросы, проверку орфографии, русско-английский словарь… И на сдачу еще скринсейвер.

                            0
                            Был такой sled… 16K, много окон, файлы больше 64К. Мышки правда не понимал.
                              0

                              Придумали же название!
                              Доносы строчить :)

                                0
                                Есть такой браузер — оперА :-)
                              0
                              Есть такая вот маленькая электронная таблица. Один exe-файл размером в пол-мегабайта. А умеет многое…

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

                              Самое читаемое