@jchat/addon-sdk 1.0.0-alpha.3 → 1.0.0-alpha.30
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/CHANGELOG.md +223 -0
- package/LICENSE +18 -196
- package/README.md +385 -20
- package/dist/cli.d.mts +54 -0
- package/dist/cli.mjs +154 -0
- package/dist/contracts.d.mts +2 -130
- package/dist/contracts.mjs +64 -0
- package/dist/contracts2.d.mts +282 -0
- package/dist/diagnostics.d.mts +55 -9
- package/dist/diagnostics.mjs +67 -2
- package/dist/i18n-validator.d.mts +17 -0
- package/dist/i18n-validator.mjs +91 -0
- package/dist/i18n.mjs +169 -0
- package/dist/icon.d.mts +4 -0
- package/dist/icon.mjs +4 -0
- package/dist/index.d.mts +16 -267
- package/dist/index.mjs +5 -105
- package/dist/index2.d.mts +831 -0
- package/dist/signing.d.mts +2 -0
- package/dist/signing.mjs +136 -0
- package/dist/signing2.d.mts +76 -0
- package/dist/src.mjs +468 -0
- package/dist/state.d.mts +1 -0
- package/dist/state.mjs +2 -0
- package/dist/testing.d.mts +2 -0
- package/dist/testing.mjs +2 -0
- package/dist/types.d.mts +12 -0
- package/dist/ui.d.mts +42 -0
- package/dist/ui.mjs +33 -0
- package/dist/vite.d.mts +64 -10
- package/dist/vite.mjs +232 -38
- package/package.json +139 -20
- package/skills/create-jchat-addon/SKILL.md +140 -0
- package/skills/jchat-addon-development/SKILL.md +260 -0
- package/tsconfig.addon.json +20 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/i18n-validator.d.ts
|
|
2
|
+
export interface AddonLocaleValidationResult {
|
|
3
|
+
valid: boolean;
|
|
4
|
+
errors: string[];
|
|
5
|
+
missingFiles: string[];
|
|
6
|
+
missingKeys: Record<string, string[]>;
|
|
7
|
+
extraKeys: Record<string, string[]>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Validates that an addon's `locales/` directory contains valid translation files for all 20 host
|
|
11
|
+
* languages with 100% key parity against `en.json`.
|
|
12
|
+
*
|
|
13
|
+
* @param dirPath - Path to the addon's locales directory.
|
|
14
|
+
* @param domainName - Optional label for error messages (defaults to "Addon").
|
|
15
|
+
*/
|
|
16
|
+
export declare function validateAddonLocales(dirPath: string, domainName?: string): AddonLocaleValidationResult;
|
|
17
|
+
//#endregion
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { HOST_LOCALES } from "./contracts.mjs";
|
|
2
|
+
import "./i18n.mjs";
|
|
3
|
+
import * as path$1 from "node:path";
|
|
4
|
+
import * as fs$1 from "node:fs";
|
|
5
|
+
//#region src/i18n-validator.ts
|
|
6
|
+
function extractNestedKeys(obj, prefix = "") {
|
|
7
|
+
if (!obj || typeof obj !== "object") return [];
|
|
8
|
+
const keys = [];
|
|
9
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
10
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
11
|
+
if (value && typeof value === "object" && !Array.isArray(value)) keys.push(...extractNestedKeys(value, fullKey));
|
|
12
|
+
else keys.push(fullKey);
|
|
13
|
+
}
|
|
14
|
+
return keys;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Validates that an addon's `locales/` directory contains valid translation files for all 20 host
|
|
18
|
+
* languages with 100% key parity against `en.json`.
|
|
19
|
+
*
|
|
20
|
+
* @param dirPath - Path to the addon's locales directory.
|
|
21
|
+
* @param domainName - Optional label for error messages (defaults to "Addon").
|
|
22
|
+
*/
|
|
23
|
+
function validateAddonLocales(dirPath, domainName = "Addon") {
|
|
24
|
+
const errors = [];
|
|
25
|
+
const missingFiles = [];
|
|
26
|
+
const missingKeys = {};
|
|
27
|
+
const extraKeys = {};
|
|
28
|
+
const enPath = path$1.join(dirPath, "en.json");
|
|
29
|
+
if (!fs$1.existsSync(enPath)) {
|
|
30
|
+
const errorMsg = `[${domainName}] Missing base locale file: en.json`;
|
|
31
|
+
errors.push(errorMsg);
|
|
32
|
+
missingFiles.push("en.json");
|
|
33
|
+
return {
|
|
34
|
+
valid: false,
|
|
35
|
+
errors,
|
|
36
|
+
missingFiles,
|
|
37
|
+
missingKeys,
|
|
38
|
+
extraKeys
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
let enContent;
|
|
42
|
+
try {
|
|
43
|
+
enContent = JSON.parse(fs$1.readFileSync(enPath, "utf-8"));
|
|
44
|
+
} catch (err) {
|
|
45
|
+
const errorMsg = `[${domainName}] Invalid JSON in en.json: ${err instanceof Error ? err.message : String(err)}`;
|
|
46
|
+
errors.push(errorMsg);
|
|
47
|
+
return {
|
|
48
|
+
valid: false,
|
|
49
|
+
errors,
|
|
50
|
+
missingFiles,
|
|
51
|
+
missingKeys,
|
|
52
|
+
extraKeys
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const enKeys = new Set(extractNestedKeys(enContent));
|
|
56
|
+
for (const lang of HOST_LOCALES) {
|
|
57
|
+
if (lang === "en") continue;
|
|
58
|
+
const langFileName = `${lang}.json`;
|
|
59
|
+
const langPath = path$1.join(dirPath, langFileName);
|
|
60
|
+
if (!fs$1.existsSync(langPath)) {
|
|
61
|
+
errors.push(`[${domainName}] Missing locale file: ${langFileName}`);
|
|
62
|
+
missingFiles.push(langFileName);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const langContent = JSON.parse(fs$1.readFileSync(langPath, "utf-8"));
|
|
67
|
+
const langKeys = new Set(extractNestedKeys(langContent));
|
|
68
|
+
for (const key of enKeys) if (!langKeys.has(key)) {
|
|
69
|
+
errors.push(`[${domainName}] Missing key in ${langFileName}: "${key}"`);
|
|
70
|
+
if (!missingKeys[lang]) missingKeys[lang] = [];
|
|
71
|
+
missingKeys[lang].push(key);
|
|
72
|
+
}
|
|
73
|
+
for (const key of langKeys) if (!enKeys.has(key)) {
|
|
74
|
+
errors.push(`[${domainName}] Extra key in ${langFileName} (not in en.json): "${key}"`);
|
|
75
|
+
if (!extraKeys[lang]) extraKeys[lang] = [];
|
|
76
|
+
extraKeys[lang].push(key);
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
errors.push(`[${domainName}] Invalid JSON in ${langFileName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
valid: errors.length === 0,
|
|
84
|
+
errors,
|
|
85
|
+
missingFiles,
|
|
86
|
+
missingKeys,
|
|
87
|
+
extraKeys
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
export { validateAddonLocales };
|
package/dist/i18n.mjs
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
2
|
+
//#region src/i18n.ts
|
|
3
|
+
function checkIsRtl(code) {
|
|
4
|
+
if (!code) return false;
|
|
5
|
+
const normalized = code.toLowerCase().split("-")[0];
|
|
6
|
+
return normalized === "ar" || normalized === "he" || normalized === "ur";
|
|
7
|
+
}
|
|
8
|
+
function getLanguageDirection(code) {
|
|
9
|
+
return checkIsRtl(code) ? "rtl" : "ltr";
|
|
10
|
+
}
|
|
11
|
+
function interpolate(text, params) {
|
|
12
|
+
if (!params) return text;
|
|
13
|
+
return text.replace(/\{\{\s*(\w+)\s*\}\}|\{\s*(\w+)\s*\}/g, (match, key1, key2) => {
|
|
14
|
+
const key = key1 || key2;
|
|
15
|
+
return key in params ? String(params[key]) : match;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const LocaleContext = createContext({
|
|
19
|
+
locale: "en",
|
|
20
|
+
dir: "ltr"
|
|
21
|
+
});
|
|
22
|
+
/** Resolves a nested key in an object dictionary using dot-notation (e.g. "buttons.submit") */
|
|
23
|
+
function resolveNestedKey(obj, keyPath) {
|
|
24
|
+
if (!obj || typeof obj !== "object") return void 0;
|
|
25
|
+
const segments = keyPath.split(".");
|
|
26
|
+
let current = obj;
|
|
27
|
+
for (const seg of segments) if (current && typeof current === "object" && seg in current) current = current[seg];
|
|
28
|
+
else return;
|
|
29
|
+
return current;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Creates a standalone translation function for backend server routes (`src/api.ts`) or utilities.
|
|
33
|
+
*
|
|
34
|
+
* @param resources - Dictionary of translation resources (e.g., `{ en: enJson, ta: taJson }`).
|
|
35
|
+
* @param defaultLocale - Default locale to use if not specified per call (defaults to "en").
|
|
36
|
+
* @param fallbackLocale - Fallback locale if a key is missing in the target language (defaults to
|
|
37
|
+
* "en").
|
|
38
|
+
*/
|
|
39
|
+
function createTranslator(resources = {}, defaultLocale = "en", fallbackLocale = "en") {
|
|
40
|
+
return (key, paramsOrLang, maybeLang) => {
|
|
41
|
+
let params;
|
|
42
|
+
let lang = defaultLocale;
|
|
43
|
+
if (typeof paramsOrLang === "string") lang = paramsOrLang;
|
|
44
|
+
else if (paramsOrLang && typeof paramsOrLang === "object") {
|
|
45
|
+
params = paramsOrLang;
|
|
46
|
+
if (typeof maybeLang === "string") lang = maybeLang;
|
|
47
|
+
}
|
|
48
|
+
const normalizedLang = (lang || defaultLocale).toLowerCase().split("-")[0];
|
|
49
|
+
const normalizedFallback = fallbackLocale.toLowerCase().split("-")[0];
|
|
50
|
+
const langDict = resources[normalizedLang];
|
|
51
|
+
let resolved = resolveNestedKey(langDict, key);
|
|
52
|
+
if (resolved === void 0 && normalizedLang !== normalizedFallback) {
|
|
53
|
+
const fallbackDict = resources[normalizedFallback];
|
|
54
|
+
resolved = resolveNestedKey(fallbackDict, key);
|
|
55
|
+
}
|
|
56
|
+
if (typeof resolved === "string") return interpolate(resolved, params);
|
|
57
|
+
return typeof resolved === "number" || typeof resolved === "boolean" ? String(resolved) : interpolate(key, params);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Hook to access the current active host locale and text direction in an addon component.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ;```tsx
|
|
65
|
+
* import { useLocale } from "@jchat/addon-sdk"
|
|
66
|
+
*
|
|
67
|
+
* export default function MyAddon() {
|
|
68
|
+
* const { locale, dir, isRtl } = useLocale()
|
|
69
|
+
* return <div dir={dir}>Current language: {locale}</div>
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
function useLocale() {
|
|
74
|
+
const context = useContext(LocaleContext);
|
|
75
|
+
const locale = context?.locale || "en";
|
|
76
|
+
const dir = context?.dir || getLanguageDirection(locale);
|
|
77
|
+
return {
|
|
78
|
+
locale,
|
|
79
|
+
dir,
|
|
80
|
+
isRtl: dir === "rtl" || checkIsRtl(locale)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const loadedChunkCache = {};
|
|
84
|
+
/**
|
|
85
|
+
* Hook to access translation function and locale metadata inside addon components. Automatically
|
|
86
|
+
* synchronizes with host language switches and falls back to English. Supports static dictionary
|
|
87
|
+
* resources or dynamic async locale loaders.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ;```tsx
|
|
91
|
+
* import { useTranslation } from "@jchat/addon-sdk"
|
|
92
|
+
* import locales from "./locales"
|
|
93
|
+
*
|
|
94
|
+
* export default function MyAddon() {
|
|
95
|
+
* const { t, locale, dir } = useTranslation(locales)
|
|
96
|
+
* return (
|
|
97
|
+
* <div dir={dir}>
|
|
98
|
+
* <h1>{t("welcome", { name: "Guest" })}</h1>
|
|
99
|
+
* </div>
|
|
100
|
+
* )
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @param resourcesOrLoader - Addon-scoped translation dictionaries or async chunk loader.
|
|
105
|
+
* @param fallbackLocale - Fallback language code when a string is missing (default: "en").
|
|
106
|
+
*/
|
|
107
|
+
function useTranslation(resourcesOrLoader = {}, fallbackLocale = "en") {
|
|
108
|
+
const { locale, dir, isRtl } = useLocale();
|
|
109
|
+
const isLoader = typeof resourcesOrLoader === "function";
|
|
110
|
+
const [dynamicResources, setDynamicResources] = useState(() => {
|
|
111
|
+
return isLoader ? loadedChunkCache : resourcesOrLoader;
|
|
112
|
+
});
|
|
113
|
+
const [isReady, setIsReady] = useState(!isLoader);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (!isLoader) {
|
|
116
|
+
setDynamicResources(resourcesOrLoader);
|
|
117
|
+
setIsReady(true);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const loader = resourcesOrLoader;
|
|
121
|
+
const normLocale = locale.toLowerCase().split("-")[0];
|
|
122
|
+
if (loadedChunkCache[normLocale]) {
|
|
123
|
+
setDynamicResources((prev) => ({
|
|
124
|
+
...prev,
|
|
125
|
+
[normLocale]: loadedChunkCache[normLocale]
|
|
126
|
+
}));
|
|
127
|
+
setIsReady(true);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
let isMounted = true;
|
|
131
|
+
Promise.resolve(loader(normLocale)).then((dict) => {
|
|
132
|
+
if (!isMounted || !dict) return;
|
|
133
|
+
loadedChunkCache[normLocale] = dict;
|
|
134
|
+
setDynamicResources((prev) => ({
|
|
135
|
+
...prev,
|
|
136
|
+
[normLocale]: dict
|
|
137
|
+
}));
|
|
138
|
+
setIsReady(true);
|
|
139
|
+
}).catch((err) => {
|
|
140
|
+
console.warn(`[addon-sdk] Failed to dynamically load locale chunk "${normLocale}":`, err);
|
|
141
|
+
if (isMounted) setIsReady(true);
|
|
142
|
+
});
|
|
143
|
+
return () => {
|
|
144
|
+
isMounted = false;
|
|
145
|
+
};
|
|
146
|
+
}, [
|
|
147
|
+
isLoader,
|
|
148
|
+
resourcesOrLoader,
|
|
149
|
+
locale
|
|
150
|
+
]);
|
|
151
|
+
const translator = useMemo(() => {
|
|
152
|
+
return createTranslator(dynamicResources, locale, fallbackLocale);
|
|
153
|
+
}, [
|
|
154
|
+
dynamicResources,
|
|
155
|
+
locale,
|
|
156
|
+
fallbackLocale
|
|
157
|
+
]);
|
|
158
|
+
return {
|
|
159
|
+
t: useMemo(() => {
|
|
160
|
+
return (key, params) => translator(key, params, locale);
|
|
161
|
+
}, [translator, locale]),
|
|
162
|
+
locale,
|
|
163
|
+
dir,
|
|
164
|
+
isRtl,
|
|
165
|
+
isReady
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
export { useTranslation as i, createTranslator as n, useLocale as r, LocaleContext as t };
|
package/dist/icon.d.mts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { i as IconPack, n as IconComponent, r as IconName, t as ICON_NAMES } from "./types.mjs";
|
|
2
|
+
import { a as getIconPack, c as lucidePack, i as fa6Pack, l as phosphorPack, n as Icons, o as heroPack, r as Youtube, s as isIconName, t as IconPackName, u as resolveIcon } from "./index.mjs";
|
|
3
|
+
export * from "lucide-react";
|
|
4
|
+
export { ICON_NAMES, type IconComponent, type IconName, type IconPack, IconPackName, Icons, Icons as default, Youtube, fa6Pack, getIconPack, heroPack, isIconName, lucidePack, phosphorPack, resolveIcon };
|
package/dist/icon.mjs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { i as IconPack, n as IconComponent, r as IconName, t as ICON_NAMES } from "./types.d.mts";
|
|
2
|
+
import { a as getIconPack, c as lucidePack, i as fa6Pack, l as phosphorPack, n as Icons, o as heroPack, r as Youtube, s as isIconName, t as IconPackName, u as resolveIcon } from "./index.d.mts";
|
|
3
|
+
export * from "lucide-react";
|
|
4
|
+
export { ICON_NAMES, IconComponent, IconName, IconPack, IconPackName, Icons, Icons as default, Youtube, fa6Pack, getIconPack, heroPack, isIconName, lucidePack, phosphorPack, resolveIcon };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,271 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
type IconComponent = ComponentType<SVGProps<SVGSVGElement>>;
|
|
7
|
-
declare const ICON_NAMES: readonly ["ProfileMen", "ProfileWomen", "Stories", "Search", "AdminPanel", "Investigation", "Logout", "Users", "Settings", "RoomList", "AdminShip", "Announcement", "NewsFeed", "Ranks", "TopPoints", "Like", "DisLike", "Love", "Funny", "Comment", "Chat", "Brush", "Image", "Mic", "More", "Palette", "Smiley", "Send", "Private", "Action", "View", "Female", "Male", "LuSend", "Heart", "Trash", "HorizontalOption", "Menu", "UserGroup", "Globe", "Plus", "Loader", "X", "UserRoundPlus", "Footprints", "Calendar", "Volume", "Youtube", "Upload", "Play", "Pause", "Stop", "Ok", "ImageFill", "MicFill", "YoutubeFill", "PlusFill", "Music", "Gift", "Camera", "Envelope", "Key", "Signature", "UserPen", "Edit", "AddressCard", "Star", "StatusOnline", "StatusAway", "StatusBusy", "StatusEating", "StatusGaming", "StatusSinging", "StatusListening", "StatusStay"];
|
|
8
|
-
type IconName = (typeof ICON_NAMES)[number];
|
|
9
|
-
type IconPack = Record<IconName, IconComponent>;
|
|
1
|
+
import { i as IconPack, n as IconComponent, r as IconName, t as ICON_NAMES } from "./types.mjs";
|
|
2
|
+
//#region ../icon/dist/index.d.mts
|
|
3
|
+
//#region src/packs/lucide.d.ts
|
|
4
|
+
declare const Youtube: IconComponent;
|
|
5
|
+
declare const lucidePack: IconPack;
|
|
10
6
|
//#endregion
|
|
11
7
|
//#region src/index.d.ts
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* schema, and host API access. This package is always EXTERNALISED from addon bundles — the host
|
|
18
|
-
* app provides the runtime implementation.
|
|
19
|
-
*/
|
|
20
|
-
/** Minimal user profile accessible to addon backends. */
|
|
21
|
-
interface AddonUser {
|
|
22
|
-
/** Unique user identifier */
|
|
23
|
-
id: string;
|
|
24
|
-
/** Display name */
|
|
25
|
-
name: string;
|
|
26
|
-
/** Full display name */
|
|
27
|
-
displayName: string;
|
|
28
|
-
/** Full URL to the user's avatar image */
|
|
29
|
-
avatar: string;
|
|
30
|
-
/** User's gender identifier */
|
|
31
|
-
gender: string;
|
|
32
|
-
}
|
|
33
|
-
interface AddonRequestUser {
|
|
34
|
-
id?: string;
|
|
35
|
-
permissions?: string[];
|
|
36
|
-
siteId?: string;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Typed, schema-scoped database facade for an addon. The host deliberately omits raw execution,
|
|
40
|
-
* schema switching, dialect/connection access, and lifecycle methods.
|
|
41
|
-
*/
|
|
42
|
-
type AddonDatabase<TDatabase = Record<string, Record<string, unknown>>> = Pick<Kysely<TDatabase>, "deleteFrom" | "fn" | "insertInto" | "selectFrom" | "transaction" | "updateTable">;
|
|
43
|
-
/**
|
|
44
|
-
* Context provided to an addon's server-side factory. Parameterize it with the addon's table
|
|
45
|
-
* interface: `AddonServerContext<MyAddonDatabase>`.
|
|
46
|
-
*/
|
|
47
|
-
interface AddonServerContext<TDatabase = Record<string, Record<string, unknown>>> {
|
|
48
|
-
/**
|
|
49
|
-
* Retrieve the encrypted settings for this addon for a specific site. Internal secrets are
|
|
50
|
-
* automatically decrypted using the host's master key.
|
|
51
|
-
*/
|
|
52
|
-
getConfig: (siteId: string) => Promise<Record<string, unknown>>;
|
|
53
|
-
/** Fetch a specific user's public profile */
|
|
54
|
-
getUser: (userId: string) => Promise<AddonUser | null>;
|
|
55
|
-
/** Efficiently fetch multiple user profiles in a single query. Returns a Map keyed by user ID. */
|
|
56
|
-
getUsers: (userIds: string[]) => Promise<Map<string, AddonUser>>;
|
|
57
|
-
/**
|
|
58
|
-
* Check whether the request user holds a given permission. Provides autocomplete for core Jchat
|
|
59
|
-
* permissions while also accepting any custom addon permission string.
|
|
60
|
-
*
|
|
61
|
-
* @param user - The user object from the request context
|
|
62
|
-
* @param permission - The permission to check, e.g., 'message.send' or 'addon.stories.view'
|
|
63
|
-
*/
|
|
64
|
-
hasPermission: (user: AddonRequestUser | null | undefined, permission: AddonPermission) => boolean;
|
|
65
|
-
/**
|
|
66
|
-
* A Kysely database instance scoped entirely to your addon's tables. You cannot access core Jchat
|
|
67
|
-
* tables (Users, Rooms, etc.) through this instance.
|
|
68
|
-
*/
|
|
69
|
-
db: AddonDatabase<TDatabase>;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* The entry point for your addon's server bundle. You should default-export a function matching
|
|
73
|
-
* this signature.
|
|
74
|
-
*
|
|
75
|
-
* @example
|
|
76
|
-
* ;```ts
|
|
77
|
-
* import { type AddonServerFactory } from '@jchat/addon-sdk'
|
|
78
|
-
*
|
|
79
|
-
* const factory: AddonServerFactory = ({ db, hasPermission }, app) => {
|
|
80
|
-
* return app.get('/trending', async () => { ... })
|
|
81
|
-
* }
|
|
82
|
-
* export default factory
|
|
83
|
-
* ```
|
|
84
|
-
*/
|
|
85
|
-
type AddonServerFactory<TDatabase = Record<string, Record<string, unknown>>> = (ctx: AddonServerContext<TDatabase>, app: Elysia) => Elysia;
|
|
86
|
-
/** Props passed to your addon's root React component by the Jchat host. */
|
|
87
|
-
interface AddonProps {
|
|
88
|
-
/** The mount point where this addon instance is rendered (e.g., 'chat-sidebar') */
|
|
89
|
-
mountPoint: MountPoint;
|
|
90
|
-
/** Information about the currently logged-in user (if any) */
|
|
91
|
-
user?: {
|
|
92
|
-
id: string;
|
|
93
|
-
name: string;
|
|
94
|
-
displayName?: string;
|
|
95
|
-
avatar?: string;
|
|
96
|
-
gender?: string;
|
|
97
|
-
};
|
|
98
|
-
/** The ID of the room the user is currently viewing (if applicable) */
|
|
99
|
-
roomId?: string;
|
|
100
|
-
/** The ID of the active Jchat site */
|
|
101
|
-
siteId?: string;
|
|
102
|
-
}
|
|
103
|
-
interface AddonApi {
|
|
104
|
-
get: <TResponse = unknown>(path: string) => Promise<TResponse>;
|
|
105
|
-
post: <TResponse = unknown>(path: string, body?: AddonJsonBody) => Promise<TResponse>;
|
|
106
|
-
put: <TResponse = unknown>(path: string, body?: AddonJsonBody) => Promise<TResponse>;
|
|
107
|
-
delete: <TResponse = unknown>(path: string) => Promise<TResponse>;
|
|
108
|
-
}
|
|
109
|
-
type AddonEventListener = (data: unknown) => void;
|
|
110
|
-
/** A font option provided by the JChat host. */
|
|
111
|
-
interface AddonFont {
|
|
112
|
-
/** Display label shown to users (e.g. 'Modern', 'Elegant') */
|
|
113
|
-
label: string;
|
|
114
|
-
/** Font identifier stored in the database (e.g. 'poppins', 'lato') */
|
|
115
|
-
value: string;
|
|
116
|
-
/** CSS font-family string ready for use in inline styles */
|
|
117
|
-
family: string;
|
|
118
|
-
/** Google Fonts name for loading the font via a <link> tag */
|
|
119
|
-
googleName: string;
|
|
120
|
-
/** Relative size multiplier — apply this to base font sizes to match the host's rendering */
|
|
121
|
-
scale: number;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* The bridge between your addon and the Jchat host application. Use the `useAddonSDK()` hook to
|
|
125
|
-
* access this interface in your components.
|
|
126
|
-
*/
|
|
127
|
-
interface AddonSDK {
|
|
128
|
-
/**
|
|
129
|
-
* Make authenticated API calls to your addon's backend or core Jchat endpoints. All calls
|
|
130
|
-
* automatically include the current user's sessionId and site validation headers.
|
|
131
|
-
*/
|
|
132
|
-
api: AddonApi;
|
|
133
|
-
/**
|
|
134
|
-
* Display a temporary notification toast in the host UI.
|
|
135
|
-
*
|
|
136
|
-
* @param message - The text content to display
|
|
137
|
-
* @param variant - Visual style: 'default' (blue), 'success' (green), or 'error' (red)
|
|
138
|
-
*/
|
|
139
|
-
toast: (message: string, variant?: "default" | "success" | "error") => void;
|
|
140
|
-
/**
|
|
141
|
-
* Navigate the host application to a different internal route.
|
|
142
|
-
*
|
|
143
|
-
* @param path - The target location, e.g., '/settings' or '/addons/store'
|
|
144
|
-
*/
|
|
145
|
-
navigate: (path: string) => void;
|
|
146
|
-
/**
|
|
147
|
-
* Interaction utilities for the active Slate message editor. Only available if your addon is
|
|
148
|
-
* mounted near the chat input.
|
|
149
|
-
*/
|
|
150
|
-
editor: {
|
|
151
|
-
/**
|
|
152
|
-
* Trigger a specialized editor command defined by the host.
|
|
153
|
-
*
|
|
154
|
-
* @example
|
|
155
|
-
* sdk.editor.dispatchCommand("INSERT_YOUTUBE_COMMAND", "dQw4w9WgXcQ")
|
|
156
|
-
*/
|
|
157
|
-
dispatchCommand: (command: string, payload?: unknown) => void;
|
|
158
|
-
/**
|
|
159
|
-
* Insert a low-level Slate node directly at the user's cursor position.
|
|
160
|
-
*
|
|
161
|
-
* @param node - A valid Slate Descendant or Fragment object
|
|
162
|
-
*/
|
|
163
|
-
insertNode: (node: unknown) => void;
|
|
164
|
-
/**
|
|
165
|
-
* Upload and attach a file to the current message without inserting it inline. Users will see
|
|
166
|
-
* the file in the attachment bar below the editor.
|
|
167
|
-
*/
|
|
168
|
-
addAttachment: (attachment: {
|
|
169
|
-
url: string;
|
|
170
|
-
name: string;
|
|
171
|
-
type: string;
|
|
172
|
-
}) => void;
|
|
173
|
-
/** Force the browser focus back to the editor input */
|
|
174
|
-
focus: () => void;
|
|
175
|
-
};
|
|
176
|
-
/** Event Bus for reacting to real-time application state changes. */
|
|
177
|
-
events: {
|
|
178
|
-
/**
|
|
179
|
-
* Register a persistent listener for a specific event.
|
|
180
|
-
*
|
|
181
|
-
* @param event - Event name, e.g., 'room.message', 'user.typing'
|
|
182
|
-
*/
|
|
183
|
-
on: (event: string, callback: AddonEventListener) => void;
|
|
184
|
-
/** Unregister a previously attached listener */
|
|
185
|
-
off: (event: string, callback: AddonEventListener) => void;
|
|
186
|
-
};
|
|
187
|
-
/**
|
|
188
|
-
* The list of fonts available in this JChat deployment, as configured by the site administrator.
|
|
189
|
-
* Use this to build a font picker or apply a matching font family in your addon UI.
|
|
190
|
-
*/
|
|
191
|
-
supportedFonts?: AddonFont[];
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Seed data for your addon's tables, provided inside the addon's isolated schema.
|
|
195
|
-
*
|
|
196
|
-
* These documents are inserted into the addon's dedicated schema on installation and can be used to
|
|
197
|
-
* provide default values, references, or supporting data. The host guarantees that these documents
|
|
198
|
-
* do not conflict with core schemas or other addons.
|
|
199
|
-
*/
|
|
200
|
-
interface AddonSeedDocument {
|
|
201
|
-
/** Addon-owned table name. Core prefixes the addon's isolated PostgreSQL schema. */
|
|
202
|
-
table: string;
|
|
203
|
-
/** Behavior when a row conflicts with an existing unique or primary key. */
|
|
204
|
-
conflict?: "error" | "ignore" | "update";
|
|
205
|
-
/** Required for update behavior; identifies the unique key used by ON CONFLICT. */
|
|
206
|
-
conflictColumns?: string[];
|
|
207
|
-
/** JSON-compatible rows inserted using parameterized values. */
|
|
208
|
-
rows: Record<string, unknown>[];
|
|
209
|
-
}
|
|
210
|
-
/** Define a manifest with full validation-friendly typing while preserving literal values. */
|
|
211
|
-
declare function defineAddonManifest<const TManifest extends AddonManifest>(manifest: TManifest): TManifest;
|
|
212
|
-
/** Define a typed server factory without widening its addon database interface. */
|
|
213
|
-
declare function defineAddonServer<TDatabase = Record<string, Record<string, unknown>>>(factory: AddonServerFactory<TDatabase>): AddonServerFactory<TDatabase>;
|
|
214
|
-
/**
|
|
215
|
-
* Access the host application's SDK. Provides authenticated API access, toast notifications, and
|
|
216
|
-
* navigation.
|
|
217
|
-
*
|
|
218
|
-
* This function is a shell; the real implementation is provided by the Jchat host environment at
|
|
219
|
-
* runtime.
|
|
220
|
-
*
|
|
221
|
-
* @example
|
|
222
|
-
* ;```tsx
|
|
223
|
-
* import { useAddonSDK } from "@jchat/addon-sdk"
|
|
224
|
-
*
|
|
225
|
-
* export default function MyAddon({ mountPoint }: AddonProps) {
|
|
226
|
-
* const sdk = useAddonSDK()
|
|
227
|
-
*
|
|
228
|
-
* const handleClick = async () => {
|
|
229
|
-
* const data = await sdk.api.get("/trending")
|
|
230
|
-
* sdk.toast("Fetched trending posts!", "success")
|
|
231
|
-
* }
|
|
232
|
-
* }
|
|
233
|
-
* ```
|
|
234
|
-
*/
|
|
235
|
-
declare function useAddonSDK(): AddonSDK;
|
|
236
|
-
/**
|
|
237
|
-
* Access the host application's active icon pack. Returns the icon set matching the site's
|
|
238
|
-
* configured icon pack preference.
|
|
239
|
-
*
|
|
240
|
-
* This function is a shell; the real implementation is provided by the Jchat host environment at
|
|
241
|
-
* runtime.
|
|
242
|
-
*
|
|
243
|
-
* @example
|
|
244
|
-
* ;```tsx
|
|
245
|
-
* import { useIcons } from "@jchat/addon-sdk"
|
|
246
|
-
*
|
|
247
|
-
* export default function MyAddon() {
|
|
248
|
-
* const { Icons } = useIcons()
|
|
249
|
-
* return <Icons.Send className="size-5" />
|
|
250
|
-
* }
|
|
251
|
-
* ```
|
|
252
|
-
*/
|
|
253
|
-
declare function useIcons(): {
|
|
254
|
-
Icons: IconPack;
|
|
8
|
+
declare const fa6Pack: IconPack;
|
|
9
|
+
declare const heroPack: IconPack;
|
|
10
|
+
declare const phosphorPack: IconPack;
|
|
11
|
+
declare const IconPackName: {
|
|
12
|
+
readonly LUCIDE: "lucide";
|
|
255
13
|
};
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
* ;```tsx
|
|
262
|
-
* import { getAddonSdkVersion } from "@jchat/addon-sdk"
|
|
263
|
-
*
|
|
264
|
-
* console.log(getAddonSdkVersion())
|
|
265
|
-
* ```
|
|
266
|
-
*
|
|
267
|
-
* @returns
|
|
268
|
-
*/
|
|
269
|
-
declare function getAddonSdkVersion(): string;
|
|
14
|
+
type IconPackName = (typeof IconPackName)[keyof typeof IconPackName];
|
|
15
|
+
declare const getIconPack: (_name?: string) => IconPack;
|
|
16
|
+
declare const isIconName: (value: string) => value is IconName;
|
|
17
|
+
declare function resolveIcon(packOrName?: IconPack | string, nameOrFallback?: string | IconName, fallback?: IconName): IconComponent;
|
|
18
|
+
declare const Icons: IconPack;
|
|
270
19
|
//#endregion
|
|
271
|
-
export {
|
|
20
|
+
export { getIconPack as a, lucidePack as c, fa6Pack as i, phosphorPack as l, Icons as n, heroPack as o, Youtube as r, isIconName as s, IconPackName as t, resolveIcon as u };
|