diff --git a/docs/README.md b/docs/README.md index 7611c66..8d76a22 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -# 为什么喜欢使用 Glidea +# 为什么喜欢使用 Glidea :id=like
@@ -55,7 +55,7 @@

-# 主题 +# 主题 :id=theme

@@ -73,7 +73,7 @@

-# 客户端下载 +# 客户端下载 :id=install [install](./commont/install.md ':include') @@ -82,7 +82,7 @@ ______ -### 其它 +### 其它 :id=other [源码地址](https://github.com/wonder-light/glidea) diff --git a/docs/en-us/README.md b/docs/en-us/README.md index 16b1237..0995baf 100644 --- a/docs/en-us/README.md +++ b/docs/en-us/README.md @@ -1,4 +1,4 @@ -# Why do you like Glidea +# Why do you like Glidea :id=like
@@ -55,7 +55,7 @@

-# Themes +# Themes :id=theme
@@ -74,7 +74,7 @@

-# Client download +# Client download :id=install [install](../commont/install.md ':include') @@ -82,7 +82,7 @@ Current version: v1.0.0 [✨ What's new?](https://github.com/wonder-light/glidea ______ -### Other +### Other :id=other [Source address](https://github.com/wonder-light/glidea) diff --git a/docs/en-us/docs/_sidebar.md b/docs/en-us/docs/_sidebar.md index 173319e..2afce90 100644 --- a/docs/en-us/docs/_sidebar.md +++ b/docs/en-us/docs/_sidebar.md @@ -9,6 +9,7 @@ * [Variable](en-us/docs/theme/var.md) * [Custom](en-us/docs/theme/custom.md) * [Share](en-us/docs/theme/shared.md) + * [Render](en-us/docs/theme/render.md) * Guide * [Install](en-us/docs/guide/install.md) * [FAQ](en-us/docs/guide/faq.md) diff --git a/docs/en-us/docs/changelog.md b/docs/en-us/docs/changelog.md index 238e274..d60ff1f 100644 --- a/docs/en-us/docs/changelog.md +++ b/docs/en-us/docs/changelog.md @@ -1,4 +1,4 @@ -# Change Log +# Change Log :id=changelog [filename](https://raw.githubusercontent.com/wonder-light/glidea/refs/heads/main/CHANGELOG-en.md ':include') \ No newline at end of file diff --git a/docs/en-us/docs/guide/faq.md b/docs/en-us/docs/guide/faq.md index b0730cc..227523e 100644 --- a/docs/en-us/docs/guide/faq.md +++ b/docs/en-us/docs/guide/faq.md @@ -1,3 +1,3 @@ -# FAQ +# FAQ :id=faq ## 1 \ No newline at end of file diff --git a/docs/en-us/docs/guide/install.md b/docs/en-us/docs/guide/install.md index d8ad939..f8c5597 100644 --- a/docs/en-us/docs/guide/install.md +++ b/docs/en-us/docs/guide/install.md @@ -1,4 +1,4 @@ -# Installing the client +# Installing the client :id=install [install](../../../commont/install.md ':include') diff --git a/docs/en-us/docs/theme/custom.md b/docs/en-us/docs/theme/custom.md index 22f04b2..dd9bc9f 100644 --- a/docs/en-us/docs/theme/custom.md +++ b/docs/en-us/docs/theme/custom.md @@ -1,5 +1,5 @@ -# Theme customization +# Theme customization :id=theme-custom > Glidea provides powerful theme customization capabilities, you can design your own custom configuration to provide theme users @@ -8,7 +8,7 @@ Each theme is optionally paired with a `config.json` configuration file and a `s ## Example :id=example -### config.json +### config.json :id=config ```json { @@ -102,7 +102,7 @@ Each theme is optionally paired with a `config.json` configuration file and a `s } ``` -### style-override.dart +### style-override.dart :id=style-override ```django {# Dark skin #} @@ -208,6 +208,7 @@ the format for each element is as follows: } ``` + ## Array type configuration :id=array-config ```json @@ -266,10 +267,11 @@ the format for each element is as follows: In most cases, using the input type is sufficient -These fields can be used either in the template (corresponding to: [`site.customconfig.customField`](#configjson)) or in the style overlay file (corresponding to: input) +These fields can be used either in the template (corresponding to: [`site.customconfig.customField`](#config)) or in the style overlay file (corresponding to: input) When used in the template, you can play your imagination, social, statistics, friend chain, external chain background map, background music... + ## Style overlay configuration :id=style-override Of course, it can also be used in style overlay files: diff --git a/docs/en-us/docs/theme/dev.md b/docs/en-us/docs/theme/dev.md index a030ba5..ec15073 100644 --- a/docs/en-us/docs/theme/dev.md +++ b/docs/en-us/docs/theme/dev.md @@ -115,4 +115,9 @@ Macros can then be called like functions: ``` django

{{ input('username') }}

{{ input('password', type='password') }}

Due to the use of commands to start the program, there are cases that some devices do not support\ +Please explain how to use good friends! + +## Set custom :id=custom + +Add the `process` field to `config.json` and use the node.js example + +```json +// /config.json +{ + "name": "fog", + "version": "1.0", + "author": "wonder-light", + "repository": "https://github.com/wonder-light/glidea-theme-fog", + "process": "node ./index.js", + "customConfig": {} +} +``` + +### Argument :id=params + +The output path and the path to render data are injected \ +at the end of the command before starting the program, and also in 'env' + +```shell +node ./index.js +#=> +node ./index.js C:/.../output C:/.../render/config.json +# There is a path.json directory in the config.json sibling directory to refer to the path writing +``` + +### Environment :id=env + +```js +console.log(process.argv[process.argv.length - 2]); +// C:/.../output + +console.log(process.argv[process.argv.length - 1]); +// C:/.../render/config.json + +console.log(process.env['buildDir']); +// C:/.../output + +console.log(process.env['renderData']); +// C:/.../render/config.json + +console.log(process.env['renderPath']); +// C:/.../render/paths.json +``` + diff --git a/docs/en-us/docs/theme/shared.md b/docs/en-us/docs/theme/shared.md index e9b1808..38c6290 100644 --- a/docs/en-us/docs/theme/shared.md +++ b/docs/en-us/docs/theme/shared.md @@ -1,4 +1,4 @@ -# Share Theme +# Share Theme :id=share > Share your theme with your friends on GitHub diff --git a/docs/en-us/themes/README.md b/docs/en-us/themes/README.md index 64851c3..cbdcdcb 100644 --- a/docs/en-us/themes/README.md +++ b/docs/en-us/themes/README.md @@ -1,4 +1,4 @@ -# Theme +# Theme :id=theme
diff --git a/docs/zh-cn/docs/_sidebar.md b/docs/zh-cn/docs/_sidebar.md index e69989e..9b62b1d 100644 --- a/docs/zh-cn/docs/_sidebar.md +++ b/docs/zh-cn/docs/_sidebar.md @@ -9,6 +9,7 @@ * [变量](zh-cn/docs/theme/var.md) * [自定义](zh-cn/docs/theme/custom.md) * [分享](zh-cn/docs/theme/shared.md) + * [渲染](zh-cn/docs/theme/render.md) * 指南 * [安装](zh-cn/docs/guide/install.md) * [常见问题](zh-cn/docs/guide/faq.md) diff --git a/docs/zh-cn/docs/changelog.md b/docs/zh-cn/docs/changelog.md index c4aa911..03a7af9 100644 --- a/docs/zh-cn/docs/changelog.md +++ b/docs/zh-cn/docs/changelog.md @@ -1,4 +1,4 @@ -# 更改日志 +# 更改日志 :id=changelog [filename](https://raw.githubusercontent.com/wonder-light/glidea/refs/heads/main/CHANGELOG.md ':include') \ No newline at end of file diff --git a/docs/zh-cn/docs/guide/ability.md b/docs/zh-cn/docs/guide/ability.md index af26007..c885b47 100644 --- a/docs/zh-cn/docs/guide/ability.md +++ b/docs/zh-cn/docs/guide/ability.md @@ -1,5 +1,5 @@ -# 功能 +# 功能 :id=ability
diff --git a/docs/zh-cn/docs/guide/faq.md b/docs/zh-cn/docs/guide/faq.md index 9bf046a..571b1ff 100644 --- a/docs/zh-cn/docs/guide/faq.md +++ b/docs/zh-cn/docs/guide/faq.md @@ -1,3 +1,3 @@ -# 常见问题 +# 常见问题 :id=faq ## 1 \ No newline at end of file diff --git a/docs/zh-cn/docs/theme/custom.md b/docs/zh-cn/docs/theme/custom.md index fdc3d68..0c1ca38 100644 --- a/docs/zh-cn/docs/theme/custom.md +++ b/docs/zh-cn/docs/theme/custom.md @@ -1,5 +1,5 @@ -# 主题自定义 +# 主题自定义 :id=theme-custom > Glidea 提供了强大的主题自定义能力,你可以自行设计自定义配置提供给主题使用者 @@ -8,7 +8,7 @@ ## 示例 :id=example -### config.json +### config.json :id=config ```json { @@ -102,7 +102,7 @@ } ``` -### style-override.j2 +### style-override.j2 :id=style-override ```django {# 暗黑皮肤 #} @@ -208,6 +208,7 @@ } ``` + ## 数组类型配置 :id=array-config ```json @@ -266,7 +267,7 @@ 大部分情况下,使用 input 类型的就够用了 -这些字段都可以在模版中(对应: [`site.customConfig.自定义字段`](#configjson))或样式覆盖文件(对应:入参)中使用 +这些字段都可以在模版中(对应: [`site.customConfig.自定义字段`](#config))或样式覆盖文件(对应:入参)中使用 在模版中使用时,你可以尽情发挥你的想象,社交、统计、友链、外链背景图、背景音乐... diff --git a/docs/zh-cn/docs/theme/dev.md b/docs/zh-cn/docs/theme/dev.md index 48d2bba..19a9209 100644 --- a/docs/zh-cn/docs/theme/dev.md +++ b/docs/zh-cn/docs/theme/dev.md @@ -112,4 +112,8 @@ ``` django

{{ input('username') }}

{{ input('password', type='password') }}

由于使用的是由命令来启动程序, 所以存在着部分设备不怎么支持的情况,\ +请各位小伙伴说明好使用办法哦! + +## 设置自定义 :id=custom + +在 `config.json` 中添加 `process` 字段即可, 接下来以 node.js 示例 + +```json +// <主题目录>/config.json +{ + "name": "fog", + "version": "1.0", + "author": "wonder-light", + "repository": "https://github.com/wonder-light/glidea-theme-fog", + "process": "node ./index.js", + "customConfig": {} +} +``` + +### 参数 :id=params + +在启动程序前会在命令的后面注入输出路径和渲染数据所在路径, 也会在 `env` 中注入 + +```shell +node ./index.js +#=> +node ./index.js C:/.../output C:/.../render/config.json +# 在 config.json 同级目录下有一个 paths.json 可以参考路径的写法 +``` + +### 环境 :id=env + +```js +console.log(process.argv[process.argv.length - 2]); +// C:/.../output + +console.log(process.argv[process.argv.length - 1]); +// C:/.../render/config.json + +console.log(process.env['buildDir']); +// C:/.../output + +console.log(process.env['renderData']); +// C:/.../render/config.json + +console.log(process.env['renderPath']); +// C:/.../render/paths.json +``` + diff --git a/docs/zh-cn/docs/theme/shared.md b/docs/zh-cn/docs/theme/shared.md index 041484e..d70915c 100644 --- a/docs/zh-cn/docs/theme/shared.md +++ b/docs/zh-cn/docs/theme/shared.md @@ -1,4 +1,4 @@ -# 分享主题 +# 分享主题 :id=share > 将小伙伴你的主题分享到 Github 吧 diff --git a/docs/zh-cn/themes/README.md b/docs/zh-cn/themes/README.md index 6fb6933..aa9b120 100644 --- a/docs/zh-cn/themes/README.md +++ b/docs/zh-cn/themes/README.md @@ -1,4 +1,4 @@ -# 主题 +# 主题 :id=theme
diff --git a/lib/helpers/render/render.dart b/lib/helpers/render/render.dart index 59f3799..b541ce9 100644 --- a/lib/helpers/render/render.dart +++ b/lib/helpers/render/render.dart @@ -1,4 +1,4 @@ -import 'dart:io' show FileMode; +import 'dart:io' show Directory, FileMode; import 'dart:math' show min; import 'package:glidea/helpers/constants.dart'; @@ -16,7 +16,8 @@ import 'package:glidea/models/post.dart'; import 'package:glidea/models/tag.dart'; import 'package:glidea/models/theme.dart'; import 'package:jinja/jinja.dart' show Environment; -import 'package:jinja/loaders.dart'; +import 'package:jinja/loaders.dart' show FileSystemLoader; +import 'package:process_runner/process_runner.dart' show ProcessRunner; typedef _TChangeData = TChangeCallback; @@ -30,16 +31,12 @@ final class RemoteRender { /// 主题路径 late final String themePath = FS.join(site.appDir, 'themes', site.themeConfig.selectTheme); + /// 是否执行自定义模板解析 + bool isCustom = false; + /// jinja 执行环境 late Environment env; - /// 匹配 feature 本地图片路径的正则 - /// - /// 'file://.*/post-images/' => '/post-images/' - /// - /// (?<=匹配开头).*(?=匹配结尾) - late final RegExp featureReg = RegExp(featurePrefix + r'.*(?=/post-images)'); - /// 渲染 post 的数据 final List postsData = []; @@ -53,8 +50,18 @@ final class RemoteRender { /// 菜单数据 List menusData = []; - /// 站点地图 - late Sitemap sitemap; + /// 自定义模板 + Set customTemplates = {}; + + /// 站点 URL 记录 + /// + /// index => '', 'page/2/' + /// archives => 'archives/', 'archives/page/2/' + /// tags => 'tags/' + /// post => 'post/{fileName}/' + /// tag => 'tag/{slug}/', 'tag/{slug}/page/2/' + /// custom => 404, start/ + TMapList siteUrls = {}; String get domain => site.themeConfig.domain; @@ -182,21 +189,27 @@ final class RemoteRender { /// 构建模板 Future buildTemplate() async { - sitemap = Sitemap(); - env = Environment( - globals: { - 'site': { - 'posts': postsData, - 'tags': tagsData, - 'menus': menusData, - 'themeConfig': themeConfig, - 'customConfig': customConfig, - 'utils': { - 'now': DateTime.now().millisecondsSinceEpoch, - }, - 'isHomepage': false, + final siteData = { + 'site': { + 'posts': postsData, + 'tags': tagsData, + 'menus': menusData, + 'themeConfig': themeConfig, + 'customConfig': customConfig, + 'commentSetting': site.comment, + 'utils': { + 'now': DateTime.now().millisecondsSinceEpoch, }, + 'isHomepage': false, }, + }; + await _recordSiteUrl(); + if (await customBuildTemplate(siteData)) { + return; + } + + env = Environment( + globals: siteData, autoReload: true, loader: FileSystemLoader( paths: ['$themePath/templates'], @@ -216,6 +229,38 @@ final class RemoteRender { await buildSiteMap(); } + /// 自定义构建模板 + Future customBuildTemplate(TJsonMap data) async { + final processFilePath = FS.join(themePath, 'config.json'); + final renderDataPath = FS.join(site.supportDir, 'render', 'config.json'); + final renderPathData = FS.join(site.supportDir, 'render', 'paths.json'); + // 检测 config.json 是否存在 + if (!FS.fileExistsSync(processFilePath)) return false; + // 获取 process 字段 + final config = FS.readStringSync(processFilePath).deserialize(); + // 获取命令行 + final exec = (config?['process'] as String?)?.split(RegExp(r'\s*')) ?? []; + if (exec.isEmpty) return false; + // 写入渲染数据 + FS.writeStringSync(renderDataPath, data.toJson()); + // 模板的输出路径数据 + final pathData = { + for (var MapEntry(:key, :value) in siteUrls.entries) + key: [ + for (var url in value) FS.join(site.buildDir, url == '404' ? '404.html' : url, 'index.html'), + ], + }; + FS.writeStringSync(renderPathData, pathData.toJson()); + // 加入构建目录 + exec.add(site.buildDir); + // 加入渲染数据的路径 + exec.add(renderDataPath); + // 执行程序 + final process = ProcessRunner(environment: {'buildDir': site.buildDir, 'renderData': renderDataPath, 'renderPath': renderPathData}); + final result = await process.runProcess(exec, workingDirectory: Directory(themePath)); + return true; + } + /// 构建 css Future buildCss() async { final cssPath = FS.join(themePath, 'style-override.j2'); @@ -248,7 +293,6 @@ final class RemoteRender { 'themeConfig': themeConfig, }); FS.writeStringSync(renderPath, html); - addSiteUrl('tags', '/'); } /// 呈现文章详细信息页面,包括隐藏的文章 @@ -264,7 +308,6 @@ final class RemoteRender { }); FS.createDirSync(renderFolderPath); FS.writeStringSync(FS.join(renderFolderPath, 'index.html'), html); - addSiteUrl(themeConfig.postPath, post.fileName, '/'); } for (var i = 0, length = showPosts.length; i < length; i++) { @@ -306,27 +349,19 @@ final class RemoteRender { /// 渲染自定义页面 Future buildCustomPage() async { - final customTemplates = env.listTemplates().toSet().difference({ - homeTemplate, postTemplate, archivesTemplate, tagsTemplate, tagTemplate, - // 👇 Glidea 保护字,因为这些文件名是 Glidea 文件夹的名称 - 'images.j2', 'media.j2', 'post-images.j2', 'styles.j2', - }); - for (var template in customTemplates) { + for (var name in customTemplates) { String renderPath; - // 404 页面在根目录下创建 - final name = FS.basename(template); if (name == '404') { renderPath = FS.join(site.buildDir, '404.html'); } else { renderPath = FS.join(site.buildDir, name, 'index.html'); } - final html = env.getTemplate(template).render({ + final html = env.getTemplate('$name.j2').render({ 'menus': menusData, 'themeConfig': themeConfig, 'commentSetting': site.comment, }); FS.writeStringSync(renderPath, html); - addSiteUrl(themeConfig.postPath, name, name == '404' ? '' : '/'); } } @@ -336,6 +371,15 @@ final class RemoteRender { var str = '${themeConfig.robotsText}\nSitemap: $domain/sitemap.xml'; FS.writeStringSync(FS.join(site.buildDir, 'robots.txt'), str); // 创建一个站点地图 + final sitemap = Sitemap(); + + /// 添加站点地图的 URl + for (var lists in siteUrls.values) { + for (var url in lists) { + sitemap.add(SitemapEntry(location: FS.join(domain, url))); + } + } + FS.writeStringSync(FS.join(site.buildDir, 'sitemap.xml'), sitemap.generate()); if (themeConfig.useFeed) { str = Feed( @@ -351,6 +395,45 @@ final class RemoteRender { } } + /// 记录站点 URL + Future _recordSiteUrl() async { + final postPagesize = (showPosts.length / themeConfig.postPageSize).ceil(); + final archivePagesize = (showPosts.length / themeConfig.archivesPageSize).ceil(); + List setAt(String base, int size, [bool isUpdate = false, bool skipEmpty = false]) { + return [ + base, + for (var i = 2; i <= postPagesize; i++) FS.join(base, 'page', '$i', '/'), + ]; + } + + var name = FS.basename(homeTemplate); + siteUrls[name] = setAt('', postPagesize); + name = FS.basename(archivesTemplate); + siteUrls[name] = setAt(FS.join(themeConfig.archivePath, '/'), archivePagesize); + name = FS.basename(tagsTemplate); + siteUrls[name] = setAt('tags/', 1); + name = FS.basename(postTemplate); + siteUrls[name] = [...postsData.map((p) => FS.join(themeConfig.postPath, p.fileName, '/'))]; + name = FS.basename(tagTemplate); + final tagUrls = siteUrls[name] = []; + for (var tag in tagsData) { + tagUrls.addAll(setAt(FS.join(themeConfig.tagPath, tag.slug, '/'), (tag.count / themeConfig.postPageSize).ceil())); + } + + final templates = { + ...siteUrls.keys, + // 👇 Glidea 保护字,因为这些文件名是 Glidea 文件夹的名称 + 'images', 'media', 'post-images', 'styles', + }; + final custom = siteUrls['custom'] = []; + final files = FS.getFilesSync(FS.join(themePath, 'templates'), recursive: false); + customTemplates = files.map((file) => FS.basename(file.path)).toSet().difference(templates); + for (var name in customTemplates) { + if (templates.contains(name)) continue; + custom.add(name == '404' ? name : '$name/'); + } + } + /// 构建具有 post list 的页面 /// /// [urlPath] url 地址, home => '', archive => '/archives', tag => '/tag/{tag.slug}' @@ -421,13 +504,17 @@ final class RemoteRender { final html = env.getTemplate(templatePath).render(update == null ? renderData : update(renderData)); // 写入文件 FS.writeStringSync(renderPath, html); - addSiteUrl(currentUrlPath); } } /// 将文章中本地图片路径,变更为线上路径 String _changeImageUrlToDomain(String content) { - return content.replaceAll(featureReg, domain); + /// 匹配 feature 本地图片路径的正则 + /// + /// 'file://.*/post-images/' => '/post-images/' + /// + /// (?<=匹配开头).*(?=匹配结尾) + return content.replaceAll(RegExp(featurePrefix + r'.*(?=/post-images)'), domain); } /// 获取内容摘要 @@ -469,9 +556,4 @@ final class RemoteRender { stats.time = (minutes * 60).floor() * 1000; return stats; } - - /// 添加站点地图的 URl - void addSiteUrl(String path1, [String? path2, String? path3]) { - sitemap.add(SitemapEntry(location: FS.join(domain, path1, path2, path3))); - } } diff --git a/pubspec.lock b/pubspec.lock index 49e12bc..a3fd5dd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -946,6 +946,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "6.0.1" + process: + dependency: transitive + description: + name: process + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.3" + process_runner: + dependency: "direct main" + description: + name: process_runner + sha256: "4763f660895a1aea3c097bba3204794094b828cb9a279a65a2506a9b93d47d12" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.2.0" provider: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3bc8f3f..aa435b8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -76,6 +76,7 @@ dependencies: dartssh2: ^2.11.0 jinja: ^0.6.0 xml: ^6.5.0 + process_runner: ^4.2.0 dev_dependencies: flutter_test: