我的个人资料网站现在是一个终端
年轻的时候,我总以为我的个人资料网站应该很酷,功能齐全,色彩鲜艳,动画精美,采用最新的尖端前端技术构建……
 结果,年纪越大,我越喜欢简洁的终端。没有用户界面,只有文本和命令。
我上次更新我的个人资料网站时,它看起来像这样:
这已经够简约了,对吧?但这还不够。现在我的个人资料网站只是一个终端:
让我们看看这是如何实现的。
务实的做法
几天前,我正在构思这个想法,发现了一个很酷的库:xterms。很多应用程序都在使用它,VS Code 就是其中之一。我决定尝试一下,看看它到底有多复杂,于是我查看了文档,开始把代码添加到我的网站。正如你所见,文档写得相当不错,虽然它们确实是从 TS 文档自动生成的,但这本身就很好,因为这意味着代码本身有很好的文档记录。
在开始编码之前我设定了一些要求:
- 我不想使用npm模块。我希望我的网站源代码简洁、精简。
- 我想使用所有(相关)浏览器支持的JavaScript 模块
- 终端命令应该是抽象的,以便我可以通过一些更改随意删除或添加命令
那么,如何xtermjs在不使用的情况下安装npm?解决方案很简单,我托管这些文件。我使用以下命令从 npm 包中提取了这些文件
npm v xterm dist.tarball | xargs curl | tar -xz
并搬进package/lib/xterm.js了app/
要使用 JavaScript 模块,我只需要将main.js文件导入为模块
<script type="module" src="/app/main.js"></script>
终端命令
尽管不使用,typescript但假设终端命令实现以下接口
interface Command {
  id: string;
  description: string;
  usage: string;
  args: number;
  run: (terminal: Terminal) => Promise<void>;
}
然后我们需要一个命令运行器来解析用户输入
interface CommandRunner {
    (term: Terminal, userInput: string) => Promise<boolean>;
}
false如果未找到命令,运行器将返回。
 现在我们定义一个命令:
const lsCommand =   {
  id: "ls",
  description: 'list files',
  usage: '[usage]: ls filename'
  args: 0,
  async run(term, args) {
    for (const file of files) {
      term.write(file.name + '\t\t');
    }
  },
};
现在我们已经塑造了command,我们可以考虑处理用户输入。
终端基本功能
终端应支持:
- 
  它应该显示 prompt
- 
  ctrl + l:应该清除终端
- 
  ctrl + c:应该发送SIGINT
- 
  enter:应该从当前用户输入运行命令
终端还应该处理常见错误:
- 未找到命令
- 带有错误参数的命令
考虑到这一点,我们就可以开始处理用户输入。
xterm提供了一个onKey接收处理函数的事件({ key, domEvent }) => void,因此用户每次按下按键时,我们都会收到一个事件。这意味着我们需要跟踪用户输入,并将每个按键添加为一个字符。当用户按下按键时,enter我们应该评估迄今为止的输入。非常简单
let userInput = '';
if (ev.keyCode == 13) {
  await runCommand(term, userInput);
  userInput = '';
  prompt(term);
} else {
  term.write(key);
  userInput += key;
}
注意: xterm不呈现用户输入,因此我们需要在有意义时执行此操作(不是输入,不是箭头键等)
处理清屏可以实现为
if (ev.ctrlKey && ev.key === 'l') {
  term.clear();
  return;
}
和SIGINT
if (ev.ctrlKey && ev.key === 'c') {
  prompt(term);
  userInput = '';
  return;
}
现在我们已经有了一个非常基本的工作终端,所以让我们添加一些命令
基本命令
最知名的命令是什么?我希望我的终端能够使用cat、ls、rm、exit。但请记住,这个终端实际上是我的个人资料网站,所以它们应该在这个上下文中有意义。所以我决定终端应该有一个文件系统,其中的文件格式如下
interface File {
  name: string;
  content: string;
}
例子
const files = [{ name: "about.md", content: "once upon a time"}];
考虑到这一点,cat将打印文件内容,ls将打印每个文件的名称,rm并将从数组中删除该文件。
对于exit命令,我们可以从 javascript 关闭窗口:window.close()。
更进一步
我决定创建一个文件,blog.md其中包含我最近的 5 篇博文。为了获取这些信息,我使用了Hugo为我的博客
 生成的 RSS feed xml 文件。我需要做的就是获取这个文件,解析文档,然后获取每篇博文的标题和链接:xml
export async function fecthLastPosts() {
  const res = await fetch('/blog/index.xml');
  const text = await res.text();
  const parser = new DOMParser();
  const xmlDoc = parser.parseFromString(text,"text/xml");
  const posts = xmlDoc.getElementsByTagName('item');
  const lastPosts = [];
  for (let i = 0; i < 5; i++) {
    const title = posts[i].getElementsByTagName('title')[0].childNodes[0].nodeValue;
    const link = posts[i].getElementsByTagName('link')[0].childNodes[0].nodeValue;
    lastPosts.push(title + `\r\n${link}\r\n`);
  }
  files[0].content = lastPosts.join('\n');
}
现在cat blog.md打印我最近的 5 篇帖子,而且由于插件web link的存在,xterm每个链接都可以点击。好棒啊。
 不过,干嘛就到此为止呢?每个hackerman终端都应该有相应的whoami命令。所以这个命令只会打印我自己的信息。
另外,一些很酷的 Web 应用包含猫咪照片,所以我决定编写一个randc命令,可以打开一张随机的猫咪照片。
 为此,我找到了这个很棒的REST API
  {
    id: "randc",
    description: 'get a random cat photo',
    args: 0,
    async run(term, args) {
      term.writeln('getting a cato...');
      const res = await fetch('https://cataas.com/cat?json=true');
      if (!res.ok) {
        term.writeln(`[error] no catos today :( -- ${res.statusText}`));
      }  else {
        const { url } = await res.json();
        term.writeln(colorize(TermColors.Green, 'opening cato...'));
        await sleep(1000);
        window.open('https://cataas.com' + url);
      }
    },
  },
结果:
我觉得这应该够用了profile terminal。我对它的简洁性和我实现的命令非常满意。
 以后我可能会添加更多命令,也可能会实现streams,纯粹为了好玩。
你想在配置文件终端中添加什么命令?
 快来试试吧:https://protiumx.dev
更新:
我重构了项目结构,以提高可读性并使其更通用。
 它还会从本地存储加载您的命令历史记录。所有更改可在此处查看:https://github.com/protiumx/protiumx.github.io/pull/1
更新 2:
- rm支持全局模式
更新 3:
- 添加的man命令
- 添加的uname命令
其他文章:
👽
文章来源:https://dev.to/protium/my-profile-website-is-now-a-terminal-2j57 后端开发教程 - Java、Spring Boot 实战 - msg200.com
            后端开发教程 - Java、Spring Boot 实战 - msg200.com
          


