核心目标

实现一个表格布局,当表格内容宽度超过容器宽度时,在表格容器内显示横向滚动条,而不是让内容撑开页面导致整个页面出现滚动条。

关键技术要点

1. Flex 布局的 min-width: 0 设置

这是实现的最关键部分。当使用 Flexbox 布局时,flex 子元素默认的 min-widthauto,这会阻止元素收缩到比其内容更小的宽度。

<!-- 错误示例:缺少 min-w-0 -->
<div class="flex">
  <div class="w-64 flex-shrink-0">侧边栏</div>
  <div class="flex-1">内容区</div> <!-- 内容会撑开容器 -->
</div>

<!-- 正确示例:添加 min-w-0 -->
<div class="flex">
  <div class="w-64 flex-shrink-0">侧边栏</div>
  <div class="flex-1 min-w-0">内容区</div> <!-- 允许收缩 -->
</div>

2. 容器的 overflow 设置层次

正确的 overflow 层次结构至关重要:

<!-- 表格容器结构 -->
<div class="flex-1 overflow-hidden"> <!-- 外层:防止内容撑开 -->
  <div class="flex-1 overflow-auto"> <!-- 内层:显示滚动条 -->
    <table class="w-full min-w-max"> <!-- 表格:保持内容宽度 -->
      <!-- 表格内容 -->
    </table>
  </div>
</div>

3. 表格的宽度设置

表格需要同时设置 width: 100%min-width: max-content

table {
  width: 100%;           /* 默认占满容器宽度 */
  min-width: max-content; /* 但不能小于内容的自然宽度 */
  table-layout: auto;     /* 自动计算列宽 */
}

4. Sticky 表头的实现

使表头在垂直滚动时保持固定:

<thead class="bg-gray-100 sticky top-0 z-10">
  <tr>
    <th class="whitespace-nowrap">列标题</th>
  </tr>
</thead>

5. 滚动条美化

使用 CSS 美化滚动条样式:

/* Firefox */
.overflow-auto {
  scrollbar-width: thin;
  scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
  scrollbar-gutter: auto; /* 预留滚动条空间,避免内容跳动 */
}

/* Webkit (Chrome, Safari) */
.overflow-auto::-webkit-scrollbar {
  width: 12px;
  height: 12px;
}

.overflow-auto::-webkit-scrollbar-thumb {
  background-color: rgba(156, 163, 175, 0.5);
  border-radius: 6px;
}

完整示例代码结构

<!-- 页面主容器 -->
<div class="h-screen flex overflow-hidden">
  <!-- 侧边栏 -->
  <div class="w-64 flex-shrink-0">
    <!-- 侧边栏内容 -->
  </div>

  <!-- 主内容区 -->
  <div class="flex-1 flex flex-col min-w-0"> <!-- 关键:min-w-0 -->
    <!-- 其他内容(如搜索条) -->
    <div class="flex-shrink-0">...</div>

    <!-- 表格容器 -->
    <div class="flex-1 overflow-hidden flex flex-col">
      <div class="flex-shrink-0">表格标题</div>

      <!-- 滚动容器 -->
      <div class="flex-1 overflow-auto">
        <table class="w-full min-w-max">
          <thead class="sticky top-0">...</thead>
          <tbody>...</tbody>
        </table>
      </div>
    </div>
  </div>
</div>

完整示例预览

线上预览

线上预览

关键要点总结

  1. min-w-0 是灵魂 - 必须在 flex 容器上设置,否则内容会撑开页面
  2. 双层 overflow 结构 - 外层 overflow-hidden 防止撑开,内层 overflow-auto 显示滚动条
  3. 表格设置 min-w-max - 保持表格内容的自然宽度
  4. 合理的 flex 布局 - 使用 flex-1flex-shrink-0 控制伸缩
  5. 使用 scrollbar-gutter: auto - 预留滚动条空间,避免内容跳动

常见问题

Q: 为什么滚动条出现在页面底部而不是表格容器?

A: 通常是因为没有设置 min-w-0,导致 flex 容器无法收缩。

Q: 为什么表格内容被压缩了?

A: 检查是否设置了 min-w-max (或 min-width: max-content)。

Q: 表格在窄屏设备上如何处理?

A: 可以考虑在移动端使用不同的展示方式,如卡片布局或允许横向滚动整个表格。

参考资源

在 Windows 调试一些程序的时候,有可能会遇到程序本身编译为始终以管理员身份运行的。而这些程序在拿到管理员身份的时候会启动一些保护以检测自己是否被调试注入等等。

而我们不希望这些程序以管理员身份启动。

如果是直接启动该程序,我们可以使用命令:

cmd /min /C "set __COMPAT_LAYER=RUNASINVOKER && start "" "(your app path)"

替换 (your app path) 部分为你们的程序路径

如果经常需要使用的话,还可以把该命令集成到 Windows 右键菜单里,通过注册表

RunWithoutPrivilegeElevation.reg

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\*\shell\Run Without Privilege Elevation]
@="Run Without Privilege Elevation"

[HKEY_CLASSES_ROOT\*\shell\Run Without Privilege Elevation\command]
@="cmd /min /C \"set __COMPAT_LAYER=RUNASINVOKER && start \"\" \"%1\"\""

点我下载:RunWithoutPrivilegeElevation.reg

但有时该程序是被另外的程序启动的,这时我们就无法执行命令来启动了。 不过不用担心,Windows 还给了我们另外的方案

为程序设置 Compatibility Flags

DisableAppRunAsAdministrator.reg

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers]
"(your app path)"="RunAsInvoker"

替换 (your app path) 部分为你们的程序路径

设置好后,无论是谁启动的该程序,程序都无法以最高的管理员身份运行了。

仗剑江湖WebMUD客户端

最近在学习AI与一些前端方面的知识,各种前端方向依然属于我不擅长的领域,也是在为后面一些可能的情况存储知识储备。

前段时间忆起只有老网虫才知道的在西陆版的WebMUD游戏 “仗剑江湖” 游玩的岁月。或许只是突然心生怀念,就把早之前2012年写的一个客户端程序(仗剑江湖MUD客户端)拿出来找个服进去逛逛。

意外的有个服里还有不少活人,结果聊的正开心时突然程序崩溃了。原本的程序对付老最古老的江湖版本应该是可以稳定跑的,目前来看可能是遇到了之前开发那东西时没遇到过的状况。后来实际中了解也知道了因为别人江湖添加了很多他自己独有的活动和內容。

现在想修复一下,可原来那时的程序源码早不知道丢哪里去了。

那就干脆重新撸一个,刚好试试目前不擅长的方向可以做到哪些事情。

使用说明

  1. 配置好 地址.txt 为你们游玩的江湖网址,例如我压缩包里内置了 追梦仗剑江湖

    如果需要游玩其他服,只用把这个文件里的地址修改为其他的江湖地址即可

  2. 没有账号的同学记得先去你们的江湖网页注册好账号,我这里就不加注册功能了。别人网站有可能有公告之类的提示信息,还是要去看看。

  3. 启动主程序 仗剑江湖WEBMUD客户端.exe 剩下的应该各位都是老手可以看得懂了

目录说明

  • 脚本 - 是存放脚本的目录,目前内置了另一个辅助工具里内置的所有脚本。
  • 脚本/按钮 - 是存放游戏图形区域下方 快捷按钮 的脚本,所有放在这个目录的脚本都会被加载到按钮里。
  • 脚本/挂机 - 是右下方 循环挂机 功能的脚本,只能进行重复间隔时间自动逐步执行的脚本功能。
  • 脚本/快速行走 - 是右侧 快速行走 功能的脚本,分为两级目录。第一级为分类目录,第二级为快速行走脚本名称,通常使用目标地点或目标人物名称作为文件名以方便搜索。 (记得善用 快速行走 下方的 搜索所有脚本 功能。另外可以在快速行走的脚本列表用 双击 来代替 快速行走 按钮进行执行)
  • 用户 - 是存放多角色信息的目录,会保存用户最后在线以及会话信息用于免登录的快速进入游戏功能。里面还存放着对应角色的系统日志,每次进入游戏后会自动加载历史日志。系统日志不用特意清理,本身会限制只最后5000条日志。
  • fonts - 是放字体的目录,里面有个我认为比较漂亮的字体会被加载到网页。如果你们觉得不漂亮,可以用同样 woff2 格式的字体文件替换成你们喜欢的
  • downloads - 是把游戏里的图片下载到本地缓存的目录,第一次进某个服会图片加载速度会比较慢。但缓存好了以后图片都从本地加载,可以帮巫师他们节省一些不必要的流量开销。如果某个服有更新图片资源,删除这个目录让程序自己重新缓存即可。
  • 窗口信息.json - 用于记录最后一次窗口大小的。每次启动后会按照上次的窗口大小还原,如果不小心窗口变的奇怪,就把这个删掉重新启动程序即可。
  • 其他那些奇怪的文件都是程序打包后运行时所需要的,如果不小心删除了某个文件程序可能会无法正常运行

下载地址

OneDrive:

仗剑江湖WebMUD客户端

本站资源:

仗剑江湖WebMUD客户端

后记

暂时就这些,更多自动化的功能暂时不会加。因为游戏服的大部分人包括我都更喜欢手动慢慢玩,就是体会这种慢节奏像读一遍互动式小说的乐趣。

如果发现问题,请联系邮箱回馈: admin@leelib.com

程序会在我修正完问题后自动发现更新并提示,只需要点击一下按钮刷新过后,不出意外的话使用的就是新版本了。

更新历史

1.0.1

  • 由于一些原因不再使用阿里云CDN,所以更新了一些功能用于适配新的CDN。
  • 更新 Electron 内核到当前最新 v30.0.8。由于目前新的 Electron 版本压缩包超过100MB,不再适合存储在 GitHub 里,所以额外建了资源站来存储当作备选下载线路

1.0.0

  • 完成基本功能

收到别人发来的压缩包,结果解压出来文件名是乱码。 这种情况在别人和自己使用不同的操作系统平台以及编码时经常发生,为了解决这一问题。自己编写了使用指定编码解压文件的程序。

zip_name_fixer.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import zipfile


def main():
    parser = argparse.ArgumentParser(description='ZIP filenames fixer')
    parser.add_argument('path', type=str, help='path to the ZIP file.')
    parser.add_argument('encoding', type=str, help='encoding of the filenames inside the ZIP file.')
    parser.add_argument('-o', '--output', type=str, help='output directory for the extracted files.', default='.')
    parser.add_argument('-p', '--password', type=str, help='decompression password for the ZIP file.', default='')
    args = parser.parse_args()

    with zipfile.ZipFile(args.path, 'r', metadata_encoding=args.encoding) as zip:
        zip.extractall(path=args.output, pwd=args.password.encode('utf-8'))


if __name__ == '__main__':
    main()

点我下载:zip_name_fixer.py

使用方式: python zip_name_fixer.py [-h] [-o OUTPUT] [-p PASSWORD] path encoding

其中 encoding 就是python中常用的编码代码,例如: gbkcp437 等等…

同时顺手还写了一个针对 tar.gz 压缩包的。

tar_name_fixer.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import tarfile


def main():
    parser = argparse.ArgumentParser(description='tar.gz filenames fixer')
    parser.add_argument('path', type=str, help='path to the tar.gz file.')
    parser.add_argument('encoding', type=str, help='encoding of the filenames inside the tar.gz file.')
    parser.add_argument('-o', '--output', type=str, help='output directory for the extracted files.', default='.')
    args = parser.parse_args()

    with tarfile.open(args.path, 'r:gz', encoding=args.encoding) as tar:
        tar.extractall(path=args.output)


if __name__ == '__main__':
    main()

点我下载:tar_name_fixer.py

使用方式: python tar_name_fixer.py [-h] [-o OUTPUT] path encoding

当遇到某人发来的文本內容内不是标准 utf-8 编码时,可以使用本程序来进行转换。

转换时遇到文本行尾使用了Windows专用的 CRLF 换行符时,也会将文本行尾统一更换为 LF。这样的好处是可以缩小文本文件的空间占用,并且可以保证该文件可以在基于Unix和Linux的操作系统以相同的格式显示。

encoding_line_ending_converter.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import os


def main():
    parser = argparse.ArgumentParser(description='Text Encoding and Line Ending Converter')
    parser.add_argument('path', type=str, help='directory to be traversed and processed.')
    parser.add_argument('encoding', type=str, help='encoding of the text file.')
    parser.add_argument('-e', '--extensions', type=str, help='only process specified file extensions. use "|" as the separator, for example: ".txt|.log|.html"', default='')
    parser.add_argument('-q', '--quiet', action='store_true', help='do not output information during processing.')
    parser.add_argument("-v", "--verbose", action="store_true", help="increase output verbosity")
    args = parser.parse_args()
    extensions = tuple(args.extensions.split('|'))

    processed_count = 0
    for root, _, files in os.walk(args.path):
        for file_name in files:
            if len(extensions) == 0 or file_name.endswith(extensions):
                if trans(os.path.join(root, file_name), args.encoding, args.quiet, args.verbose):
                    processed_count += 1

    if not args.quiet:
        print(f'{processed_count} files have been processed.')


def trans(file_path, file_encoding, quiet, verbose) -> bool:
    """Convert file encoding and line endings."""
    # check file encoding
    encoding = 'utf-8'
    content = ''
    try:
        with open(file_path, 'r', encoding=encoding) as f:
            content = f.read()
    except UnicodeDecodeError:
        with open(file_path, 'rb') as f:
            try:
                content = f.read().decode(file_encoding)
                encoding = file_encoding
            except UnicodeDecodeError:
                if not quiet:
                    print(f'{file_path} the file encoding is not {file_encoding}.')
                return False

    # determine if a file uses LF line endings.
    if '\r' in content:
        # convert CRLF line endings to LF.
        content = content.replace('\r\n', '\n')

        # write content
        with open(file_path, 'w', encoding='utf-8', newline='\n') as f:
            f.write(content)

        if not quiet:
            print(f'{file_path} the file encoding and line endings have been converted to UTF-8 and LF.')
        return True
    if encoding != 'utf-8':
        # write content
        with open(file_path, 'w', encoding='utf-8', newline='\n') as f:
            f.write(content)

        if not quiet:
            print(f'{file_path} the file encoding has been converted to UTF-8.')
        return True
    if not quiet and verbose:
        print(f'{file_path} the file is now encoded in UTF-8 with LF line endings.')
    return False


if __name__ == '__main__':
    main()

点我下载:encoding_line_ending_converter.py

> python encoding_line_ending_converter.py -h

usage: encoding_line_ending_converter.py [-h] [-e EXTENSIONS] [-q] [-v] path encoding

Text Encoding and Line Ending Converter

positional arguments:
  path                  directory to be traversed and processed.
  encoding              encoding of the text file.

options:
  -h, --help            show this help message and exit
  -e EXTENSIONS, --extensions EXTENSIONS
                        only process specified file extensions. use "|" as the separator, for example: ".txt|.log|.html"
  -q, --quiet           do not output information during processing.
  -v, --verbose         increase output verbosity

其中 encoding 就是python中常用的编码代码,例如: gbkcp437 等等…

Header Content Footer 布局

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Tailwind CSS Box Layout</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style type="text/tailwindcss">
      html,
      body {
        font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
        color: #c9d1d9;
        background-color: #0d1117;
      }
    </style>
  </head>

  <body>
    <div id="box" class="flex flex-col h-screen justify-between">
      <header class="border-b p-5">Header</header>
      <main class="flex flex-1 flex-col justify-center items-center mb-auto h-auto bg-gray-900">Content</main>
      <footer class="flex justify-center items-center border-t border-gray-800 p-6">Footer</footer>
    </div>
  </body>
</html>

线上预览

预览

准备工作

  • 安装 Scoop
  • 安装 Visual Studio 2022
  • 安装 Ninja v1.11.1
  • 安装 Python v3.10 和 Numpy
  • 安装 wget 和 7zip
  • 安装 Cuda SDK v11.7
  • 一键下载 OpenCV 4.6.0 源码并编译
  • 部署
  • 测试 OpenCV

安装 Scoop

打开 PowerShell 后执行:

Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-WebRequest get.scoop.sh | Invoke-Expression

安装参考: https://scoop.sh

安装 Visual Studio 2022

  1. https://visualstudio.microsoft.com 下载你喜欢的版本,推荐免费的 Community 版本
  2. 安装 使用C++的桌面开发 工作负荷

    使用所选工具(包括 MSVC、CLang、CMake 或 MSBuild)生成适用于 Windows 的现代 C++ 应用

安装 Ninja v1.11.1

scoop install ninja@1.11.1

安装 Python v3.10 和 Numpy

scoop bucket add versions
scoop install python310
pip install numpy

安装 wget 和 7zip

scoop install wget 7zip

安装 Cuda SDK v11.7

安装流程: https://docs.nvidia.com/cuda/cuda-installation-guide-microsoft-windows/index.html#installing-cuda-development-tools

一键下载 opencv 4.6.0 源码并编译

download_and_build_opencv.bat

@ECHO OFF
ECHO -- Downloading OpenCV Source Code --
wget https://github.com/opencv/opencv/archive/refs/tags/4.6.0.zip -O opencv-4.6.0.zip
IF %ERRORLEVEL% NEQ 0 GOTO :error
7z x -y opencv-4.6.0.zip
IF %ERRORLEVEL% NEQ 0 GOTO :error
wget https://github.com/opencv/opencv_contrib/archive/refs/tags/4.6.0.zip -O opencv_contrib-4.6.0.zip
IF %ERRORLEVEL% NEQ 0 GOTO :error
7z x -y opencv_contrib-4.6.0.zip
IF %ERRORLEVEL% NEQ 0 GOTO :error
SET "USERHOME=%USERPROFILE:\=/%"
SET OPENCV_SRC_DIR=%cd%/opencv-4.6.0
SET OPENCV_CONTRIB_SRC_DIR=%cd%/opencv_contrib-4.6.0
SET OPENCV_BUILD_DIR=%cd%/opencv-build
SET SCOOP_ROOT=%USERHOME%/scoop
SET PYTHON_PATH=%SCOOP_ROOT%/apps/python310/current
SET PYTHON_LIBRARY=%PYTHON_PATH%/libs/python310.lib
SET PYTHON_VERSION=3.10
SET CUDA_SDK_DIR="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.7"
@REM CUDA_ARCH=8.6 for GeForce RTX 3080 Ti
@REM Choice value for your GPU with: https://developer.nvidia.com/cuda-gpus
SET CUDA_ARCH=8.6
ECHO -- Starting OpenCV Configuration --
call "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/vcvars64.bat"
@REM If you want to use the Visual Studio 2019 MSVC:
@REM call "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/vcvars64.bat" -vcvars_ver=14.29
cmake ^
-S%OPENCV_SRC_DIR% ^
-B%OPENCV_BUILD_DIR% ^
-DCMAKE_INSTALL_PREFIX=%OPENCV_BUILD_DIR%/install ^
-DOPENCV_EXTRA_MODULES_PATH=%OPENCV_CONTRIB_SRC_DIR%/modules ^
-DPYTHON3_EXECUTABLE=%PYTHON_PATH%/python.exe ^
-DPYTHON3_INCLUDE_DIR=%PYTHON_PATH%/include ^
-DPYTHON3_LIBRARY=%PYTHON_LIBRARY% ^
-DPYTHON3_NUMPY_INCLUDE_DIRS=%PYTHON_PATH%/Lib/site-packages/numpy/core/include ^
-DPYTHON3_PACKAGES_PATH=%PYTHON_PATH%/Lib/site-packages ^
-DPYTHON_INCLUDE_DIRS=%PYTHON_PATH%/include ^
-DPYTHON_LIBRARIES=%PYTHON_LIBRARY% ^
-DCUDA_TOOLKIT_ROOT_DIR=%CUDA_SDK_DIR% ^
-DENABLE_FAST_MATH=ON ^
-DCUDA_FAST_MATH=ON ^
-DWITH_CUDA=ON ^
-DWITH_CUDNN=ON ^
-DOPENCV_DNN_CUDA=ON ^
-DCUDA_ARCH_BIN=%CUDA_ARCH% ^
-DCUDA_ARCH_PTX=%CUDA_ARCH% ^
-DWITH_OPENGL=ON ^
-DWITH_GSTREAMER=ON ^
-DWITH_FREETYPE=ON ^
-DBUILD_opencv_python3=ON ^
-DCMAKE_CONFIGURATIONS_TYPES=Release ^
-DCMAKE_BUILD_TYPE=Release ^
-DOPENCV_PYTHON3_VERSION=%PYTHON_VERSION% ^
-DBUILD_TESTS=OFF ^
-DBUILD_PERF_TESTS=OFF ^
-DBUILD_JAVA=OFF ^
-DBUILD_opencv_objc_bindings_generator=OFF ^
-DBUILD_opencv_js=OFF ^
-GNinja ^
-DBUILD_opencv_world=ON
IF %ERRORLEVEL% NEQ 0 GOTO :error
ECHO -- OpenCV Configuration has finished, press any key to proceeding to build phase... --
PAUSE > NUL
cmake --build %OPENCV_BUILD_DIR% --target install
IF %ERRORLEVEL% NEQ 0 GOTO :error
ECHO -- OpenCV Build has finished, press any key to install... --
PAUSE > NUL
pip uninstall opencv-python opencv-contrib-python
IF %ERRORLEVEL% NEQ 0 GOTO :error
COPY %OPENCV_BUILD_DIR%/lib/python3/cv2.cp310-win_amd64.pyd %PYTHON_PATH%/Lib/site-packages/cv2.cp310-win_amd64.pyd /Y
IF %ERRORLEVEL% NEQ 0 GOTO :error
COPY %OPENCV_BUILD_DIR%/install/x64/vc17/bin/*.dll %PYTHON_PATH%/Scripts /Y
IF %ERRORLEVEL% NEQ 0 GOTO :error
pip install opencv-contrib-python
IF %ERRORLEVEL% NEQ 0 GOTO :error
ECHO -- all finished, press any key to exit... --
PAUSE > NUL
GOTO :EOF

:error
ECHO -- Error has occurred!!! press any key to exit... --
PAUSE > NUL

点我下载:download_and_build_opencv.bat

部署

  1. 卸载 opencv-python opencv-contrib-python
  2. 拷贝 %OPENCV_BUILD_DIR%/lib/python3/cv2.cp310-win_amd64.pyd%PYTHON_PATH%/Lib/site-packages 目录
  3. 拷贝 %OPENCV_BUILD_DIR%/install/x64/vc17/bin/*.dll 到 Windows 的 %PATH% 目录 或者 %PYTHON_PATH%/Scripts 目录
  4. 重新安装 opencv-contrib-python
pip uninstall opencv-python opencv-contrib-python
COPY %OPENCV_BUILD_DIR%/lib/python3/cv2.cp310-win_amd64.pyd %PYTHON_PATH%/Lib/site-packages/cv2.cp310-win_amd64.pyd /Y
COPY %OPENCV_BUILD_DIR%/install/x64/vc17/bin/*.dll %PYTHON_PATH%/Scripts /Y
pip install opencv-contrib-python

测试 OpenCV

python -c "import cv2; print(cv2.__version__); print(cv2.cuda.getCudaEnabledDeviceCount())"

更换原因

Hexo 作为博客引擎长期以来使用还是非常方便的。

但由于其本身是基于nodejsnpm,其中使用到的各种npm库的嵌套依赖关系十分杂乱。有可能因為某个子包出BUG而导致引用了该包的所有库均出现各种问题。

已经受够了长期以来明明没有更新文章,但本地却需要保持各种npm包为较新的版本所花费的额外精力。

所以干脆直接更换为基本没有三方依赖的 Hugo,并自己重写了自己使用的主题。

如果有Hugo用户恰好喜欢这款主题,欢迎使用: Fika theme for Hugo

Hexo文章 转 Hugo文章 的脚本

hexo_to_hugo.py

# pip install yaml
# usage: python hexo_to_hugo.py
import os
import re
import yaml

# fill default date with this GMT zone
GMT = '+08:00' # for SG time zone

HEADER_FLAG = '---\n'

def file_lines(fname):
    f = open(fname, "r", encoding='utf-8')
    ret = f.readlines()
    f.close()
    return ret


def parser_head_lines(lines: list[str]) -> list[str]:
    header_lines = list[str]([])
    in_header = False
    for i, line in enumerate(lines):
        if line == HEADER_FLAG:
            if not in_header:
                in_header = True
                continue
            else:  # header end flag founded
                break
        if in_header:
            header_lines.append(line)
    return header_lines


def revise_header_flag(lines):
    revised = False
    if len(lines) == 0:
        return lines, revised
    if lines[0] == HEADER_FLAG:
        return lines, revised
    lines.insert(0, HEADER_FLAG)
    revised = True
    return lines, revised


def revise_header_content(header_lines: list[str]):
    revised = False
    for i, line in enumerate(header_lines):
        # date line
        rdate = re.search(r'date:\s+(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})', line, re.M)
        if rdate:  # date line for need revise to TZ format
            header_lines[i] = f'date: {rdate.group(1)}-{rdate.group(2)}-{rdate.group(3)}T{rdate.group(4)}:{rdate.group(5)}:{rdate.group(6)}{GMT}\n'
            revised = True

    # revise for tags and categories to type list
    header_revised = False
    header = yaml.safe_load(''.join(header_lines))
    if 'categories' in header: # make sure categories are type list
        categories = header['categories']
        if not isinstance(categories, list):
            if categories == None:
                header['categories'] = []
            else:
                header['categories'] = [categories]
            header_revised = True

    if 'tags' in header: # make sure tags are type list
        tags = header['tags']
        if not isinstance(tags, list):
            if tags == None:
                header['tags'] = []
            else:
                header['tags'] = [tags]
            header_revised = True

    if header_revised:
        header_lines = yaml.dump(header, default_flow_style=False, allow_unicode=True).splitlines(True)
        revised = True
    return header_lines, revised


def save_file(fname, lines):
    f = open(fname, 'w', encoding='utf-8', newline='\n')
    f.writelines(lines)
    f.close()


def main():
    HEADER_FLAG = '---\n'
    print('--- starting revise for posts... ---')
    POST_PATH = os.path.join('content', 'posts')
    posts_count = 0
    header_flag_revised_count = 0
    header_revised_count = 0
    files = os.listdir(POST_PATH)
    for fname in files:
        fullpath = os.path.join(POST_PATH, fname)
        if not os.path.isfile(fullpath):
            continue
        lines = file_lines(fullpath)
        lines, header_flag_revised = revise_header_flag(lines)
        if header_flag_revised:
            print(f'revised header flag: {fname}')
            header_flag_revised_count += 1
        header_lines = parser_head_lines(lines)
        body_lines = lines[len(header_lines)+2:]  # skip two header flag lines
        header_lines, header_revised = revise_header_content(header_lines)
        if header_revised:
            print(f'revised date: {fname}')
            header_revised_count += 1
        if header_flag_revised or header_revised:
            print(f'save revised file: {fname}')
            header_lines.insert(0, HEADER_FLAG)
            header_lines.append(HEADER_FLAG)
            save_file(fullpath, header_lines + body_lines)
        posts_count += 1
    print(f'--- revise over. ---\nposts: {posts_count}\nheader flag revised: {header_flag_revised_count}\nheader revised: {header_revised_count}')


if __name__ == "__main__":
    main()

使用方法: 进入 Hugo 站点根目录运行

python hexo_to_hugo.py

脚本下载: hexo_to_hugo.py

内容部分占满页面的剩余高度

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Dock.Full</title>
  <style type="text/css">
    html,
    body,
    #full {
      color: #EFEFEF;
      background-color: #423F3E;
      margin: 0;
      padding: 0;
      height: 100%;
    }

    #full {
      background-color: #171010;
      display: flex;
      flex-direction: column;
    }

    #someid {
      background-color: #362222;
      flex-grow: 1;
    }
  </style>
</head>

<body>
  <div id="full">
    <div id="header">Dock.Top</div>
    <div id="someid">Dock.Full</div>
  </div>
</body>

</html>

线上预览

登录框居中显示

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style type="text/css">
    html,
    body,
    #parent {
      color: #EFEFEF;
      background-color: #423F3E;
      margin: 0;
      padding: 0;
      height: 100%;
    }

    #parent {
      background-color: #171010;
      /* 使用Flex布局 */
      display: flex;
      /* 主轴位于中间 */
      justify-content: center;
      /* 交叉轴位于中间 */
      align-items: center;
    }

    #someid {
      background-color: #362222;
      width: 200px;
      height: 200px;
    }
  </style>
</head>

<body>
  <div id="parent">
    <div id="someid">Dialog Content</div>
  </div>
</body>

</html>

线上预览