@occultus/article-api 0.18.2

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/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright © 2025 CTN Studios
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # Star Tenon Article API
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@occultus/article-api",
3
+ "version": "0.18.2",
4
+ "description": "Star Tenon format tool",
5
+ "main": "src/index.ts",
6
+ "keywords": [
7
+ "Minecraft",
8
+ "Occultus SDK",
9
+ "Script API"
10
+ ],
11
+ "contributors": [
12
+ "FangLimao <mucigames@outlook.com>",
13
+ "RawDiamondMC <RawDiamondMC@outlook.com>"
14
+ ],
15
+ "license": "MIT",
16
+ "author": "CTN Studios",
17
+ "dependencies": {
18
+ "@occultus/text-api": "0.18.1"
19
+ },
20
+ "peerDependencies": {
21
+ "@occultus/core": ">=0.18.2 || <0.19.0",
22
+ "@minecraft/server": ">=2.0.0",
23
+ "@minecraft/server-ui": ">=2.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "typedoc": "^0.28.9"
27
+ }
28
+ }
@@ -0,0 +1,135 @@
1
+ import { Player } from "@minecraft/server";
2
+ import { ActionFormData } from "@minecraft/server-ui";
3
+ import { ChapterData } from "../interface/ChapterData";
4
+ import { BaseArticle } from "./Base/BaseArticle";
5
+ import { parseText, TextProvider } from "@occultus/text-api";
6
+ import { OccultusSDKError } from "@occultus/core";
7
+
8
+ /**
9
+ * 创建一个文章
10
+ * @category Stable
11
+ * @since 1.0.0
12
+ */
13
+ export class Article extends BaseArticle {
14
+ /**
15
+ * @param id 文章ID
16
+ * @param title 文章标题
17
+ * @param body 文章内容,支持字符串、RawMessage 和动态生成
18
+ * @param chapters 文章的章节
19
+ * @param iconPath 文章的图标路径
20
+ * @param needUnlock 是否需要解锁
21
+ */
22
+ constructor(
23
+ public readonly id: string,
24
+ public title: TextProvider,
25
+ public body: TextProvider,
26
+ public chapters?: ChapterData[],
27
+ public iconPath?: string,
28
+ public needUnlock = true
29
+ ) {
30
+ super(id, title, body, chapters);
31
+ }
32
+ /**
33
+ * 向玩家展示文章(文章无章节时)
34
+ * @param player 要展示文章的玩家
35
+ * @param backTo 关闭文章后返回的界面
36
+ * @private
37
+ */
38
+ private simpleDisplay(player: Player, backTo?: BaseArticle): void {
39
+ if (!this.checkUnlock(player)) {
40
+ this.unlock(player);
41
+ }
42
+ const title = parseText(this.title, player);
43
+ const body = parseText(this.body, player);
44
+ const mainForm = new ActionFormData()
45
+ .title(title)
46
+ .body(body)
47
+ .button({ translate: "gui.ok" });
48
+ // @ts-ignore
49
+ mainForm.show(player).then((response) => {
50
+ if (response.selection === 0 || response.canceled) {
51
+ backTo?.display(player);
52
+ return;
53
+ }
54
+ });
55
+ }
56
+ /**
57
+ * 向玩家展示文章(文章有章节时)
58
+ * @param player 要展示文章的玩家
59
+ * @param backTo 关闭文章后返回的界面
60
+ * @private
61
+ */
62
+ private chapterDisplay(player: Player, backTo?: BaseArticle): void {
63
+ if (!this.checkUnlock(player)) {
64
+ this.unlock(player);
65
+ }
66
+ const [title, body] = [
67
+ parseText(this.title, player),
68
+ parseText(this.body, player),
69
+ ];
70
+ const contentsForm = new ActionFormData().title(title).body(body);
71
+ if (!this.chapters) {
72
+ throw new OccultusSDKError("Cannot find chapter data!");
73
+ }
74
+ this.chapters.forEach((chapter) => {
75
+ const chapterTitle = parseText(chapter.title, player);
76
+ contentsForm.button(chapterTitle, chapter.iconPath);
77
+ });
78
+ // @ts-ignore
79
+ contentsForm.show(player).then((response) => {
80
+ if (response.canceled || response.selection === undefined) {
81
+ backTo?.display(player);
82
+ return;
83
+ }
84
+ if (!this.chapters) {
85
+ throw new OccultusSDKError("Invaild chapter data!");
86
+ }
87
+ const [chapterTitle, chapterBody] = [
88
+ parseText(this.chapters[response.selection].title, player),
89
+ parseText(this.chapters[response.selection].body, player),
90
+ ];
91
+
92
+ const chapterForm = new ActionFormData()
93
+ .title(chapterTitle)
94
+ .body(chapterBody)
95
+ .button({ translate: "gui.ok" });
96
+ // @ts-ignore
97
+ chapterForm.show(player).then((response) => {
98
+ if (response.selection === 0 || response.canceled) {
99
+ // @ts-ignore
100
+ contentsForm.show(player);
101
+ return;
102
+ }
103
+ });
104
+ });
105
+ }
106
+ /**
107
+ * 向玩家展示文章
108
+ * @param player 要展示文章的玩家
109
+ * @param backTo 关闭文章后返回的界面
110
+ */
111
+ display(player: Player, backTo?: BaseArticle): void {
112
+ if (this.chapters) {
113
+ this.chapterDisplay(player, backTo);
114
+ return;
115
+ }
116
+ this.simpleDisplay(player, backTo);
117
+ }
118
+ /**
119
+ * 在文章中心里解锁文章
120
+ * @param player
121
+ */
122
+ unlock(player: Player): void {
123
+ player.addTag(`articleUnlock:${this.id}`);
124
+ }
125
+ /**
126
+ * 检查文章是否在文章中心里解锁
127
+ * @param player
128
+ */
129
+ checkUnlock(player: Player): boolean {
130
+ if (this.needUnlock) {
131
+ return player.hasTag(`articleUnlock:${this.id}`);
132
+ }
133
+ return true;
134
+ }
135
+ }
@@ -0,0 +1,111 @@
1
+ import { Player } from "@minecraft/server";
2
+ import { MessageFormData } from "@minecraft/server-ui";
3
+ import { ArticleRegistries } from "./Base/ArticleRegistries";
4
+ import { Article } from "./Article";
5
+ import { BaseArticle } from "./Base/BaseArticle";
6
+ import { generateContentForm } from "../lib/utils";
7
+ import { TextProvider } from "@occultus/text-api";
8
+
9
+ /**
10
+ * 集中显示文章的类
11
+ *
12
+ * 核心功能包括:
13
+ * - 显示文章列表给玩家
14
+ * - 处理文章的解锁和显示
15
+ *
16
+ * 注意:
17
+ * - 如果`articles`参数为布尔值,如果为`true`,则会从该脚本环境注册的所有文章中显示已经解锁的文章,反之则显示无文章提示
18
+ * - 如果`articles`参数为文章列表,则只从提供的文章中显示已解锁的文章
19
+ *
20
+ * @example
21
+ * const articleCenter = new ArticleCenter("article:id", title, body, true);
22
+ * articleCenter.display(player); // 显示文章中心给玩家
23
+ * articleCenter.subscribeEvent("itemId"); // 注册文章事件
24
+ * articleCenter.registryComponent("componentName"); // 注册自定义组件
25
+ *
26
+ * @category Stable
27
+ * @since 1.0.0
28
+ */
29
+
30
+
31
+ export class ArticleCenter extends BaseArticle {
32
+ /**
33
+ * @param id 文章中心ID
34
+ * @param title 文章中心标题
35
+ * @param body 文章中心内容
36
+ * @param articles 可用的文章,如果为`true`,则会从该脚本环境注册的所有文章中显示已经解锁的文章,反之则显示无文章提示,如果为文章列表,则只从提供的文章中显示已解锁的文章
37
+ */
38
+ constructor(
39
+ public readonly id: string,
40
+ public title: TextProvider,
41
+ public body: TextProvider,
42
+ public articles: Article[] | boolean,
43
+ public readonly articlesManager: ArticleRegistries,
44
+ public iconPath?: string
45
+ ) {
46
+ super(id, title, body, articles);
47
+ }
48
+ /**
49
+ * 向玩家展示文章中心
50
+ * @param player 要展示文章中心的玩家
51
+ */
52
+ display(player: Player): void {
53
+ if (typeof this.articles === "boolean") {
54
+ if (!this.articles) {
55
+ this.warn(player);
56
+ return;
57
+ }
58
+ const contentForm = generateContentForm(
59
+ player,
60
+ this.title,
61
+ this.body,
62
+ this.articlesManager.getAll()
63
+ );
64
+ if (!contentForm) {
65
+ this.warn(player);
66
+ return;
67
+ }
68
+ // @ts-ignore
69
+ contentForm.form.show(player).then((response) => {
70
+ if (response.canceled || response.selection === undefined) {
71
+ return;
72
+ }
73
+ contentForm.list[response.selection].display(player, this);
74
+ });
75
+ return;
76
+ }
77
+ const contentForm = generateContentForm(
78
+ player,
79
+ this.title,
80
+ this.body,
81
+ this.articles
82
+ );
83
+ if (!contentForm) {
84
+ this.warn(player);
85
+ return;
86
+ }
87
+ // @ts-ignore
88
+ contentForm.form.show(player).then((response) => {
89
+ if (response.canceled || response.selection === undefined) {
90
+ return;
91
+ }
92
+ contentForm.list[response.selection].display(player, this);
93
+ });
94
+ }
95
+ /**
96
+ * 当没有可用的文章时,向玩家显示警告
97
+ * @param player 被显示提示的玩家
98
+ * @private
99
+ */
100
+ private warn(player: Player): void {
101
+ const warningForm = new MessageFormData()
102
+ .title({ translate: "article.nothing.title" })
103
+ .body({ translate: "article.nothing.body" })
104
+ .button1({ translate: "gui.ok" })
105
+ .button2({ translate: "gui.close" });
106
+ // @ts-ignore
107
+ warningForm.show(player).then((_response) => {
108
+ return;
109
+ });
110
+ }
111
+ }
@@ -0,0 +1,65 @@
1
+ import { Registries } from "@occultus/core";
2
+ import { Article } from "../Article";
3
+ import { BaseArticle } from "./BaseArticle";
4
+ import { articleRegister } from "../../lib/utils";
5
+ import { ArticleCenter } from "../ArticleCenter";
6
+ import { ReadableArticle } from "../../types/ReadableArticle";
7
+ import { system } from "@minecraft/server";
8
+
9
+ /**
10
+ * 管理文章数据的类
11
+ *
12
+ * 该类提供了获取所有文章和文章ID列表的方法,以及根据ID获取特定文章的功能。
13
+ *
14
+ * @category Stable
15
+ * @since 1.0.0
16
+ */
17
+ export class ArticleRegistries extends Registries {
18
+ id = "ArticleRegistries";
19
+ /**
20
+ * @category Internal
21
+ * @since 1.0.0
22
+ */
23
+ private registry: Map<string, Article | ArticleCenter> = new Map();
24
+
25
+ add(value: ReadableArticle | ReadableArticle[]): void {
26
+ if (Array.isArray(value)) {
27
+ value.forEach((article) => this.add(article));
28
+ return;
29
+ }
30
+ this.registry.set(value.id, value);
31
+ }
32
+ register(): void {
33
+ system.beforeEvents.startup.subscribe((arg) => {
34
+ this.registry.forEach((article) => {
35
+ articleRegister(article.id, article, arg);
36
+ });
37
+ });
38
+ }
39
+ delete(id: string) {
40
+ return this.registry.delete(id);
41
+ }
42
+ /**
43
+ * 获取该注册表下所有文章
44
+ * @returns 一个包含该注册表下所有文章的数组
45
+ */
46
+ getAll() {
47
+ return Array.from(this.registry.values());
48
+ }
49
+ /**
50
+ * 根据文章ID获取文章
51
+ * @param id 文章ID
52
+ * @return 返回找到的文章,如果没有找到则返回`undefined`
53
+ */
54
+ get(id: string): undefined | BaseArticle {
55
+ return this.registry.get(id);
56
+ }
57
+ /**
58
+ * 判断注册表中是否含有某一特定文章
59
+ * @param id 文章的 ID
60
+ * @return 如果含有该文章,则返回`true`,否则返回`false`
61
+ */
62
+ has(id: string): boolean {
63
+ return this.registry.has(id);
64
+ }
65
+ }
@@ -0,0 +1,26 @@
1
+ import { Player } from "@minecraft/server";
2
+ import { TextProvider } from "@occultus/text-api";
3
+
4
+ /**
5
+ * 描述文章的抽象类
6
+ */
7
+ export abstract class BaseArticle {
8
+ /**
9
+ * @param id 文章的唯一标识符
10
+ * @param title 文章的标题
11
+ * @param body 文章的内容
12
+ * @param data 文章的其他数据
13
+ */
14
+ constructor(
15
+ public readonly id: string,
16
+ public title: TextProvider,
17
+ public body: TextProvider,
18
+ public data: unknown
19
+ ) {}
20
+ /**
21
+ * 向玩家展示文章
22
+ * @param player 要展示文章的玩家
23
+ * @param backTo 展示后返回的界面
24
+ */
25
+ abstract display(player: Player, backTo?: BaseArticle): void;
26
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./lib/utils";
2
+ export * from "./api/Article";
3
+ export * from "./api/ArticleCenter";
4
+ export * from "./api/Base/ArticleRegistries";
5
+ export * from "./api/Base/BaseArticle";
6
+ export * from "./interface/ChapterData";
@@ -0,0 +1,20 @@
1
+ import { TextProvider } from "@occultus/text-api";
2
+
3
+
4
+ /**
5
+ * 描述文章的章节数据的接口
6
+ */
7
+ export interface ChapterData {
8
+ /**
9
+ * 章节标题
10
+ */
11
+ title: TextProvider;
12
+ /**
13
+ * 章节内容
14
+ */
15
+ body: TextProvider;
16
+ /**
17
+ * 章节图标路径
18
+ */
19
+ iconPath?: string;
20
+ }
@@ -0,0 +1,67 @@
1
+ import {
2
+ Player,
3
+ RawMessage,
4
+ StartupEvent,
5
+ system,
6
+ world,
7
+ } from "@minecraft/server";
8
+ import { ActionFormData } from "@minecraft/server-ui";
9
+ import { Article } from "../api/Article";
10
+ import { BaseArticle } from "../api/Base/BaseArticle";
11
+ import { ReadableArticle } from "../types/ReadableArticle";
12
+ import { TextProvider, parseText } from "@occultus/text-api";
13
+
14
+ /**
15
+ * 根据提供的资源生成目录表单
16
+ * @param player
17
+ * @param rawTitle
18
+ * @param rawBody
19
+ * @param articles
20
+ * @returns
21
+ * @category Internal
22
+ * @since 1.0.0
23
+ */
24
+ export function generateContentForm(
25
+ player: Player,
26
+ rawTitle: TextProvider,
27
+ rawBody: TextProvider,
28
+ articles: ReadableArticle[]
29
+ ): { form: ActionFormData; list: ReadableArticle[] } | undefined {
30
+ const unlockedArticles: ReadableArticle[] = [];
31
+ const [title, body] = [
32
+ parseText(rawTitle, player),
33
+ parseText(rawBody, player),
34
+ ];
35
+ const contentForm = new ActionFormData().title(title).body(body);
36
+ articles.forEach((article) => {
37
+ if (article instanceof Article) {
38
+ if (!article.checkUnlock(player)) {
39
+ return;
40
+ }
41
+ const title = parseText(article.title, player);
42
+ contentForm.button(title, article.iconPath);
43
+ unlockedArticles.push(article);
44
+ return;
45
+ }
46
+ const title = parseText(article.title, player);
47
+ contentForm.button(title, article.iconPath);
48
+ unlockedArticles.push(article);
49
+ });
50
+ if (unlockedArticles.length === 0) {
51
+ return;
52
+ }
53
+ return { form: contentForm, list: unlockedArticles };
54
+ }
55
+
56
+ export function articleRegister(
57
+ componentId: string,
58
+ article: BaseArticle,
59
+ arg: StartupEvent
60
+ ) {
61
+ arg.itemComponentRegistry.registerCustomComponent(componentId, {
62
+ onUse: (callback) => {
63
+ article.display(callback.source);
64
+ },
65
+ });
66
+ }
67
+
@@ -0,0 +1,7 @@
1
+ import { Article } from "../api/Article";
2
+ import { ArticleCenter } from "../api/ArticleCenter";
3
+
4
+ /**
5
+ * 所有可读文章类型
6
+ */
7
+ export type ReadableArticle = Article | ArticleCenter;
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "include": ["src/*"],
3
+ "exclude": ["./out"],
4
+ "Modules": {
5
+ "resolvePackageJsonExports": true
6
+ },
7
+ "compilerOptions": {
8
+ "noEmit": true,
9
+ "noEmitOnError": true,
10
+ "target": "es2022",
11
+ "lib": ["es2020", "dom"],
12
+ "strict": true,
13
+ "esModuleInterop": true,
14
+ "module": "es2022",
15
+ "moduleResolution": "bundler",
16
+ "outDir": ".",
17
+ "removeComments": true,
18
+ "newLine": "lf",
19
+ "resolveJsonModule": true
20
+ }
21
+ }