本文参加「自力更生」征文活动。


看到了首页许多关于自托管服务的文章,容易发现它们常有一个共同特点:均对服务器/NAS有刚需,且配置过程冗余。同时,这些服务虽然提供了相对较高的自由度,但仍然有一定的限制,很难完全满足每个人的需求。从某种程度上讲,这些服务的主动权并不完全在你手里:倘若这些服务的开发者对其进行大幅迭代,甚至改的面目全非,你可能就要背起数据「行囊」,走向另一个服务。

在尝试N多种工具(见我的 2022:HTML、数字生活和写作方法论)后,我选择彻底从头开始,迭代一个属于自己的数据记录系统,乃至——「电子工作台」。从 2022 年到如今,也逾一年半载,在此把我的心得体会记录在这里。

这么高的自由度是如何实现的?上手门槛高吗?

这主要归功于这个工作台的实现方式。

译文 | 过去 12 年里,我的生产力工具只是一个 .txt 文件 的启发,我将所有笔记写在了同一个 HTML 文件里,HTML 最大的优势就是能够与 css /JavaScript 密切配合,CSS:层叠样式表是一种样式表语言,用来描述 HTMLXML(包括如 SVGMathMLXHTML 之类的 XML 分支语言)文档的呈现方式。它提供了强大的表现力从而允许我完全自定义我的文档的样式。而 JavaScript 则是一种强大的编程语言,允许我定义网站的行为。这也是文档进化为「电子工作台」的基础。

至于上手门槛...这取决于你的需求。如果你仅仅是想要获取能够安心记录的、颜值相对高的、可自定义性强的一个记录处,那么它很好入手;如果你想像我一样将它打造成一个独属于自己的「个人电子工作台」,那么你可能需要颇费一番周折(我本人用了整整两年半),并学习一些 CSS 和 JavaScript 的相关知识才能把它配置的称心如意。不过,这一套组合拳其实并不难学,这主要取决于你自定义的愿望是否足够强烈;同时我也会提供一些功能模块供大家使用。

如果你感兴趣,就接着往下看。

第零步:挑一个顺手的编辑器

工欲善其事,必先利其器。我们要撰写的 HTML 文档属于代码的一种,一个好用的编辑器能极大提高美观度与效率。这里推荐 Visual Studio Code ,它最大的特点是简单易上手,便于新手使用。在桌面上新建一个名为「my.html」的文件,开始我们的旅程吧!(当然名字随便起,不过下文如果用到「my.html」这个名字,记得换成你自己起的名字!)

渲染 markdown ,记录优先

在我派对 markdown 有诸多介绍,你可以参考这篇文章快速入门。以下主要介绍 markdown 如何便捷的插入 HTML 文档块中。

<!DOCTYPE html>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<div id="inbox">
## 这里是你的 markdown 内容。
***hello,world!***
- 写少数派投稿
- 看世界杯预选赛
</div>
<script>
    document.getElementById("inbox").innerHTML=marked.parse(document.getElementById("inbox").innerHTML);
</script>

这样,就实现了一份markdown文档的渲染,其实十分容易。

看看效果

渲染 HTML 文档用的是浏览器,直接双击写好的文件,然后就可以看到效果。

不过确实页面有一点素,接下来轮到 css 登场!

美化页面

将以下内容粘贴到新建好的文档的最上方。

<style type="text/css">
    h2 {
        border-bottom: 2px solid #b0b7bf;
        width: 100%;
        padding-bottom: 2px;
        color: #2e80f2;
    }

    h3 {
        color: #2e80f2;
    }

    h4 {
        color: SlateBlue;
    }

    b,
    strong,
    i,
    em {
        color: #ff82b2;
    }

    a:link[href^="#"],
    a:visited[href^="#"] {
        padding: 1px 10px;
        text-decoration: none;
        color: #557ded;
        border-radius: 4px;
        background-color: #eef3fe;
    }

    a:hover[href^="#"] {
        padding: 1px 10px;
        text-decoration: none;
        color: #e1eafa;
        border-radius: 4px;
        background-color: #557ded;
    }

    a {
        color: #50aaef;
        text-decoration: none;
        background-color: #fff7c3;
    }

    mark {
        background-color: #fff7c3;
        border-radius: 5px;
    }

    ul {
        list-style-type: disc;
    }

    ul li::marker,
    ol li::marker {
        color: #b0b7bf;
    }
</style>
调整以后的效果

可以看到好看了许多。这其实就是 css 在调整页面的样式。那如果你想将二级标题改为红色呢?只需删掉h2及其后面{}内包裹的内容,并添加以下内容:

h2 {
        border-bottom: 2px solid #b0b7bf;
        width: 100%;
        padding-bottom: 2px;
        color: red;
    }

 

可以看到标题的确变为了红色。

本段的最后再给大家推荐几个样式主题,注意要使用的话记得先把上面我的自定义样式删除,以免导致渲染问题。

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify@4/themes/vue.css"/>

 

<link href="https://cubox.pro/article/css/reader.css" rel="stylesheet" />

 

参考文章:

特殊需求:代码高亮/公式

一般的 markdown 编辑器都会自带代码块高亮和 LaTeX 公式插入,但 HTML 并没有自带支持。好在已经有热心的开发者为我们做好适配,我们只需要在<div id="inbox">前插入以下代码:

<!-- LaTeX嵌入网页 -->
<script id="MathJax-script" async src="https://cdn.bootcdn.net/ajax/libs/mathjax/4.0.0-alpha.1/es5/tex-svg.js"></script>
<script>
MathJax = {
    tex: {
        //inlineMath: [['$', '$'], ['\\(', '\\)']]
        inlineMath: [['$', '$'], ['\\$$', '\\$$']]
    },
    svg: {
        fontCache: 'global'
    }
};
</script>
<!--highlight.js,负责代码高亮-->
<link href="https://cdn.bootcdn.net/ajax/libs/highlight.js/11.9.0/styles/github-dark-dimmed.min.css#Github" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
<script>
    window.onload=hljs.highlightAll;
</script>
再看看效果?

添加标签,丰富呈现

众所周知,markdown 其实是 html 的一个子集,那么 html 本身又有什么功能呢?让我们从几个「标签」聊起。

一个 html 文档的整体框架

一般来说,一个 html 文档由以下几部分构成:

<!DOCTYPE html><!--说明这是一个 html 文件,一定要有-->
<html>
<head>
    <!--head标签中的内容通常不会显示,用来放一些脚本,比如上文的LaTeX,marked和hljs(都是用来配置 markdown 的工具,在上文中出现过)应该写在这里-->
</head>
<body>
    <!--body是文档的主体,内容应该写在这里-->
</body>
</html>

但是出于减少文件大小和简洁的目的,我一般遵循Google Style Guide for HTML(中文翻译见下图),省略所有可以省略的标签。

于是文档变成这样:

<!DOCTYPE html><!--说明这是一个 html 文件,一定要有-->
    <!--脚本放这里-->

    <!--内容放这里-->

常见 markdown 样式与 html 标签的一一对应

此处直接放一张表。

描述markdownHTML TAG
标题# 一级标题 ## 二级标题…h1,h2…h6
粗体**粗体**b , strong
斜体*斜体*em, i
分割线---hr
无序列表* 1 或- 1或 + 1ul
有序列表1.ol
引用>blockquote
超链接[url](url)a
图片![img](imgUrl)img
表格见下table
代码段``` your code```code
段落无特殊表示p
//一个表格

| Tables        | Are           | Wonderful!  |

| ------------- |:-------------:| -----:|

| cool is      | right-aligned | 0 or 1 |

markdown中没有的标签

  • <sub>:下标文本,与之对应的还有<sup> :上标文本
  • <details><summary>:可折叠文本。一个如下的示例:
<details open><summary>hello,</summary>world!</details>
<details><summary>hello,</summary>world!</details>

所以,html 本身并不难上手。

进阶:卡片效果

我会把所有内容放在一个 ul 无序列表中,每一个 li 节点都类似于一张卡片。所以...为什么我不直接把每一个 li 节点内容分割成卡片呢?
将以下 css 代码添加到 style 标签内。

:root {
    --back: #FFF;
    --box-shadow: #DDDDDD;
    --bg: #FAFAFA;
    --tr: #F2F2F2;
}

@media (prefers-color-scheme: dark) {
    :root {
        --back: #303030;
        --box-shadow: #222222;
        --bg: #000;
        --tr: #0D0D0D;
    }
}

body {
    background: var(--bg);
    /* background: url("./fufuer.jpg") no-repeat center center fixed;
    -webkit-background-size: cover;
    -o-background-size: cover;
    background-size: cover; */
}

.card {
    margin: 20px 0;
    background: var(--back);
    padding: 20px;
    border-radius: 6px;
}

.card:hover {
    box-shadow: 0px 2px 16px var(--box-shadow);
}

 

<div class="card"></div><!--每一个这样的 div 都是一张卡片-->
<li class="card"></li><!--当然也可以是其他元素-->

 


 

我日常使用的文档示例

添加脚本,满足需求

截至现在,我们做到的其实仅仅是一个能渲染 markdown 的、颜值相对高的 markdown 渲染器;现在,我们要向里面加功能了。

或许不仅仅是速记板

代码构成

接下来,让我们实现一个速记板,并从中感受 JavaScript 的代码风格。

!!!接下来的部分本文假设读者至少有一定的编程基础(...只要知道变量/循环/条件/函数等名词的意思即可),如果你不了解,可以简单的学习一门编程语言。其实 JavaScript 本身也是一门极佳的入门语言,你可以阅读JavaScript 第一步 - 学习 Web 开发 | MDN以了解,只要读完「JavaScript 第一步」和 「JavaScript 基础」两部分就可以了。当然如果你不懂也没关系,接着往下读吧!

<style>
    textarea {
    font-size: 0.8rem;
    letter-spacing: 1px;
    }

    textarea {
    padding: 10px;
    max-width: 100%;
    border-radius: 5px;
    border: 1px solid #ccc;
    box-shadow: 1px 1px 1px #999;
    }
</style>
<div class="card" id="inputer">
    <script>
        let inputer_count=0;
        function inputer_upload(){
            //这个函数的功能是载入本地文件到速记框中
            let inputer_reader = new FileReader();//新建一个文件读取器
            inputer_reader.onload = function(){//指定文件上传完成时的行为
                document.getElementById("inputer_content").value=this.result;//document.getElementById("inputer_content").value就是下文中输入框的值,this.result是JavaScript特有写法,this指代inputer_reader,result是读取到文件的内容。
            }
            inputer_reader.readAsText(document.getElementById("filePicker_inputer").files[0]);//读取文件上传器中的内容到文件读取器中
        }
        function inputer_download(){ 
            //这个函数的功能是将速记框中的内容下载到本地文件中
            //主要思路是先生成一个链接,其指向的内容是速记框中的内容,然后再模拟点击这个链接从而实现下载
            let inputer_blob = new Blob([document.getElementById("inputer_content").value]);
            let inputer_anchor = document.createElement("a"); 
            inputer_anchor.href = window.URL.createObjectURL(inputer_blob);
            inputer_anchor.download = document.getElementById("inputer_name").value;
            inputer_anchor.target ="_blank";
            inputer_anchor.style.display = "none"; // just to be safe! 
            document.body.appendChild(inputer_anchor); 
            inputer_anchor.click(); 
            document.body.removeChild(inputer_anchor); 
        }
        function inputer_runCode(obj){
            //这个函数的功能是在新页面中运行速记框中的 html 文档
            let inputer_window=window.open();//打开一个新窗口并与变量 inputer_window 绑定
            inputer_window.opener = null;//新窗口链接为空
            inputer_window.document.write(obj);//向新窗口中写入内容
            inputer_window.document.close();//关闭新窗口,当然这是最后的工作
        }
        function inputer_insert(obj,identify){
            //这个函数的功能是在本页面中插入速记框中的 html 文档
            let inputer_inserter=document.createElement("div");//新建一个元素
            inputer_inserter.id=identify;//元素的id
            inputer_inserter.innerHTML = obj;//元素的内容就是速记板的内容
            document.getElementById('inputer').appendChild(inputer_inserter);//添加元素
            inputer_scriptfinder(document.getElementById(identify));
            inputer_count+=1;
            document.getElementById("inputer_deleter").innerHTML="delete:"+inputer_count;
        }
            function inputer_scriptfinder(obj){
                //如果直接插入html的话由于安全策略其中的脚本不会被执行,所以需要调用此函数另外重新运行
                //原理是在原 script 标签内插入新的 script 标签绕过 innetHTML 安全策略,从而执行
                for(let i of obj.children){
                    if(i.tagName=='SCRIPT'){
                        let g = document.createElement("script");
                        g.text = i.innerHTML;
                        if(i.src!=''){
                            let scriptAttribute=["src","charset","async","defer"];
                            for(let j of scriptAttribute)g.setAttribute(j,i.getAttribute(j));
                        }
                        i.before(g);
                        g.remove();
                    }
                    inputer_scriptfinder(i);
                }
            }
        function inputer_delete(){
            document.querySelector('#inputer :last-child').remove();
            inputer_count-=1;
            document.getElementById("inputer_deleter").innerHTML="delete:"+inputer_count;
        }
    </script>
    <input type="file" name="file" id="filePicker_inputer" onchange="inputer_show()"  /><br />
    <textarea id="inputer_content" cols="40%" rows="15"></textarea><br />
    <textarea id="inputer_name" cols="40%" rows="1"></textarea><br />
    <button type="button" onclick="inputer_download()">download</button>
    <button type="button" onclick="inputer_runCode(this.offsetParent.getElementsByTagName('textarea')[0].value)">new window open</button>
    <button type="button" onclick="inputer_insert(document.getElementById('inputer_content').value,document.getElementById('inputer_name').value)">insert!</button>
    <button type="button" onclick="inputer_delete()" id="inputer_deleter">delete:0</button>
</div>

开始使用

使用非常简单:点击「选择文件」来加载本地文件/直接在速记框内输入内容/点击「download」来下载速记框内容/点击「new window open」来新页面打开速记框内容/点击「insert」来新页面打开速记框内容

但实际上,insert 插入前会渲染插入的文字,所以样式/脚本也可以通过这种方式带入页面。

享受音乐的力量!

代码构成

同理,将代码和注释贴上:

<style>
.musicontorl{

        border-radius: 5px;

    }

    .musicDiv {

        width: 75%;

        border: 1px solid skyblue;

        margin: 10px;

        padding: 5px;

        padding-top: 15px;

        overflow: auto;

    }
</style>
<script>
function randomNum(minNum, maxNum) {
        // 一个随机数函数,用途是生成[minNum, maxNum]区间内的随机数
                switch (arguments.length) {

                    case 1:

                        return parseInt(Math.random() * minNum + 1, 10);

                        break;

                    case 2:

                        return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);

                        break;

                    default:

                        return 0;

                        break;

                }

            }

            function playMusic(musicobjection) {
        // 主函数,从输入框中接受歌单数据和播放器名称
                let musicList=musicobjection.list.reverse();
        // 内部歌单初始化
                let wid=musicobjection.name;

                let israndom = false;

                function objecter(obj) {
        // 模板函数,用于生成播放器控件
                    let object = (obj.tag != undefined) ? document.createElement(obj.tag) : obj.method;
        // object.method:创建的标签类型
                    if(obj.parent!=undefined)obj.parent.appendChild(object);

                    if (obj.identity != undefined) {
            // object.id:创建标签的 id
                        object.id = wid + '.' + obj.identity;

                        eval(obj.identity + '\= object');

                    }
            // 将 obj.features 的属性赋给标签
                    if(obj.features!=undefined)for (let i of Object.keys(obj.features)) object[i]=obj.features[i];
            // 绑定监听器
                    if (obj.eventer != undefined) document.getElementById(object.id).addEventListener("click", obj.eventer);

                }

                function checkInfor(obj) {

                    return obj.identity == "Infor";

                }

                function playwer(src) {
        // 更换歌曲的重载函数
                    player.load();

                    player.src = src;

                    document.getElementById(wid + '.InforUrl').innerHTML = "url:" + src;

                    setTimeout(() => {

                        player.play();

                    }, 512);

                }

                function music_next() {
        // 下一曲函数
                    let src = musicList.pop();

                    musicList.unshift(src);

                    return playwer(src);

                }

                function music_last() {
        // 上一曲函数
                    let src = musicList.shift();
                    musicList.push(src);
                    return playwer(src);
                }

                function music_random() {
        // 随机下一曲函数
                    return playwer(musicList[randomNum(1, musicList.length)]);
                }

                function music_src() {
        // 音乐链接的计算函数
                    return (israndom) ? music_random() : music_next();
                }

                function music_looping(getid) {
        // 循环面板函数
                    document.getElementById(getid + ".player").loop = !document.getElementById(getid + ".player").loop;

                    document.getElementById(getid + ".InforLoop").innerText = "isloop:" + document.getElementById(getid + ".player").loop;

                }

                function music_randoming(getid) {
        // 随机面板函数
                    israndom = !israndom;

                    document.getElementById(getid + ".InforRandom").innerText = "israndom:" + israndom;

                }

                objecter({ identity: "playbox", tag: "div", features: { className: "musicDiv" }, parent: document.getElementById("musicplayer") });

                objecter({ identity: "player", method: new Audio(), features: { preload: true, controls: true, loop: false, autoplay: true }, parent: playbox });

                let music_object = [
        // 一个播放器控件的所有函数
                    { tag: "br", parent: playbox },

                    { identity: "randomer", tag: "a", features: { innerHTML: wid + ".randomer", className: "musicontorl" }, parent: playbox, eventer: function () { music_randoming(wid); } },

                    { tag: "br", parent: playbox },

                    { identity: "looping", tag: "a", features: { innerHTML: wid + ".playLoop!", className: "musicontorl" }, parent: playbox, eventer: function () { music_looping(wid); } },

                    { identity: "Infor", tag: "ul", parent: playbox },

                    { identity: "controlup", tag: "button", features: { innerHTML: wid + ".up" }, parent: playbox, eventer: music_next },

                    { identity: "controldown", tag: "button", features: { innerHTML: wid + ".down" }, parent: playbox, eventer: music_last }

                ];

                for (let i of music_object) objecter(i);

                music_object.filter(checkInfor)[0].children = [

                    { identity: "InforLoop", tag: "li", features: { innerHTML: "isloop:" + player.loop }, parent: Infor },

                    { identity: "InforRandom", tag: "li", features: { innerHTML: "israndom:" + israndom }, parent: Infor },

                    { identity: "InforUrl", tag: "li", parent: Infor }

                ];

                for (let i of music_object.filter(checkInfor)[0].children) objecter(i);

                let musicer =music_src();

                player.addEventListener("ended", music_src, false);// 将 ended 事件绑定到 music_src 函数,一曲结束立即播放下一曲

            }

        </script>

        <mark>仅限配置 server 后可加载本地音乐</mark><br />

        <textarea id="song_sheet" rows="15" cols="40%">

{

    "list":[
        "http://music.163.com/song/media/outer/url?id=1950343972#可能",

        "http://music.163.com/song/media/outer/url?id=2160850755#未行之路",

        "http://music.163.com/song/media/outer/url?id=2155423468#希望有羽毛和翅膀",

        "http://music.163.com/song/media/outer/url?id=2155422573#使一颗心免于哀伤",

        "http://music.163.com/song/media/outer/url?id=2155423467#在银河中孤独摇摆",

        "http://music.163.com/song/media/outer/url?id=2112196350.mp3#浮光",

        "/music/终焉×end of all[原神 2024 新春会].mp3",

        "http://music.163.com/song/media/outer/url?id=2100372359.mp3#轻涟 Chinese",

        "http://music.163.com/song/media/outer/url?id=2100334024.mp3#轻涟 France",

        "http://music.163.com/song/media/outer/url?id=2026286081.mp3#舍离去",

        "http://music.163.com/song/media/outer/url?id=31234244.mp3#blessing(world))",

        "http://music.163.com/song/media/outer/url?id=2026565329.mp3#da capo",

        "http://music.163.com/song/media/outer/url?id=1859652717.mp3#moon halo",

        "/music/G大调弦乐小乐曲.mp3",

        "/music/春.mp3",

        "/music/Interstellar Main Theme.mp3",

        "/music/ryukyuvania.m4a",

        "/music/诀别书.mp3",

        "/music/rise(heroes).mp3",

        		"/music/野火.mp3",

        "/music/Alone.mp3",

        "/music/At The Edge.mp3",

        "/music/Crying In The Sun.mp3",

        "/music/Energy Drink.mp3",

        "/music/HOYO-MiX - Hustle and Bustle of Ormos 喧繁之港 [qmms2].flac",

        "/music/KemimikE - Mountains (Original Mix).mp3",

        "/music/Masked Heroes.mp3",

        "/music/Middle Of The Night-Original Mix.mp3",

        "/music/Monsters-Katie Sky.flac",

        "/music/Moonlight Sonata.mp3",

        "/music/Nevada.mp3",

        "/music/Rise - Epic Music.mp3",

        "/music/experience.mp3",

        "/music/faded.mp3",

        "/music/夜空中最亮的星.mp3",

        "/music/夜航星.mp3",

    ],

    "name":"musicplay"

}

        </textarea><br />

        <button type="button" onclick="playMusic(JSON.parse(document.getElementById('song_sheet').value));">enjoy!</button>//播放控件初始化并启动播放

开始使用

我零零散散自己攒了约 80 首歌曲,放在与 my.html 同级的 /music 文件夹内;另外一些来自于网易云音乐的开放 API ,我比较建议使用。在网易云音乐网页端,以这首未行之路 The Road Not Taken(原神动画短片「未行之路」插曲) - HOYO-MiX/Aimer为例,复制浏览器框中的链接,是https://music.163.com/#/song?id=2160850755,其id为2160850755,那么音乐的实际链接就是http://music.163.com/song/media/outer/url?id=2160850755(将 id 替换为你想要歌曲的 id )。

振兴 RSS!

众所周知,现在算法推荐系统已经深刻的、无孔不入的侵入了我们的生活,甚至连微信都开始搞公众号算法推荐;但是我希望能尽量拒绝算法造成的信息茧房,所以我选择 RSS。我相信使用 RSS 的以 hacker 为主,所以我直接将代码贴在下方,你可以直接复制使用。

<style>
.feeder-style {

        border-radius: 10px;

        box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.1);

        background-color: #FDFFFA;

        padding: 60px;

        width: auto;

        display: flex;

        flex-direction: column;

    }
   </style>
<li class="card">feeder:时隔一年多的长期项目取得阶段性进展
    <template>
    <div class="feeder-style">
        <a href="{{link}}"><h3>{{title}}</h3></a>
        <p>{{pubdate}}</p>
        <p>{{description}}</p>
        <!--<a href="{{enclosure}}">open enclosure</a>-->
    </div>
    </template>
    <script>
        function xml_upload() {
            let xml_reader = new FileReader();
            xml_reader.onload = function() {document.getElementById('rss').innerHTML = document.getElementById('rss').innerHTML + this.result;}
            xml_reader.readAsText(document.getElementById("filePicker_feed").files[0]);
        }

        function xml_download() {
            let xml_text = document.getElementById("rss").innerHTML;
            let xml_blob = new Blob([xml_text]);
            let xml_anchor = document.createElement("a");
            xml_anchor.href = window.URL.createObjectURL(xml_blob);
            //xml_anchor.download = "my-filename.txt";
            xml_anchor.download = "RSS" + Date.now();
            xml_anchor.target = "_blank";
            xml_anchor.style.display = "none"; // just to be safe! 
            document.body.appendChild(xml_anchor);
            xml_anchor.click();
            document.body.removeChild(xml_anchor);
        }
        /*在这里碎碎念一下:
        2023.07.15上午我验证了近两小时,确定代码并无问题,而网站疯狂报"Problem retrieving XML data"错误是由于一个"跨域访问"的问题,打开 DevTools 会发现"Access to XMLHttpRequest at 'https://lychee.love/feed/' from origin 'http://127.0.0.1:5500' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."报错和"xml.html:55 GET https://lychee.love/feed/ net::ERR_FAILED 200"报错,一个解决方法是在chrome安装目录下输入"chrome.exe --disable-web-security --user-data-dir=C:\MyChromeUserFata"并执行,以关闭其同源策略。

        2024.06.06:在使用 nginx 服务器后,我发现跨域问题消失了...一部分?

        2024.06.08:解决以上问题可采取以下手段:
        1.使用 moesif-origincors-changer 插件以伪造服务器允许 CORS 的假象;
        2.使用 CORS Proxy 服务,如[cors-anywhere](https://github.com/Rob--W/cors-anywhere);
        3.自建 CORS Proxy 服务器;
            P.S.也可以让服务器直接下载 feed 文件;
        4.使用以下 fetch 绕过CORS(亲测无效!),参考[Building an RSS reader in Javascript](https://dev.to/geekgalgroks/building-an-rss-reader-in-javascript-1ep0)
        ```javascript
        function getRssFeed(feed) {
            fetch(feed)
            .then(response => response.text())
            .then(str => new window.DOMParser().parseFromString(str, "text/xml"))
            .then(data => {
                console.log(data);
                const items = data.querySelectorAll("item");
                let html = ``;
                items.forEach(el => {
                html += `
                    <article>
                    <img src="${el.querySelector("link").innerHTML}/image/large.png" alt="">
                    <h2>
                        <a href="${el.querySelector("link").innerHTML}" target="_blank" rel="noopener">
                        ${el.querySelector("title").innerHTML}
                        </a>
                    </h2>
                    </article>
                `;
                });
                document.body.insertAdjacentHTML("beforeend", html);
            });
        }
        ```
        P.S 为什么无效呢?这是因为 fetch API 同样遵守同源策略,而所谓的"mode:no-cors"不过是返回了一个type为opaque的,里面什么都没有的response!参考[fetch() 简介](https://web.developers.google.cn/articles/introduction-to-fetch?hl=zh-cn | https://web.dev/articles/introduction-to-fetch?hl=zh-cn)
        5.下载 feed 源文件再让用户上传以在本地解析,绕过同源策略,然而我不会下载!!!附上可供参考代码一份(别想了,能用早用了):
        ```javascript
        window.onload=function(){    
            let eleForm = document.createElement("form");// 创建form元素
            eleForm.method = "get";
            eleForm.style.display = "none"; // 隐藏表单,因为它不需要显示在页面上
            eleForm.action = "https://sspai.com/feed";// 设置表单的action属性
            document.body.appendChild(eleForm);// 将form元素添加到文档体中
            eleForm.submit();// 提交表单,实现下载
        }
        ```
        6.别看 RSS 了![笑]*/
        /*console.log = (function (logFunc, isLog = true, isLogStack = true) {
        return function () {
            if (!isLog) {
                return
            }
            try {
                let arr = []
                arr.push(...arguments)
                arr.forEach((item, index) => {
                    if (Object.prototype.toString.call(item) === '[object Object]' ||
                    Object.prototype.toString.call(item) === '[object Array]') {
                    arr[index] = JSON.parse(JSON.stringify(item))
                    }
                })
                logFunc.call(console, ...arr)
                isLogStack ? console.trace() : null
            } catch (e) {
                console.log(`a log error: ${e}`)
            }
        }
        })(console.log)
        console.error=(function (oriLogFunc) {
            return function () {
                oriLogFunc(...arguments);
                if(test!=undefined)test=[...arguments][0];
            }
        })(console.error);*/
        function cors(url){
            // const CORSer="http://crossorigin.me/";
            const CORSer="https://cors-anywhere.herokuapp.com/";
            loadXMLDoc(CORSer+url);
        }
        function loadXMLDoc(url) {
            xmlhttp = null;
            if (window.XMLHttpRequest) xmlhttp = new XMLHttpRequest(); // code for IE7, Firefox, Mozilla, etc.
            else if (window.ActiveXObject) xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); // code for IE5, IE6
            if (xmlhttp != null) {
                xmlhttp.open("get", url);
                // xmlhttp.setRequestHeader("Access-Control-Expose-Headers","Access-Control-Allow-Origin");
                // xmlhttp.setRequestHeader("Access-Control-Allow-Origin","*");
                xmlhttp.onreadystatechange = function(){
                    onResponse(url);
                };
                xmlhttp.send();
            } else alert("Your browser does not support XMLHTTP.");
        }
        function onResponse(url) {
            if (xmlhttp.readyState != 4 || xmlhttp.status != 200) {
                console.error("Problem retrieving XML data");
                setTimeout(()=>{
                    console.groupCollapsed();
                    xmlhttp.onerror=(error) => { 
                        console.log(error);
                    }
                    xmlhttp.addEventListener('error',(error) => { 
                        console.log(error);
                    })
                    console.groupEnd();
                },512);
                console.warn("maybe because cors.if yes,start down details to get solution");
                console.groupCollapsed();
                console.warn("You browser cannot get the feed because of CORS. Try to run :'cors(document.getElementById(\"direction\").value' to retry.");
                console.warn("If cannot again,please click under first button and upload files that you just got under second button.")
                console.warn("编不下去了...其实想过打开 feed 地址然后从 feed 地址访问 feed 以绕过同源策略,遗憾的是由于同源策略的限制,这件事只好你自己做。打开弹出窗口的DevTools,然后。。。(严肃)复制以下代码并运行以获取 feed 内容,然后复制并保存,最后访问这个新文件!");
                console.warn("当然,也可以将上述想法写成油猴脚本直接运行,但那样的话...为什么不用已经写好的 Allow CORS 插件呢?");
                console.log("var xhr = new XMLHttpRequest();\nxhr.open(\"GET\", window.location.href , true);\n// 如果已指明,responseType 必须是空字符串或 \"document\"\nxhr.responseType = \"document\";\n// overrideMimeType() 用来强制解析 response 为 XML\nxhr.overrideMimeType(\"text/xml\");\n\nxhr.onload = function () {\n  if (xhr.readyState === xhr.DONE) {\n    if (xhr.status === 200) {\n      console.log(xhr.response);\n      console.log(xhr.responseXML);\n    }\n  }\n};\nxhr.send(null);");
                console.groupEnd();
                // window.open(url);
            }else{
                Infor(xmlhttp.responseXML.documentElement.getElementsByTagName("title")[0].innerHTML,xmlhttp.responseXML.documentElement.getElementsByTagName("item"));
            }
        }
        function Infor(channeltitle,items){
            let readals = document.createElement("div");
            readals.id = document.getElementById("direction").value;
            readals_channel_title = document.createElement("h1");
            readals_channel_title.innerText = channeltitle;
            readals.appendChild(readals_channel_title);
            document.getElementById("rss").appendChild(readals);
            for (let i of items) {
                item=document.getElementsByTagName("template")[0].content.querySelector("div");
                readal=document.importNode(item, true);
                let objecter={
                    "{{link}}" : i.getElementsByTagName("link")[0].innerHTML ,
                    "{{title}}" : i.getElementsByTagName("title")[0].innerHTML ,
                    "{{pubdate}}" : i.getElementsByTagName("pubDate")[0].innerHTML ,
                    "{{description}}" : i.getElementsByTagName("description")[0].firstChild.nodeValue
                    /* , "{{enclosure}}" : i.getElementsByTagName("enclosure")[0].getAttribute("url")*/
                };
                for (let j of Object.keys(objecter))readal.innerHTML=readal.innerHTML.replace(new RegExp(j,"g"), objecter[j]);
                readals.appendChild(readal);
                readals.appendChild(document.createElement("br"));
            }
        }
    </script>
    <p>import:<input type="file" name="file" id="filePicker_feed" onchange="xml_upload()" /></p>
    <p><svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 164 75" width="164" height="75" onclick="xml_download()">
        <!-- svg-source:excalidraw -->
        <defs>
            <style class="style-fonts">
                @font-face {
                    font-family: "Virgil";
                    src: url("https://excalidraw.com/Virgil.woff2");
                }

                @font-face {
                    font-family: "Cascadia";
                    src: url("https://excalidraw.com/Cascadia.woff2");
                }
            </style>
        </defs>
        <rect x="0" y="0" width="164" height="75" fill="rgba(0,0,0,0)"></rect>
        <g stroke-linecap="round" transform="translate(10 10) rotate(0 72 27.5)">
            <path d="M1.79 5.53 C1.79 5.53, 1.79 5.53, 1.79 5.53 M1.79 5.53 C1.79 5.53, 1.79 5.53, 1.79 5.53 M1.53 11.93 C3.33 9.87, 5.12 7.8, 9.4 2.87 M1.53 11.93 C3.56 9.59, 5.6 7.25, 9.4 2.87 M1.93 17.57 C5.92 12.98, 9.91 8.39, 15.7 1.73 M1.93 17.57 C4.77 14.31, 7.61 11.04, 15.7 1.73 M1.66 23.97 C6.7 18.18, 11.74 12.38, 20.69 2.09 M1.66 23.97 C7.36 17.41, 13.07 10.86, 20.69 2.09 M1.4 30.37 C9.22 21.38, 17.03 12.39, 26.33 1.69 M1.4 30.37 C8.65 22.03, 15.9 13.7, 26.33 1.69 M1.8 36.01 C8.91 27.83, 16.03 19.64, 31.32 2.05 M1.8 36.01 C8.72 28.05, 15.64 20.08, 31.32 2.05 M1.54 42.41 C9.56 33.18, 17.58 23.95, 36.96 1.66 M1.54 42.41 C12.56 29.73, 23.58 17.05, 36.96 1.66 M3.24 46.54 C12.93 35.4, 22.61 24.26, 41.95 2.02 M3.24 46.54 C16.69 31.08, 30.13 15.61, 41.95 2.02 M3.64 52.19 C13.42 40.93, 23.21 29.68, 47.59 1.62 M3.64 52.19 C14.13 40.12, 24.63 28.04, 47.59 1.62 M7.31 54.06 C16.9 43.02, 26.5 31.99, 52.58 1.98 M7.31 54.06 C19.59 39.93, 31.87 25.81, 52.58 1.98 M10.33 56.68 C28.17 36.16, 46 15.65, 58.22 1.59 M10.33 56.68 C26.77 37.77, 43.21 18.86, 58.22 1.59 M15.97 56.29 C33.57 36.04, 51.17 15.8, 63.21 1.95 M15.97 56.29 C29.55 40.66, 43.13 25.04, 63.21 1.95 M20.96 56.65 C36.43 38.85, 51.9 21.06, 68.85 1.55 M20.96 56.65 C34.98 40.52, 49.01 24.38, 68.85 1.55 M25.95 57.01 C35.76 45.72, 45.58 34.42, 73.84 1.91 M25.95 57.01 C36.77 44.55, 47.6 32.1, 73.84 1.91 M31.59 56.61 C50.65 34.68, 69.72 12.75, 79.48 1.52 M31.59 56.61 C43.75 42.62, 55.91 28.63, 79.48 1.52 M36.58 56.97 C51.36 39.97, 66.13 22.97, 84.47 1.88 M36.58 56.97 C54.06 36.86, 71.55 16.74, 84.47 1.88 M42.22 56.58 C56.48 40.18, 70.74 23.78, 90.11 1.48 M42.22 56.58 C60.98 35, 79.74 13.42, 90.11 1.48 M47.21 56.94 C62.03 39.89, 76.84 22.84, 95.1 1.84 M47.21 56.94 C63.08 38.68, 78.96 20.42, 95.1 1.84 M52.85 56.54 C71.95 34.57, 91.05 12.6, 100.74 1.45 M52.85 56.54 C70.55 36.19, 88.24 15.83, 100.74 1.45 M57.84 56.9 C75.08 37.07, 92.32 17.23, 105.73 1.81 M57.84 56.9 C67.43 45.87, 77.02 34.84, 105.73 1.81 M63.48 56.51 C75.68 42.48, 87.87 28.45, 111.37 1.41 M63.48 56.51 C75.42 42.77, 87.36 29.04, 111.37 1.41 M68.47 56.87 C79.94 43.67, 91.41 30.47, 116.36 1.77 M68.47 56.87 C87.39 35.1, 106.32 13.33, 116.36 1.77 M74.11 56.47 C88.25 40.21, 102.39 23.95, 122 1.38 M74.11 56.47 C92.85 34.91, 111.6 13.35, 122 1.38 M79.1 56.83 C91.25 42.86, 103.4 28.88, 126.99 1.74 M79.1 56.83 C97.69 35.45, 116.28 14.06, 126.99 1.74 M84.74 56.44 C100.18 38.68, 115.62 20.91, 131.98 2.1 M84.74 56.44 C101.49 37.18, 118.23 17.92, 131.98 2.1 M89.73 56.8 C105.76 38.36, 121.78 19.93, 137.62 1.71 M89.73 56.8 C108.87 34.78, 128.02 12.75, 137.62 1.71 M95.37 56.41 C106.06 44.11, 116.75 31.81, 141.3 3.58 M95.37 56.41 C113.55 35.49, 131.73 14.58, 141.3 3.58 M100.36 56.77 C114.91 40.02, 129.47 23.28, 143 7.71 M100.36 56.77 C113.1 42.1, 125.85 27.44, 143 7.71 M106 56.37 C116.55 44.24, 127.09 32.11, 144.71 11.84 M106 56.37 C120.37 39.85, 134.73 23.32, 144.71 11.84 M110.99 56.73 C123.23 42.64, 135.48 28.56, 145.1 17.49 M110.99 56.73 C121.32 44.84, 131.65 32.96, 145.1 17.49 M116.63 56.34 C127.41 43.94, 138.19 31.54, 144.84 23.88 M116.63 56.34 C123.24 48.73, 129.85 41.13, 144.84 23.88 M121.62 56.7 C131.06 45.84, 140.49 34.98, 145.24 29.53 M121.62 56.7 C128.17 49.16, 134.73 41.62, 145.24 29.53 M127.26 56.3 C132.12 50.71, 136.98 45.12, 144.98 35.92 M127.26 56.3 C133.25 49.41, 139.24 42.52, 144.98 35.92 M132.25 56.66 C136.01 52.34, 139.77 48.01, 144.71 42.32 M132.25 56.66 C135.2 53.26, 138.16 49.86, 144.71 42.32" stroke="#a5d8ff" stroke-width="0.5" fill="none"></path>
            <path d="M13.75 0 M13.75 0 C53.18 0, 92.61 0, 130.25 0 M13.75 0 C52.57 0, 91.39 0, 130.25 0 M130.25 0 C139.42 0, 144 4.58, 144 13.75 M130.25 0 C139.42 0, 144 4.58, 144 13.75 M144 13.75 C144 24.18, 144 34.6, 144 41.25 M144 13.75 C144 20.35, 144 26.96, 144 41.25 M144 41.25 C144 50.42, 139.42 55, 130.25 55 M144 41.25 C144 50.42, 139.42 55, 130.25 55 M130.25 55 C89.29 55, 48.34 55, 13.75 55 M130.25 55 C94.55 55, 58.86 55, 13.75 55 M13.75 55 C4.58 55, 0 50.42, 0 41.25 M13.75 55 C4.58 55, 0 50.42, 0 41.25 M0 41.25 C0 33.48, 0 25.72, 0 13.75 M0 41.25 C0 34.07, 0 26.89, 0 13.75 M0 13.75 C0 4.58, 4.58 0, 13.75 0 M0 13.75 C0 4.58, 4.58 0, 13.75 0" stroke="#1971c2" stroke-width="1" fill="none"></path>
        </g>
        <g transform="translate(20.404037475585938 15) rotate(0 61.59596252441406 22.5)"><text x="61.59596252441406" y="0" font-family="Virgil, HanziPen SC, Cangnanshoujiti, KaiTi, Segoe UI Emoji" font-size="36px" fill="#1971c2" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">backup!</text></g>
    </svg></p>
    <p><input class="input" type="text" id="direction" placeholder="要访问的源从此开始!" /></p>
    <p id="feed_feeder"><svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 164 75" width="164" height="75" onclick="loadXMLDoc(document.getElementById('direction').value);">
                <!-- svg-source:excalidraw -->
                <defs>
                    <style class="style-fonts">
                        @font-face {
                            font-family: "Virgil";
                            src: url("https://excalidraw.com/Virgil.woff2");
                        }

                        @font-face {
                            font-family: "Cascadia";
                            src: url("https://excalidraw.com/Cascadia.woff2");
                        }
                    </style>
                </defs>
                <rect x="0" y="0" width="164" height="75" fill="rgba(0,0,0,0)"></rect>
                <g stroke-linecap="round" transform="translate(10 10) rotate(0 72 27.5)">
                    <path d="M1.79 5.53 C1.79 5.53, 1.79 5.53, 1.79 5.53 M1.79 5.53 C1.79 5.53, 1.79 5.53, 1.79 5.53 M1.53 11.93 C3.33 9.87, 5.12 7.8, 9.4 2.87 M1.53 11.93 C3.56 9.59, 5.6 7.25, 9.4 2.87 M1.93 17.57 C5.92 12.98, 9.91 8.39, 15.7 1.73 M1.93 17.57 C4.77 14.31, 7.61 11.04, 15.7 1.73 M1.66 23.97 C6.7 18.18, 11.74 12.38, 20.69 2.09 M1.66 23.97 C7.36 17.41, 13.07 10.86, 20.69 2.09 M1.4 30.37 C9.22 21.38, 17.03 12.39, 26.33 1.69 M1.4 30.37 C8.65 22.03, 15.9 13.7, 26.33 1.69 M1.8 36.01 C8.91 27.83, 16.03 19.64, 31.32 2.05 M1.8 36.01 C8.72 28.05, 15.64 20.08, 31.32 2.05 M1.54 42.41 C9.56 33.18, 17.58 23.95, 36.96 1.66 M1.54 42.41 C12.56 29.73, 23.58 17.05, 36.96 1.66 M3.24 46.54 C12.93 35.4, 22.61 24.26, 41.95 2.02 M3.24 46.54 C16.69 31.08, 30.13 15.61, 41.95 2.02 M3.64 52.19 C13.42 40.93, 23.21 29.68, 47.59 1.62 M3.64 52.19 C14.13 40.12, 24.63 28.04, 47.59 1.62 M7.31 54.06 C16.9 43.02, 26.5 31.99, 52.58 1.98 M7.31 54.06 C19.59 39.93, 31.87 25.81, 52.58 1.98 M10.33 56.68 C28.17 36.16, 46 15.65, 58.22 1.59 M10.33 56.68 C26.77 37.77, 43.21 18.86, 58.22 1.59 M15.97 56.29 C33.57 36.04, 51.17 15.8, 63.21 1.95 M15.97 56.29 C29.55 40.66, 43.13 25.04, 63.21 1.95 M20.96 56.65 C36.43 38.85, 51.9 21.06, 68.85 1.55 M20.96 56.65 C34.98 40.52, 49.01 24.38, 68.85 1.55 M25.95 57.01 C35.76 45.72, 45.58 34.42, 73.84 1.91 M25.95 57.01 C36.77 44.55, 47.6 32.1, 73.84 1.91 M31.59 56.61 C50.65 34.68, 69.72 12.75, 79.48 1.52 M31.59 56.61 C43.75 42.62, 55.91 28.63, 79.48 1.52 M36.58 56.97 C51.36 39.97, 66.13 22.97, 84.47 1.88 M36.58 56.97 C54.06 36.86, 71.55 16.74, 84.47 1.88 M42.22 56.58 C56.48 40.18, 70.74 23.78, 90.11 1.48 M42.22 56.58 C60.98 35, 79.74 13.42, 90.11 1.48 M47.21 56.94 C62.03 39.89, 76.84 22.84, 95.1 1.84 M47.21 56.94 C63.08 38.68, 78.96 20.42, 95.1 1.84 M52.85 56.54 C71.95 34.57, 91.05 12.6, 100.74 1.45 M52.85 56.54 C70.55 36.19, 88.24 15.83, 100.74 1.45 M57.84 56.9 C75.08 37.07, 92.32 17.23, 105.73 1.81 M57.84 56.9 C67.43 45.87, 77.02 34.84, 105.73 1.81 M63.48 56.51 C75.68 42.48, 87.87 28.45, 111.37 1.41 M63.48 56.51 C75.42 42.77, 87.36 29.04, 111.37 1.41 M68.47 56.87 C79.94 43.67, 91.41 30.47, 116.36 1.77 M68.47 56.87 C87.39 35.1, 106.32 13.33, 116.36 1.77 M74.11 56.47 C88.25 40.21, 102.39 23.95, 122 1.38 M74.11 56.47 C92.85 34.91, 111.6 13.35, 122 1.38 M79.1 56.83 C91.25 42.86, 103.4 28.88, 126.99 1.74 M79.1 56.83 C97.69 35.45, 116.28 14.06, 126.99 1.74 M84.74 56.44 C100.18 38.68, 115.62 20.91, 131.98 2.1 M84.74 56.44 C101.49 37.18, 118.23 17.92, 131.98 2.1 M89.73 56.8 C105.76 38.36, 121.78 19.93, 137.62 1.71 M89.73 56.8 C108.87 34.78, 128.02 12.75, 137.62 1.71 M95.37 56.41 C106.06 44.11, 116.75 31.81, 141.3 3.58 M95.37 56.41 C113.55 35.49, 131.73 14.58, 141.3 3.58 M100.36 56.77 C114.91 40.02, 129.47 23.28, 143 7.71 M100.36 56.77 C113.1 42.1, 125.85 27.44, 143 7.71 M106 56.37 C116.55 44.24, 127.09 32.11, 144.71 11.84 M106 56.37 C120.37 39.85, 134.73 23.32, 144.71 11.84 M110.99 56.73 C123.23 42.64, 135.48 28.56, 145.1 17.49 M110.99 56.73 C121.32 44.84, 131.65 32.96, 145.1 17.49 M116.63 56.34 C127.41 43.94, 138.19 31.54, 144.84 23.88 M116.63 56.34 C123.24 48.73, 129.85 41.13, 144.84 23.88 M121.62 56.7 C131.06 45.84, 140.49 34.98, 145.24 29.53 M121.62 56.7 C128.17 49.16, 134.73 41.62, 145.24 29.53 M127.26 56.3 C132.12 50.71, 136.98 45.12, 144.98 35.92 M127.26 56.3 C133.25 49.41, 139.24 42.52, 144.98 35.92 M132.25 56.66 C136.01 52.34, 139.77 48.01, 144.71 42.32 M132.25 56.66 C135.2 53.26, 138.16 49.86, 144.71 42.32" stroke="#a5d8ff" stroke-width="0.5" fill="none"></path>
                    <path d="M13.75 0 M13.75 0 C53.18 0, 92.61 0, 130.25 0 M13.75 0 C52.57 0, 91.39 0, 130.25 0 M130.25 0 C139.42 0, 144 4.58, 144 13.75 M130.25 0 C139.42 0, 144 4.58, 144 13.75 M144 13.75 C144 24.18, 144 34.6, 144 41.25 M144 13.75 C144 20.35, 144 26.96, 144 41.25 M144 41.25 C144 50.42, 139.42 55, 130.25 55 M144 41.25 C144 50.42, 139.42 55, 130.25 55 M130.25 55 C89.29 55, 48.34 55, 13.75 55 M130.25 55 C94.55 55, 58.86 55, 13.75 55 M13.75 55 C4.58 55, 0 50.42, 0 41.25 M13.75 55 C4.58 55, 0 50.42, 0 41.25 M0 41.25 C0 33.48, 0 25.72, 0 13.75 M0 41.25 C0 34.07, 0 26.89, 0 13.75 M0 13.75 C0 4.58, 4.58 0, 13.75 0 M0 13.75 C0 4.58, 4.58 0, 13.75 0" stroke="#1971c2" stroke-width="1" fill="none"></path>
                </g>
                <g transform="translate(20.404037475585938 15) rotate(0 61.59596252441406 22.5)"><text x="61.59596252441406" y="0" font-family="Virgil, HanziPen SC, Cangnanshoujiti, KaiTi, Segoe UI Emoji" font-size="36px" fill="#1971c2" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">feeder!</text></g>
            </svg></p>
    <div id="rss"></div>
</li>

碍于精力所限,还有一些有意思的功能不能全部在这里分享,后续我的计划是开一个连续的写作计划来展示我两年的奋斗历程和带来的启示。而这篇文章本身也写的很仓促,后续也可能全文重写吧。

搭建服务器,拥抱新生活

前面三步与「自」关系甚密,这个 html 文档也初步体现了我的「电子工作台」的一些使用场景,但是显然和「托管」不沾边。另一方面,其实 html 最初就是为网页设计的,要使用的话还是搭配服务器更加原汁原味。

现在,我们就要开始「部署」了。

在 Android 平台上:Termux+Nginx

由于历史原因,当我意识到服务器的重要性时,我的主力设备还是一部手机。所以我想尽办法,在 Android 上搭建起了服务器。

下载 Termux

F-droid 下载地址

Termux Github Releases

配置 nginx

复制以下代码并运行。

pkg update卡住的话多按几次回车 不要傻乎乎的等

——国光

sed -i 's@^\(deb.*stable main\)$@#\1\ndeb https://mirrors.tuna.tsinghua.edu.cn/termux/termux-packages-24 stable main@' $PREFIX/etc/apt/sources.list

sed -i 's@^\(deb.*games stable\)$@#\1\ndeb https://mirrors.tuna.tsinghua.edu.cn/termux/game-packages-24 games stable@' $PREFIX/etc/apt/sources.list.d/game.list

sed -i 's@^\(deb.*science stable\)$@#\1\ndeb https://mirrors.tuna.tsinghua.edu.cn/termux/science-packages-24 science stable@' $PREFIX/etc/apt/sources.list.d/science.list

pkg update

termux-setup-storage

pkg install -y nginx

nginx -t

 

如果配置成功应如图所示:

然后,运行以下代码:

nano $PREFIX/etc/nginx/nginx.conf

在新打开的窗口中找到以下几行:

端口号你自己定,但是要记好,我填 8000;

网站根目录是你的 my.html 所在位置;

修改好后依次按下Ctrl+O、Ctrl+X保存并退出,然后输入nginx启动服务器。

若你的my.html存放位置为/sdcard/download,则在手机上打开“http://127.0.0.1:端口号/download/my.html”(实际环境下我用的是http://0.0.0.0:8000/download/engines.html)查看,若如图所示,即为成功。

如果你想将服务器部署到局域网内,只需将端口号的下一行改为server_name      0.0.0.0;即可。

在 Windows 平台上:Nginx

在 Windows 上流程大差不差。点击 nginx-1.26.1.zip 下载、解压,保存到如图目录。

随后双击 conf 文件夹,编辑nginx.conf。编辑内容同上文 Android 端,不过要记得改文件所在根目录。

如图即为成功

总结

感谢你看到这里,零零散散也写了这么多。文中代码量有些占比过高,还请多多见谅。如果你想找到的我日常使用的文件(index.html)和示例文件(my.html),你可以前往我的 Github 仓库寻找。

这篇文章如有不足,请在评论区补充。如果你需要我的技术帮助,欢迎评论区或者私信。

如果文章对你有用,请充电。

文中大部分代码为长期耕耘所得,图片为自己所截。题图源自 unsplash。

立下flag

以后我打算开一个系列,更详细的介绍我搭建顺手的 html 个人工作台的经验,欢迎关注后续文章!

当然高一的学习压力还是相对大的,下次更新很难说是什么时候。

致谢

上一次写我的 2022:HTML、数字生活和写作方法论,得到了很多人的鼓励,谢谢大家!希望我不负你们的期待,未来能写出更多有技术深度、有人文关怀的文章(当然这篇的初衷其实偏实用向)。