nodebb-plugin-timestamp 1.1.0 → 1.1.1
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.
- package/.oxfmtrc.json +5 -0
- package/.oxlintrc.json +40 -0
- package/LICENSE +21 -0
- package/README.md +64 -0
- package/docs/20f15e8a-d0f3-4020-ad62-75237ea6b6e9.webp +0 -0
- package/docs/ac198a72-2f08-4c17-82a0-29dbb1c0e4d0.webp +0 -0
- package/lib/index.ts +78 -0
- package/lib/type.ts +25 -0
- package/package.json +2 -5
- package/plugin.json +23 -0
- package/pnpm-workspace.yaml +1 -0
- package/public/client.ts +161 -0
- package/public/languages/en-GB/timestamp.json +8 -0
- package/public/languages/zh-CN/timestamp.json +8 -0
- package/public/scss/style.scss +11 -0
- package/public/tsconfig.json +17 -0
- package/rolldown.config.ts +66 -0
- package/tsconfig.json +18 -0
package/.oxfmtrc.json
ADDED
package/.oxlintrc.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
"plugins": ["typescript", "eslint", "unicorn", "oxc", "node", "jsdoc", "promise", "import"],
|
|
4
|
+
"categories": {},
|
|
5
|
+
"rules": {
|
|
6
|
+
"unicorn/no-empty-file": "allow"
|
|
7
|
+
},
|
|
8
|
+
"settings": {
|
|
9
|
+
"jsx-a11y": {
|
|
10
|
+
"components": {},
|
|
11
|
+
"attributes": {}
|
|
12
|
+
},
|
|
13
|
+
"next": {
|
|
14
|
+
"rootDir": []
|
|
15
|
+
},
|
|
16
|
+
"react": {
|
|
17
|
+
"formComponents": [],
|
|
18
|
+
"linkComponents": [],
|
|
19
|
+
"componentWrapperFunctions": []
|
|
20
|
+
},
|
|
21
|
+
"jsdoc": {
|
|
22
|
+
"ignorePrivate": false,
|
|
23
|
+
"ignoreInternal": false,
|
|
24
|
+
"ignoreReplacesDocs": false,
|
|
25
|
+
"overrideReplacesDocs": true,
|
|
26
|
+
"augmentsExtendsReplacesDocs": false,
|
|
27
|
+
"implementsReplacesDocs": false,
|
|
28
|
+
"exemptDestructuredRootsFromChecks": false,
|
|
29
|
+
"tagNamePreference": {}
|
|
30
|
+
},
|
|
31
|
+
"vitest": {
|
|
32
|
+
"typecheck": false
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"env": {
|
|
36
|
+
"builtin": true
|
|
37
|
+
},
|
|
38
|
+
"globals": {},
|
|
39
|
+
"ignorePatterns": []
|
|
40
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TASA-Ed
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# NodeBB Plugin Timestamp
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://github.com/NodeBB/NodeBB)
|
|
5
|
+
|
|
6
|
+
在帖子编辑器中插入一个可点击的按钮,方便用户选择日期时间,生成相对时间标记。其他用户看到的是相对时间(如"3 天后"),鼠标悬停即可看到浏览器本地时区的精确时间。
|
|
7
|
+
|
|
8
|
+
Add a button to the post composer for inserting relative timestamps. Other users see a relative time (e.g. "in 3 days") — hover to reveal the exact time in their local timezone.
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Features | 功能
|
|
17
|
+
|
|
18
|
+
- Composer toolbar button for inserting timestamps
|
|
19
|
+
- Datetime picker dialog for easy selection
|
|
20
|
+
- Server-side parsing: `[timestamp]1700000000000[/timestamp]` → `<time>` HTML element
|
|
21
|
+
- Client-side localization via `toLocaleString()`
|
|
22
|
+
- Relative time rendering compatible with NodeBB's built-in timeago
|
|
23
|
+
- i18n support (English / 简体中文)
|
|
24
|
+
|
|
25
|
+
## Installation | 安装
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install nodebb-plugin-timestamp
|
|
29
|
+
# or via NodeBB ACP plugin manager
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage | 使用
|
|
33
|
+
|
|
34
|
+
1. While composing a post, click the clock icon in the editor toolbar.
|
|
35
|
+
2. Select a date and time in the picker dialog.
|
|
36
|
+
3. A `[timestamp]...[/timestamp]` tag is inserted at the cursor position.
|
|
37
|
+
4. After posting, it renders as a relative time. Hover to see the exact time.
|
|
38
|
+
|
|
39
|
+
## Compatibility | 兼容性
|
|
40
|
+
|
|
41
|
+
NodeBB v4.12.0+
|
|
42
|
+
|
|
43
|
+
## Development | 开发
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Install dependencies
|
|
47
|
+
pnpm install
|
|
48
|
+
|
|
49
|
+
# Dev build with watch
|
|
50
|
+
pnpm dev
|
|
51
|
+
|
|
52
|
+
# Production build
|
|
53
|
+
pnpm build
|
|
54
|
+
|
|
55
|
+
# Type check
|
|
56
|
+
pnpm typecheck
|
|
57
|
+
|
|
58
|
+
# Lint
|
|
59
|
+
pnpm lint
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT © [TASA-Ed Studio](https://www.tasaed.top)
|
|
Binary file
|
|
Binary file
|
package/lib/index.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { FormattingPayload, ParsePostData, ParseRawData } from "./type.ts";
|
|
2
|
+
|
|
3
|
+
// 匹配标记:[timestamp]1700000000000[/timestamp]
|
|
4
|
+
const TIMESTAMP_REGEX = /\[timestamp](\d{10,13})\[\/timestamp]/g;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 向编辑器工具栏注册按钮
|
|
8
|
+
* 对应 plugin.json 中 filter:composer.formatting
|
|
9
|
+
*/
|
|
10
|
+
export function registerFormatting(
|
|
11
|
+
payload: FormattingPayload,
|
|
12
|
+
callback: (err: Error | null, payload: FormattingPayload) => void
|
|
13
|
+
): void {
|
|
14
|
+
payload.options.push({
|
|
15
|
+
name: "timestamp",
|
|
16
|
+
className: "fa fa-clock-o",
|
|
17
|
+
title: "[[timestamp:composer.timestamp]]"
|
|
18
|
+
});
|
|
19
|
+
callback(null, payload);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function applyTimestamp(content: string) {
|
|
23
|
+
return content.replace(TIMESTAMP_REGEX, (_match: string, ts: string) => {
|
|
24
|
+
const epoch = parseInt(ts, 10);
|
|
25
|
+
if (isNaN(epoch)) return _match;
|
|
26
|
+
|
|
27
|
+
const date = new Date(epoch);
|
|
28
|
+
const iso = date.toISOString();
|
|
29
|
+
// datetime 提供机器可读的 ISO 时间
|
|
30
|
+
// 实际显示文本由客户端 JS 根据用户本地时区渲染
|
|
31
|
+
// 服务端仅输出 UTC 作为 fallback
|
|
32
|
+
return [
|
|
33
|
+
`<time`,
|
|
34
|
+
` class="timeago plugin-timestamp"`,
|
|
35
|
+
` datetime="${iso}"`,
|
|
36
|
+
` title="${iso}"`,
|
|
37
|
+
` data-timestamp="${epoch}"`,
|
|
38
|
+
`>${iso}</time>`
|
|
39
|
+
].join(" ");
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 解析帖子内容,将时间戳标记转换为 HTML
|
|
45
|
+
* 对应 plugin.json 中 filter:parse.post
|
|
46
|
+
*/
|
|
47
|
+
export function parsePost(
|
|
48
|
+
data: ParsePostData,
|
|
49
|
+
callback: (err: Error | null, data: ParsePostData) => void
|
|
50
|
+
): void {
|
|
51
|
+
if (!data?.postData?.content) {
|
|
52
|
+
callback(null, data);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
data.postData.content = applyTimestamp(data.postData.content);
|
|
57
|
+
|
|
58
|
+
callback(null, data);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 解析 Raw,将时间戳标记转换为 HTML
|
|
63
|
+
* 对应 plugin.json 中 filter:parse.raw
|
|
64
|
+
*
|
|
65
|
+
* 注意:filter:parse.raw 传入的是原始字符串本身(用于编辑器预览等场景),
|
|
66
|
+
* 与 filter:parse.post 的对象结构不同
|
|
67
|
+
*/
|
|
68
|
+
export function parseRaw(
|
|
69
|
+
raw: ParseRawData,
|
|
70
|
+
callback: (err: Error | null, raw: ParseRawData) => void
|
|
71
|
+
): void {
|
|
72
|
+
if (!raw) {
|
|
73
|
+
callback(null, raw);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
callback(null, applyTimestamp(raw));
|
|
78
|
+
}
|
package/lib/type.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// filter:composer.formatting 的 payload 类型
|
|
2
|
+
export interface FormattingPayload {
|
|
3
|
+
options: Array<{
|
|
4
|
+
name: string;
|
|
5
|
+
className: string;
|
|
6
|
+
title: string;
|
|
7
|
+
// 可选:插入文本时的包裹符号
|
|
8
|
+
startTag?: string;
|
|
9
|
+
endTag?: string;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// filter:parse.post 的 data 类型
|
|
14
|
+
export interface ParsePostData {
|
|
15
|
+
postData: {
|
|
16
|
+
pid?: number;
|
|
17
|
+
content: string;
|
|
18
|
+
uid?: number;
|
|
19
|
+
};
|
|
20
|
+
uid?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// filter:parse.raw 传入的是原始字符串本身(用于解析任意文本片段,如编辑器预览),
|
|
24
|
+
// 而非 parse.post 那样的对象
|
|
25
|
+
export type ParseRawData = string;
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-timestamp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Insert a relative time component into the post | 在帖子中插入相对时间组件",
|
|
5
|
-
"homepage": "https://
|
|
5
|
+
"homepage": "https://github.com/TASA-Ed/nodebb-plugin-timestamp",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/TASA-Ed/nodebb-plugin-timestamp/issues"
|
|
8
8
|
},
|
|
@@ -15,9 +15,6 @@
|
|
|
15
15
|
"type": "git",
|
|
16
16
|
"url": "git+https://github.com/TASA-Ed/nodebb-plugin-timestamp.git"
|
|
17
17
|
},
|
|
18
|
-
"files": [
|
|
19
|
-
"build"
|
|
20
|
-
],
|
|
21
18
|
"type": "module",
|
|
22
19
|
"main": "./build/index.js",
|
|
23
20
|
"devDependencies": {
|
package/plugin.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "nodebb-plugin-timestamp",
|
|
3
|
+
"name": "Timestamp Component",
|
|
4
|
+
"description": "Insert relative times in posts; hover to display the exact time | 在帖子中插入相对时间,hover 显示精确时间",
|
|
5
|
+
"url": "https://github.com/TASA-Ed/nodebb-plugin-timestamp",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"hook": "filter:composer.formatting",
|
|
9
|
+
"method": "registerFormatting"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"hook": "filter:parse.post",
|
|
13
|
+
"method": "parsePost"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"hook": "filter:parse.raw",
|
|
17
|
+
"method": "parseRaw"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"scripts": ["build/public/client.js"],
|
|
21
|
+
"scss": ["public/scss/style.scss"],
|
|
22
|
+
"languages": "public/languages"
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
minimumReleaseAge: 60
|
package/public/client.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/// <reference types="jquery"/>
|
|
2
|
+
|
|
3
|
+
// bootbox 模块类型(通过 require(["bootbox"], ...) 按需加载)
|
|
4
|
+
interface Bootbox {
|
|
5
|
+
dialog: (options: {
|
|
6
|
+
title: string;
|
|
7
|
+
message: string;
|
|
8
|
+
buttons: Record<
|
|
9
|
+
string,
|
|
10
|
+
{
|
|
11
|
+
label: string;
|
|
12
|
+
className: string;
|
|
13
|
+
callback?: () => void | boolean;
|
|
14
|
+
}
|
|
15
|
+
>;
|
|
16
|
+
}) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// NodeBB 的 AMD require(RequireJS 风格)
|
|
20
|
+
declare function require(deps: string[], callback: (...modules: unknown[]) => void): void;
|
|
21
|
+
|
|
22
|
+
// composer/formatting 模块只用到 addButtonDispatch
|
|
23
|
+
interface FormattingModule {
|
|
24
|
+
// handler 由 dispatch table 调用,this 指向 postContainer
|
|
25
|
+
addButtonDispatch: (
|
|
26
|
+
name: string,
|
|
27
|
+
onClick: (
|
|
28
|
+
this: JQuery,
|
|
29
|
+
textarea: HTMLTextAreaElement,
|
|
30
|
+
selectionStart: number,
|
|
31
|
+
selectionEnd: number,
|
|
32
|
+
event: JQuery.Event
|
|
33
|
+
) => void
|
|
34
|
+
) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 获取 datetime-local input 所需的格式 "YYYY-MM-DDTHH:MM"
|
|
39
|
+
*/
|
|
40
|
+
function toDatetimeLocalValue(date: Date): string {
|
|
41
|
+
const pad = (n: number): string => String(n).padStart(2, "0");
|
|
42
|
+
return (
|
|
43
|
+
`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` +
|
|
44
|
+
`T${pad(date.getHours())}:${pad(date.getMinutes())}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 在 textarea 光标位置插入文本
|
|
50
|
+
*/
|
|
51
|
+
function insertAtCursor(
|
|
52
|
+
textarea: HTMLTextAreaElement,
|
|
53
|
+
start: number,
|
|
54
|
+
end: number,
|
|
55
|
+
text: string
|
|
56
|
+
): void {
|
|
57
|
+
const before = textarea.value.substring(0, start);
|
|
58
|
+
const after = textarea.value.substring(end);
|
|
59
|
+
textarea.value = before + text + after;
|
|
60
|
+
textarea.selectionStart = textarea.selectionEnd = start + text.length;
|
|
61
|
+
textarea.focus();
|
|
62
|
+
// 触发 NodeBB 编辑器的响应式更新与预览刷新
|
|
63
|
+
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 打开时间选择弹窗,确认后把标记插入到 textarea 光标处
|
|
68
|
+
*/
|
|
69
|
+
function openTimePicker(
|
|
70
|
+
textarea: HTMLTextAreaElement,
|
|
71
|
+
selectionStart: number,
|
|
72
|
+
selectionEnd: number
|
|
73
|
+
): void {
|
|
74
|
+
const defaultValue = toDatetimeLocalValue(new Date());
|
|
75
|
+
|
|
76
|
+
require(["bootbox"], (bootbox: unknown) => {
|
|
77
|
+
(bootbox as Bootbox).dialog({
|
|
78
|
+
title: "[[timestamp:dialog.title]]",
|
|
79
|
+
message: `
|
|
80
|
+
<div class="form-group">
|
|
81
|
+
<label for="plugin-ts-picker" class="form-label">[[timestamp:dialog.label]]</label>
|
|
82
|
+
<input
|
|
83
|
+
type="datetime-local"
|
|
84
|
+
id="plugin-ts-picker"
|
|
85
|
+
class="form-control"
|
|
86
|
+
value="${defaultValue}"
|
|
87
|
+
/>
|
|
88
|
+
<div class="form-text text-muted" style="margin-top:6px">
|
|
89
|
+
[[timestamp:dialog.description]]
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
`,
|
|
93
|
+
buttons: {
|
|
94
|
+
cancel: {
|
|
95
|
+
label: "[[timestamp:dialog.cancel]]",
|
|
96
|
+
className: "btn-secondary"
|
|
97
|
+
},
|
|
98
|
+
confirm: {
|
|
99
|
+
label: "[[timestamp:dialog.confirm]]",
|
|
100
|
+
className: "btn-primary",
|
|
101
|
+
callback(): void {
|
|
102
|
+
const input = document.getElementById("plugin-ts-picker") as HTMLInputElement | null;
|
|
103
|
+
if (!input?.value) return;
|
|
104
|
+
|
|
105
|
+
const ts = new Date(input.value).getTime();
|
|
106
|
+
if (isNaN(ts)) return;
|
|
107
|
+
|
|
108
|
+
insertAtCursor(textarea, selectionStart, selectionEnd, `[timestamp]${ts}[/timestamp]`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 将页面中所有 .plugin-timestamp 元素的 title(hover tooltip)
|
|
118
|
+
* 替换为用户浏览器本地时区的格式化时间。
|
|
119
|
+
* 不修改 textContent —— 让 NodeBB 内置的 timeago 继续渲染相对时间。
|
|
120
|
+
*/
|
|
121
|
+
function localizeTimestamps(root: ParentNode = document): void {
|
|
122
|
+
const elements = root.querySelectorAll<HTMLTimeElement>("time.plugin-timestamp");
|
|
123
|
+
elements.forEach((el) => {
|
|
124
|
+
const iso = el.getAttribute("datetime");
|
|
125
|
+
if (!iso) return;
|
|
126
|
+
|
|
127
|
+
const date = new Date(iso);
|
|
128
|
+
if (isNaN(date.getTime())) return;
|
|
129
|
+
|
|
130
|
+
el.title = date.toLocaleString(undefined, {
|
|
131
|
+
year: "numeric",
|
|
132
|
+
month: "2-digit",
|
|
133
|
+
day: "2-digit",
|
|
134
|
+
hour: "2-digit",
|
|
135
|
+
minute: "2-digit",
|
|
136
|
+
second: "2-digit"
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// NodeBB 页面加载完成后,向 composer 的 dispatch table 注册按钮点击处理
|
|
142
|
+
$(window).on("action:app.load", () => {
|
|
143
|
+
require(["composer/formatting"], (formatting: unknown) => {
|
|
144
|
+
(formatting as FormattingModule).addButtonDispatch(
|
|
145
|
+
"timestamp",
|
|
146
|
+
function (textarea, selectionStart, selectionEnd): void {
|
|
147
|
+
openTimePicker(textarea, selectionStart, selectionEnd);
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// 初次加载 + ajaxify 切换页面时,本地化所有时间戳
|
|
154
|
+
$(window).on("action:app.load action:ajaxify.end", () => {
|
|
155
|
+
localizeTimestamps();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// 帖子动态追加时(无限滚动等),也需要处理新出现的时间戳
|
|
159
|
+
$(window).on("action:posts.loaded", () => {
|
|
160
|
+
localizeTimestamps();
|
|
161
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"composer.timestamp": "Insert time",
|
|
3
|
+
"dialog.title": "Insert time",
|
|
4
|
+
"dialog.label": "Select a time",
|
|
5
|
+
"dialog.description": "Other users will see a relative time (such as \"in 3 days\"); hover over it to see the exact time.",
|
|
6
|
+
"dialog.cancel": "Cancel",
|
|
7
|
+
"dialog.confirm": "Insert"
|
|
8
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "umd",
|
|
5
|
+
"moduleResolution": "node10",
|
|
6
|
+
"types": ["jquery"],
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"allowImportingTsExtensions": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"noImplicitAny": false,
|
|
12
|
+
"incremental": true,
|
|
13
|
+
"composite": true,
|
|
14
|
+
"skipLibCheck": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["lib"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { defineConfig, RolldownPluginOption } from "rolldown";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
const rolldownCopyCssPlugin: RolldownPluginOption = {
|
|
6
|
+
name: "rolldown-plugin-copy-css",
|
|
7
|
+
buildStart() {
|
|
8
|
+
const srcDir = path.resolve("public/scss");
|
|
9
|
+
|
|
10
|
+
// 如果目录不存在则直接跳过
|
|
11
|
+
if (!fs.existsSync(srcDir)) return;
|
|
12
|
+
|
|
13
|
+
const files = fs.readdirSync(srcDir);
|
|
14
|
+
|
|
15
|
+
for (const file of files) {
|
|
16
|
+
if (file.endsWith(".scss")) {
|
|
17
|
+
const filePath = path.join(srcDir, file);
|
|
18
|
+
const source = fs.readFileSync(filePath, "utf-8");
|
|
19
|
+
|
|
20
|
+
// 将 CSS 注册为 Asset 资源
|
|
21
|
+
// 这里的 fileName 是相对于 Rolldown 配置中 output.dir(例如 'build')的路径
|
|
22
|
+
this.emitFile({
|
|
23
|
+
type: "asset",
|
|
24
|
+
fileName: `css/${file}`,
|
|
25
|
+
source: source
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default defineConfig([
|
|
33
|
+
{
|
|
34
|
+
input: ["lib/index.ts"],
|
|
35
|
+
platform: "node",
|
|
36
|
+
output: {
|
|
37
|
+
dir: "build",
|
|
38
|
+
format: "es",
|
|
39
|
+
cleanDir: true,
|
|
40
|
+
strict: true,
|
|
41
|
+
topLevelVar: true,
|
|
42
|
+
minify: {
|
|
43
|
+
compress: true,
|
|
44
|
+
mangle: false
|
|
45
|
+
},
|
|
46
|
+
sourcemap: true
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
input: ["public/client.ts"],
|
|
51
|
+
platform: "node",
|
|
52
|
+
output: {
|
|
53
|
+
dir: "build/public",
|
|
54
|
+
format: "umd",
|
|
55
|
+
cleanDir: true,
|
|
56
|
+
strict: true,
|
|
57
|
+
topLevelVar: true,
|
|
58
|
+
minify: {
|
|
59
|
+
compress: true,
|
|
60
|
+
mangle: false
|
|
61
|
+
},
|
|
62
|
+
sourcemap: true
|
|
63
|
+
},
|
|
64
|
+
plugins: [rolldownCopyCssPlugin]
|
|
65
|
+
}
|
|
66
|
+
]);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "node16",
|
|
5
|
+
"moduleResolution": "node16",
|
|
6
|
+
"types": ["node"],
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"allowImportingTsExtensions": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"noImplicitAny": false,
|
|
12
|
+
"incremental": true,
|
|
13
|
+
"composite": true,
|
|
14
|
+
"skipLibCheck": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["lib", "rolldown.config.ts"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|