#13 转载:在 URL 中存储状态

2023-04-08

I'm working on a flowchart editor that runs in the browser, and I wanted a way for people to use it without having to sign in, or store any data on our server. I wanted to give them control over their data and to be able to store it locally to open and edit later. And also easily share it with other people. It's easy to do this by supporting file upload/download, but I wanted something simpler, like the ability to share by sending a url. I also didn't want to store anything on the backend (at least for the free tier).
我正在开发一个在浏览器中运行的流程图编辑器,我希望人们不用登录,不用在我们的服务器上存储任何数据就能使用它。
我想让用户控制自己的数据,并能够将其存储在本地,以便以后打开和编辑,而且也很容易与他人分享。
通过支持文件上传/下载很容易做到这一点,但我想要一些更简单的东西,比如通过发送 url 来共享。
我也不想在后端存储任何东西(至少对于免费服务部分)。

I decied to encode the entire application state as a Base64 encoded string in the hashmark of the url. For example, a url would look like (note its truncated since they are very long):
我决定将整个应用程序状态编码为 Base64 字符串,放在 url 井号部分,例如(截断之后):

knotend.com/g/a#N4IgzgpgTglghgGxgLwnARgiAxA9lAWxAC5QA7X...

Everything after the /g/a# is a stringified version of a json object that contains all the information about the flowchart. It gets stringified, then compressed, then Base64 encoded. I update the url on every graph edit, so copying the graph state is as simple as copying the url in your browser bar.
流程图有关的所有信息存储在 JSON 对象中,被转换成字符串,压缩,Base64,最后放在 URL 中。
每次编辑图形的时候更新 URL,复制 URL 就可以复制图形状态。

Here's the pseudo code for creating the url, and then later reading it:
伪代码:

const stateString = JSON.stringify(appState); // appState is a json object
const compressed = compress(stateString);
const encoded = Base64.encode(compressed);
// Push that `encoded` string to the url
// ... Later, on page load or on undo/redo we read the url and
// do the following
const decoded = Base64.decode(encoded); // same encoded as above, but read from url
const uncompressed = uncompress(decoded);
const newState = JSON.parse(uncompressed);
// Now load your application with the newState

There are several options for implementing the compress/uncompress functions, such as lz-stirng or pako.
有一些库可以实现压缩解压缩,比如 lz-string,pako。

Since I update it on every graph edit, I get something major for free -- undo/redo. The browser's history stack becomes my undo/redo functionality. The user can hit the browser back/forward buttons, or Command-Z,Command-Shift-Z which I map to history pop and push. This is a major win for something which is a free product that I wanted to ship quickly.
因为每次编辑图形都会更新 URL,依赖浏览器的历史功能,通过前进、后退按钮,或者快捷键 Command-Z、Command-Shift-Z,还能实现撤销和重做。

Another great benefit is that these urls can be embedded. That means the user can put their graph on any web page that supports embedding. I see people typically do this with wikis like Notion, which means you can share with a team without anyone needing an account on my site.
另一个巨大的好处是这些 URL 可以被嵌入。这意味着用户可以将他们的图形放在任何支持嵌入的 Web 页面上。
我看到人们通常在 Notion 这样的笔记软件中使用,这意味着您可以与团队共享,而不需要任何人在我的网站上注册帐户。

You can see how it works by checking out knotend, the keyboard-centric flowchart editor that I'mw working on.
你可以在 knotend 中看到这个到底是怎么实现。knotend 是我正在开发的一个键盘为中心的流程图编辑器。

Prior work and thank yous.
之前的工作,谢谢。

I'm not the first one to take this approach. I've seen atleast mermaidjs do this before, and I'm sure there are others.
我不是第一个采用这种方法的人。我以前至少见过 mermaidjs 这样做,我相信还有其他人。

Thank you to this comment by redleader55 on hacker news for pointing out that using window.location.hashmark is better for storing longer urls since some browsers will truncate the url when sending it over http. But that this doesn't apply to the hashmark, which stays client side.
感谢 redleader55 在 hacker news 上的评论,你指出使用 window.location.hashmark 更适合存储较长的 url,因为一些浏览器在通过 http 发送 url 时会截断 url。但这并不适用于 hashmark,它停留在客户端。

See conversation on Hacker News https://news.ycombinator.com/item?id=34312546

#12 Next、Nuxt、Nest的区别

2023-02-20

Twilio Blog 的文章《Next, Nest, Nuxt… Nust?》,讲 Next.js,Nest,Nuxt 这几个名字非常相近的 NodeJS 框架到底有什么区别。

flowchart TD
    A[Framework purpose?] -->|Pure Backend| B(Plain HTTP Handlers?)
    B -->|Y| C(express)
    B -->|N| D(nest)
    A -->|Focus on Rendering| E(Rendering Type)
    E -->|CSR| F(React<br>Angular<br>Vue<br>NuxtJS)
    E -->|Backend light| H(Next<br>NuxtJS)
    E -->|Mostly SSR| G(Gatsby<br>Next<br>NuxtJS)

流行程度对比:

  • Express
  • Nest

...

  • Next.js
  • Nuxt
  • Gatsby

...

  • React
  • Vue
  • Angular

Express

Express 是 Node 圈子的老牌 HTTP Server 框架,根据 NPM 下载量来看,可以说是事实标准。

Nest

LOGO

基于 Express,当然也支持替换成其他的 HTTP Server 库。

Next

Next.js 是一个基于 React 的前端应用开发框架。

Next.js 为您提供生产环境所需的所有功能以及最佳的开发体验:包括静态及服务器端融合渲染、 支持 TypeScript、智能化打包、 路由预取等功能 无需任何配置。

Nuxt

基于 Vue 的前端应用开发框架。

#11 HTML5 Boilerplate

2021-07-26

可以保证跨浏览器兼容性的一套 HTML 模板,我决定将其应用在本站。

HTML5 Boilerplate is an HTML, CSS and JavaScript template (or boilerplate) for creating HTML5 websites with cross-browser compatibility.

#9 浏览器端存储

2020-03-02

早期只有一种浏览器存储方式,就是万维网早期,由网景公司设计,加入了 HTTP 1.0 的 Cookie。
HTTP5 的时代,一次性加入了三种 API,分别是 Web Storage,IndexedDB,Web SQL。

  • Cookie:通过小型文本文件,在浏览器端存储一些字符类型 key-value 数据,支持设置一些属性。
    1. 有单个 Cookie 的大小限制,也有 Cookie 总数限制。这个因浏览器不同而不同(大小尽可能控制在 4KB 以内)。
    2. 每一次 HTTP 调用都会自动带上,发送给服务器端。
  • Web Storage:可以存储大量字符类型的 key-value 数据。
    • localStorage:没有时间限制。
    • sessionStorage:会话结束时自动清除。
  • IndexedDB:NoSQL 数据库,可以存储大量的结构化数据。API 相对复杂一丢丢
    • 功能强大,甚至支持事务和索引。
    • 异步 API。
  • Web SQL:基于 SQL 的浏览器端数据库,后来被废弃。

Cookie

Set-Cookie: name=value; expires=Mon, 21 Oct 2019 07:28:00 GMT; path=/; domain=.example.com; secure; HttpOnly
  • expires 过期时间,GMT 格式。如果不设置该属性,则 Cookie 的生命周期为当前会话,即关闭浏览器后 Cookie 就会被删除。
  • path 路径,表示该 Cookie 归属于哪个路径。默认为当前页面的路径。
  • domain 域名,表示该 Cookie 归属于哪个域名。默认为当前页面的域名。
  • secure 只能通过 HTTPS 协议传输,不能通过 HTTP 协议传输。
  • HttpOnly 只在网络传输时使用,不能通过 JavaScript 访问。

Web Storage

localStorage.setItem('key', 'value');
var value = localStorage.getItem('key');

sessionStorage.setItem('key', 'value');
var value = sessionStorage.getItem('key');
  • setItem(key, value) 存储数据
  • getItem(key) 读取数据
  • removeItem(key) 删除数据
  • clear() 清空数据 (删除所有的键值对)
  • key(index) 获取键名 (根据索引获取对应的键名)

IndexedDB

没有研究过。

// 打开数据库
var request = indexedDB.open('myDatabase', 1);

// 创建对象仓库
request.onupgradeneeded = function(event) {
  var db = event.target.result;
  var objectStore = db.createObjectStore('users', { keyPath: 'id' });
  objectStore.createIndex('name', 'name', { unique: false });
  objectStore.createIndex('age', 'age', { unique: false });
};

// 存储数据
request.onsuccess = function(event) {
  var db = event.target.result;
  var transaction = db.transaction(['users'], 'readwrite');
  var objectStore = transaction.objectStore('users');
  var user = { id: 1, name: '张三', age: 20 };
  var request = objectStore.add(user);
  request.onsuccess = function(event) {
    console.log('数据存储成功');
  };
};

// 读取数据
request.onsuccess = function(event) {
  var db = event.target.result;
  var transaction = db.transaction(['users'], 'readonly');
  var objectStore = transaction.objectStore('users');
  var index = objectStore.index('name');
  var request = index.get('张三');
  request.onsuccess = function(event) {
    var user = event.target.result;
    console.log(user);
  };
};

#7 Bootstrap 基础模板

2019-03-04
<!doctype html>
<html lang="zh-CN">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>HELLO WORLD</title>

    <link rel="stylesheet" href="/static/bootstrap@3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
</head>

<body>
    <div class="container">
        <h1>你好,世界!</h1>
    </div>

    <script src="/static/jquery@1.12.4/dist/jquery.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
    <script src="/static/bootstrap@3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
</body>

</html>
  1. 样式参考:https://v3.bootcss.com/getting-started/#examples
  2. 相关文件:

#3 转载:jQuery 插件开发全解析

2015-11-09

jQuery 插件的开发包括两种:
一种是类级别的插件开发,即给 jQuery 添加新的全局函数,相当于给 jQuery 类本身添加方法(jQuery 的全局函数就是属于 jQuery 命名空间的函数)。
另一种是对象级别的插件开发,即给 jQuery 对象添加方法。下面就两种函数的开发做详细的说明。