【翻译】“使用Web实现世界上最小的办公套件“

247 阅读7分钟

前言

我们都熟悉传统的办公套件-文字处理器,电子表格,演示程序,也许是图表或笔记应用程序。我们已经在Microsoft Office和Google文档中看到了这些。都非常的强大。但是,构建办公套件的最小代码到底是多少呢?

平台

显然,我们的办公套件不会是台式机GUI应用程序-那些程序需要大量代码和精力来构建。同样也不是移动端的app。我们可能会考虑构建一个控制台(基于终端)的应用程序,实际上已经有了小到可怕的文本编辑器电子表格,但是如果将目标放在浏览器上会容易得多。

浏览器已经配备了不错的富文本编辑器(内容可编辑),并且对于数学表达式而言确实是很好的(尽管不安全)解析器

现在,我们能缩小多少呢?

文本编辑器

实际上,这是我使用多年的“应用程序”:

<html contenteditable>

对,就是这样。而且,可以将其转换为独立的URL,这就是当我需要便笺簿快速备忘时使用的方式:

data:text/html,<html contenteditable>

尝试将其粘贴到您的网址栏中。如果您的浏览器很好用,则应该可以使用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都是轮廓较薄的幻灯片。使用宽度和顶部填充的技巧是保持幻灯片的宽高比。可以尝试更改值以查看其如何影响布局。每个内部div是一个基本的RTF编辑器,具有相当大的字体,可从投影机屏幕上读取。

够好了。但是我们希望幻灯片上有标题和列表,不是吗?

让我们添加一些快捷键:

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留给其他你自己需要的设置,随时可以自定义。内容可编辑操作的完整列表可以在MDN上找到。

将上面的代码压缩一点,然后将其转换为数据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,然后可以在任何计算机上显示幻灯片。

快速绘图

前一阵子,我建立了onthesamepage.online , 可以与其他人快速勾勒出想法,但是尽管它很简单,但它仍然比我们在这里做的要大。 至少,我们只能在画布上绘制线条。我们需要一个元素,一些鼠标/触摸处理程序和一个标志,以指示按下鼠标时实际上正在绘制鼠标移动。

这里值得一提的是,具有id的元素可以作为window [id]或window.id访问。长期以来尚未并没有标准化,一直是都是IE里面的hack操作,现但是在已经成为标准

另外,我将光标位置处理移动到单独的短函数中,以便在mousedown和mousemove处理程序中重用它们。最后,我重置主体元素的边距以使画布全屏显示。

缩后的代码大约为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>

电子表格

这可能是最复杂的应用程序,也是最大的应用程序,但是我们将尝试将每个应用程序的大小限制在1KB以下。

布局将很简单。 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;
    }
  }
}

现在,我们有了一个大单元格网格,其中包含行和列。是时候添加一个表达式计算器了。我们可以使用3个数组来实现一个hacky,但大多数情况下都可以使用的求值器具,包含3个数组,一个用来存储所有输入字段(以获取其实际输入值,数字或公式),一个用来实现智能getter方法的数组,如果请求变量则调用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 gray",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>

认真的吗?

嗯,绝对不能替代适当的办公套件。但这是极简主义和小巧代码的很好展示。所有这些应用都是短暂的,如果刷新页面,它们将丢失其状态,并且看起来数据URL无法保持其状态。但是,如果你需要在不打开繁重的“真实”办公应用程序的情况下进行一些计算或起草一些快速笔记,它们可能会作为快速书签很有用。另外,所有这些微型应用程序最终都尊重你的隐私,并且不共享你的数据(也不要存储数据)。

因此,是的,这不是一个正规的应用程序,更多的可能是一个demo。但是,我仍然为这些微型应用程序创建了一个存储库,以防万一有人想要使用它们或进一步根据自己的需要进行自定义:github.com/ zserge / awfice。欢迎PR和进一步的改进!

原文地址:

zserge.com/posts/awfic…