chanjs 2.6.14 → 2.6.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,15 @@
1
1
  import template from "art-template";
2
2
  import dayjs from "dayjs";
3
+ import relativeTime from "dayjs/plugin/relativeTime.js";
4
+ import "dayjs/locale/zh-cn.js";
3
5
  import { createRequire } from 'module';
4
6
  const require = createRequire(import.meta.url);
5
7
  const { marked } = require('marked');
6
8
 
7
- //template.defaults.native = false; // 禁用原生模板引擎 防止模板直接调用nodejs语法
9
+ dayjs.extend(relativeTime);
10
+ dayjs.locale('zh-cn');
11
+
12
+ template.defaults.native = false; // 禁用原生模板引擎 防止模板直接调用nodejs语法
8
13
  //template.defaults.debug = false; // 禁用调试模式
9
14
 
10
15
 
@@ -30,6 +35,18 @@ template.defaults.imports.dateFormat = function (date, format) {
30
35
  return date.format(format);
31
36
  };
32
37
 
38
+ /**
39
+ * 相对时间过滤器(如"刚刚"、"5分钟前"、"3小时前")
40
+ * @param {Date|string|number} date - 日期对象、日期字符串或时间戳
41
+ * @returns {string} 相对时间字符串
42
+ */
43
+ template.defaults.imports.timeAgo = function (date) {
44
+ if (!date) return "";
45
+ const d = dayjs(date);
46
+ if (!d.isValid()) return "";
47
+ return d.fromNow();
48
+ };
49
+
33
50
  /**
34
51
  * 字符串截断过滤器
35
52
  * @param {string} str - 原始字符串
@@ -65,21 +82,48 @@ template.defaults.imports.safeStringify = (obj, keys) => {
65
82
  return JSON.stringify(obj, null, 2);
66
83
  };
67
84
 
85
+ /**
86
+ * 获取对象所有key的过滤器,方便调试查看数据结构
87
+ * 用法:{{$data | objKeys}} 或 {{$data | objKeys ','}}
88
+ * @param {Object} obj - 要获取key的对象
89
+ * @param {string} separator - 分隔符,默认为换行
90
+ * @returns {string} 所有key的字符串
91
+ */
92
+ template.defaults.imports.objKeys = (obj, separator) => {
93
+ if (!obj || typeof obj !== 'object') return '';
94
+ const sep = separator !== undefined ? separator : '\n';
95
+ return Object.keys(obj).join(sep);
96
+ };
97
+
68
98
  /**
69
99
  * Markdown 渲染过滤器
70
100
  * 自动检测内容是否为 Markdown 格式,如果是则渲染为 HTML
71
101
  * @param {string} content - 文章内容
72
102
  * @returns {string} 渲染后的 HTML
73
103
  */
74
- template.defaults.imports.renderMarkdown = (content) => {
104
+ template.defaults.imports.renderContent = (content, editorType = 'rich', allowScript = 0) => {
75
105
  if (!content || typeof content !== 'string') {
76
106
  return content || '';
77
107
  }
78
108
 
79
- try {
80
- return marked.parse(content);
81
- } catch (err) {
82
- console.error('[renderMarkdown] Markdown 渲染失败:', err.message);
83
- return content;
109
+ let html = content;
110
+
111
+ // Markdown 需要转换
112
+ if (editorType === 'md') {
113
+ try {
114
+ html = marked.parse(content);
115
+ } catch (err) {
116
+ console.error('[renderContent] Markdown 渲染失败:', err.message);
117
+ html = content;
118
+ }
119
+ }
120
+
121
+ // 如果 allowScript !== 1,转义 script 标签
122
+ if (Number(allowScript) !== 1) {
123
+ html = html
124
+ .replace(/<script\b[^>]*>/gi, '&amp;lt;script&amp;gt;')
125
+ .replace(/<\/script>/gi, '&amp;lt;/script&amp;gt;');
84
126
  }
127
+
128
+ return html;
85
129
  };
package/helper/html.js CHANGED
@@ -1,30 +1,40 @@
1
1
  /**
2
- * 解码 HTML 实体字符
3
- * @param {string} str - 包含 HTML 实体的字符串
4
- * @returns {string} 解码后的字符串,如果输入为空则返回空字符串
5
- * @description
6
- * 将 HTML 实体字符转换为对应的普通字符
7
- * 支持的实体字符:
8
- * - &amp; -> &
9
- * - &lt; -> <
10
- * - &gt; -> >
11
- * - &quot; -> "
12
- * - &apos; -> '
13
- * - &nbsp; -> 空格
14
- * @example
15
- * const html = '&lt;div&gt;Hello &amp; World&lt;/div&gt;';
16
- * const decoded = htmlDecode(html);
17
- * console.log(decoded); // '<div>Hello & World</div>'
2
+ * 标准 HTML 编码(art-template {{}} 使用)
3
+ */
4
+ export function htmlEncode(str) {
5
+ if (typeof str !== 'string') return '';
6
+ return str
7
+ .replace(/&/g, '&amp;')
8
+ .replace(/</g, '&lt;')
9
+ .replace(/>/g, '&gt;')
10
+ .replace(/"/g, '&quot;')
11
+ .replace(/'/g, '&apos;');
12
+ }
13
+
14
+ /**
15
+ * 【适配 art-template {{@}}】二次转义 script 标签
16
+ * 必须这样写才能防 XSS!
17
+ */
18
+ export const escapeScript = (str) => {
19
+ if (typeof str !== 'string') return '';
20
+ return str
21
+ // 二次转义 → 经过 {{@}} 解码后变成 &lt;script&gt;
22
+ .replace(/<script\b[^>]*>/gi, '&amp;lt;script&amp;gt;')
23
+ .replace(/<\/script>/gi, '&amp;lt;/script&amp;gt;');
24
+ };
25
+
26
+ /**
27
+ * HTML 解码
18
28
  */
19
29
  export function htmlDecode(str) {
20
- if (!str) return "";
21
- const htmlEntities = {
22
- "&amp;": "&",
23
- "&lt;": "<",
24
- "&gt;": ">",
25
- "&quot;": '"',
26
- "&apos;": "'",
27
- "&nbsp;": " ",
30
+ if (typeof str !== 'string') return '';
31
+ const entities = {
32
+ '&amp;': '&',
33
+ '&lt;': '<',
34
+ '&gt;': '>',
35
+ '&quot;': '"',
36
+ '&apos;': "'",
37
+ '&nbsp;': ' ',
28
38
  };
29
- return str.replace(/&[a-z]+;/gi, (match) => htmlEntities[match] || match);
30
- }
39
+ return str.replace(/&(amp|lt|gt|quot|apos|nbsp);/g, m => entities[m]);
40
+ }
package/helper/index.js CHANGED
@@ -19,7 +19,7 @@ export {
19
19
  } from "./file.js";
20
20
 
21
21
  // HTML处理
22
- export { htmlDecode } from "./html.js";
22
+ export { htmlDecode, htmlEncode,escapeScript } from "./html.js";
23
23
 
24
24
  // IP相关
25
25
  export { getIp } from "./ip.js";
package/middleware/waf.js CHANGED
@@ -33,6 +33,7 @@ const WAF_PATH_WHITELIST = [
33
33
  "/.well-known/change-password", // 密码管理
34
34
  "/favicon.ico", // 浏览器自动请求
35
35
  "/robots.txt", // 搜索引擎爬虫
36
+ "/llms.txt", // AI引擎爬虫
36
37
  "/sitemap.xml", // 站点地图
37
38
  ];
38
39
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "chanjs",
4
- "version": "2.6.14",
4
+ "version": "2.6.16",
5
5
  "description": "chanjs基于express5 纯js研发的轻量级mvc框架。",
6
6
  "main": "index.js",
7
7
  "module": "index.js",
@@ -23,15 +23,18 @@
23
23
  "body-parser": "^2.2.2",
24
24
  "cookie-parser": "^1.4.7",
25
25
  "cors": "^2.8.6",
26
- "dotenv": "^17.4.2",
27
26
  "dayjs": "^1.11.20",
27
+ "dotenv": "^17.4.2",
28
28
  "express": "^5.2.1",
29
29
  "express-art-template": "^1.0.1",
30
30
  "ip2region": "^2.3.0",
31
+ "jsonwebtoken": "^9.0.3",
31
32
  "knex": "^3.2.10",
33
+ "marked": "^18.0.3",
32
34
  "morgan": "^1.10.1",
33
35
  "mysql2": "^3.22.3",
36
+ "serve-favicon": "^2.5.1",
34
37
  "sqlite3": "^6.0.1",
35
- "marked": "^18.0.3"
38
+ "xss": "^1.0.15"
36
39
  }
37
40
  }