思源笔记折腾记录 - 稍微复杂一点的发布效果

本贴最后更新于 482 天前,其中的信息可能已经时移俗易

一、前情提要:

我们在之前,通过引入 express,实现了一个简单(lou)的发布功能。

思源笔记折腾记录 - 实现简单的笔记发布 - 链滴 (ld246.com)

现在我们来实现一个复杂一点的。

二、为什么要用思源鼓捣网站

如果你随便搜索一下 cms 的话,就会看到大概这样的描述:

cms 指的是内容管理系统,可以用来管理网站的各种文件、数据、乃至其他信息。

额,为什么说到这个呢。

其实是之前我不是自建了一个网站吗,就知道 cms 这个词然后到处搜搜搜,然后最开始用的是 wordpress,之后鼓捣起来实在是有点麻烦。

就又换成了某个现成的建站系统。

然后发现哎呀好像还是不行,我写很多的东西的,他的编辑器虽然功能丰富记界面美观,但是写东西实在是太慢了。

之后又把内容迁移到了我来,开始是用 obsidian 编辑之后,复制到我来发布,后来是用思源编辑之后, 用我来发布。

再后来就发现,哎呀还是不行,我来的收费政策又改了,本来就赚不到钱,也不可能花太多钱在这个上面的,而且主要是发现自己手上没有数据,万一那边政策一个变化,文件不就等于丢了么。。。。。。

之后我在使用服务器上部署的思源的时候,突然想通了一件事情:

你看,我需要能够方便舒适地在各种地方编辑网站内容,思源可以做的对吧。

我需要能够上传下载和管理附件,思源也可以做的对吧。

我需要有一个基本的静态文件服务,思源也可以做的对吧。

我还需要它能够方便地“原样”在我自己的设备上和服务器上预览,欸,巧了,思源刚好开源了,界面可以直接摸,基础的样式框架也不用自己写了。

好耶~~~

额,其实最关键的是,现有的好多建站工具的文本编辑体验实在是有点不习惯,还是用熟悉的编辑器鼓捣起来舒服.....

好了,闲话说完了,我们看一下之前的发布效果:

image

额,不是说这个。

是这个:

image

还记得有个人曾经说过,想要做到所见即所得的发布么......

这个完全不像好吧。

更好看一点的页面模板

还记得思源本身有一个能够按照原样发布 html 的功能吗?

image

嗯,就是这个。

它导出的 html,是这样的:

image

看着还可以哦,我们来看一下是怎么做到的:

image

上面是导出文件的目录,嗯,所以我们只需要挨个导出文件,然后把他们发送给访问者就行了对吧。

额,你如果这样做其实也不是不行,不过我这里要用另外一种办法,毕竟我是想要用它来做一个简单的博客,那文档树、大纲、反向链接、 关系图、导航栏我还是都想要的。

所以我们还是要自己写一写。

获取块 dom

还记得我们之前用的是哪个接口来弄发布内容的吗?

    let 文档内容 = await noobApi.核心api.exportPreview(
        {
            "id": 文档数据.id
        }
    )

这个接口获取的 DOM 实际上对应的是这个界面:

image

额,其实也还挺好,我们复制到知乎什么的也就基本上是这个样子了。

但是说好的原样的吗,我们必须给整一个差不多原样的出来。

这里需要的接口就是这个:

/api/getDoc

这个接口实际上就是获取文档 DOM 的接口。

这里我们先不纠结太多,直接按照这样传一下:

{
id:<块id>
size:102400
mode:0
}

也就是把之前的渲染函数里获取文档内容的部分从这样:

   let 文档内容 = await noobApi.核心api.exportPreview(
        {
            id: 文档数据.id,
        }
    )

改成这样:

    let 文档内容 = await noobApi.核心api.getDoc(
        {
            id: 文档数据.id,
            mode:0,
            size:102400
        }
    )

好,现在刷新一下在浏览器,就可以看到渲染结果了,它长这样:

image

这是为什么呢?

我们可以打开思源的开发者工具,找到这个元素

image

然后删掉它看看(右键里面就有删除),后果美如画:

image

这个时候我们再看看之前导出的 html 里的内容:

image

等于我们其实只拿到了一个没头没尾的 dom,光靠它,没有那些 js 和 css 代码,显然弄不出一个好看的导出页面。

所以又悟了:只要把这些加到渲染结果里面就可以了。

创建一个渲染主题

这个时候就该停下来想想了,难道还像我们之前写菜单的时候那样,在 js 里面硬写这些来搞吗?

这样怕是有点难受。

所以我们另外想一个办法。

来看一下一些其他建站系统的主题:

这个是 wordpress:

WordPress 主题制作教程:主题文件解析-菜鸟笔记 (coonote.com)

index.php //主模板文件,所有主题都需要它
style.css //样式表文件,不可缺少,并包含主题的信息标题
rtl.css //如果网站语言的文本方向是从右到左,则会自动包含从右到左的样式表
comments.php //评论模板。
page.php //访问者请求单个页面时使用页面模板,这些页面是内置模板。
home.php //默认情况下,主页模板是首页。如果您没有将WordPress设置为使用静态首页,则此模板用于显示最新帖子。
header.php //头部模板文件通常包含您网站的文档类型,元信息,样式表和脚本的链接以及其他数据。
footer.php //公共底部模板
sidebar.php //侧边栏模板
single.php中 //日志模板。
archive.php //归档模板,如果分类标签页没有模板的话,会使用这个模板
category.php //分类页模板
tag.php //标签页模板
search.php中 //搜索结果模板用于显示访问者的搜索结果。
404.php //当WordPress无法找到与访问者请求匹配的帖子,页面或其他内容时,将使用404模板
functions.php //增加WordPress功能

这是 hugo

如何创建自己的 hugo 主题 - 简书 (jianshu.com)

所以我们也来搞一个发布主题系统,嘿嘿嘿。

其他的我们先不管,先来看看内容页面,要实现一个内容页面模板,无非就是弄一个模板,然后填内容进去,那么怎么实现内容模板呢?(小编写不出东西来了都这样)

这里我使用了一个非常杀马特的办法,我们要知道,electron 的渲染进程虽然有 node 环境,但是它也有浏览器环境啊,所以我们就不去找各种奇怪的模板引擎了,我们直接这样:

    let 渲染结果 = new DOMParser().parseFromString(docTemplate, "text/html");
    渲染结果.getElementById('content').innerHTML = 文档数据.content 

这个是什么意思呢,就是生成了一个 document 对象,所以在这个的基础上,我们可以使用 js 对这个对象进行各种操作,比如注入文档内容,这样也比较适合我们这种不大会写程序的嘛,不用学更多新的东西了,由于文档都是我们自己写的,就算解析成 html,应该也不会搞出什么奇怪的问题。

然后我们使用它,来实现一个渲染函数, 这里模仿一下 express 对请求的处理,每一步对渲染结果做一点操作,最后就搞出来一个完整的页面了,因为是像管道一样流过这一堆函数,所以我们叫它管线渲染器吧:

//这个就随便导出一个文档就行了
let 默认模板路径 = 代码片段路径 + 'publishTemplate/default/doc.html'

async function 渲染页面内容(req,res,渲染结果){
    let 块id = req.params.blockID
    let 页面数据 =  await 获取数据(块id)
    渲染结果.getElementById('publish-content').innerHTML = 页面数据.content
    console.log(渲染结果)
    return 渲染结果
}
let 默认渲染管线 = 生成管线渲染器([渲染页面内容],默认模板路径)

而这个是生成管线渲染器的实现:


export function 生成管线渲染器(渲染管线, 模板路径) {
    return async (req, res) => {
        //这里是告诉浏览器,我返回的是一个html页面
        res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
        let 渲染结果 = new DOMParser().parseFromString(fs.readFileSync(模板路径), "text/html");
        //这里是一个循环,不断地把渲染结果和请求喂给它们,所以渲染管线中的每一步也都可以跳出去,直接响应请求
        for await (let 渲染函数 of 渲染管线) {
            try {
                //如果渲染结果没有这个函数说明它不是数据了
                if (!渲染结果.querySelector) {
                    let tempdoc = new DOMParser().parseFromString(
                        渲染结果,
                        "text/html"
                    );
                    渲染结果 = tempdoc;

                }
                if (渲染结果.完成) {
                    return 渲染结果;
                }
                if (渲染函数 instanceof Function) {
                    渲染结果 = (await 渲染函数(req, res, 渲染结果)) || "";
                }
                let 文字渲染结果 = "";
                try {
                    文字渲染结果 = 渲染结果.querySelector("body").innerHTML;
                } catch (e) {
                    文字渲染结果 = 渲染结果;
                    let tempdoc = new DOMParser().parseFromString(
                        文字渲染结果,
                        "text/html"
                    );
                    渲染结果 = tempdoc;
                    console.error(e);
                }
            } catch (e) {
                console.error(e);
                continue;
            }
        }
        //如果有结果就返回结果
        try {
            if (渲染结果) {
                res.end(渲染结果.documentElement.innerHTML)
            }
            else {
                res.end('渲染出错,没有有效的结果')
            }
        } catch (e) {
            渲染结果 = null
            console.error(e)
        }
        //万一我们这里对它还有其他操作呢
        return 渲染结果
    }

}  

获取文档内容就不用说了:

async function 获取文档内容(块id) {
    let stmt = `select * from blocks where id in (select root_id from blocks  where id = "${块id}" )`
    let 文档数据 = (await noobApi.核心api.sql({ stmt: stmt }))[0]
    let 文档内容 = await noobApi.核心api.getDoc(
        {
            id: 文档数据.id,
            mode: 0,
            size: 102400
        }
    )
    return 文档内容
}

这样弄完之后,我们再访问一下,发现效果还是这样:

image

那问题出在哪里呢?

  <link rel="stylesheet" type="text/css" id="themeDefaultStyle" href="stage/build/export/base.css?2.5.2" />
  <link rel="stylesheet" type="text/css" id="themeStyle" href="appearance/themes/daylight/theme.css?2.5.2" />

这是导出的文件里的样式表,我们的服务器从根目录就直接跳转到渲染页面了,所以没有给他们返回正确的文件。

所以我们改一下路由:

发布应用.use('/', async (req, res, next) => {
    console.log(req)
    req.url == '/' ? res.redirect('/block/20200812220555-lj3enxa') : null
    next()
})
//把实际的返回弄到block路径下了
发布应用.use('/block/:blockID', 默认渲染管线)

然后想办法提供一下那两个文件。

之前导出的页面文件夹里面其实就有这些文件了,所以我们把它们也放到 publishTemplate/default 里面,然后给它们做下静态文件伺服就可以了:

发布应用.use('/appearance',express.static(代码片段路径 + 'publishTemplate/default/appearance'))
发布应用.use('/stage',express.static(代码片段路径 + 'publishTemplate/default/stage'))

现在再访问一下试试看:

image

bingo,完成。

这里使用的 express.static 方法的作用就是把某个文件夹当成静态文件夹挂在路径下面,所以用了它之后就可以访问到对应的文件了.

虽然还有很多问题,但是基本的内容渲染出来了不是吗?

这次就先这样吧。

  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    18131 引用 • 66888 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...