adminizer 5.0.0-build.8 → 5.1.0-build.24

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.
Files changed (232) hide show
  1. package/assets/abnfDiagram-VCTEODGH-DFY3Pztx.js +1 -0
  2. package/assets/add-31tS-g4V.js +1 -0
  3. package/assets/add-group-Cf3zT7WE.js +1 -0
  4. package/assets/add-user-C62wOna-.js +1 -0
  5. package/assets/ai-assistant/agent.es.js +28464 -0
  6. package/assets/app-DPmI-MvN.css +1 -0
  7. package/assets/app-vdHbjOQX.js +138 -0
  8. package/assets/arc-DP4o1zz3.js +1 -0
  9. package/assets/architectureDiagram-5GKGNRK7-BjXW5Zu0.js +36 -0
  10. package/assets/asmcrypto-DZeQH0LC.js +1 -0
  11. package/assets/blockDiagram-I7D4REHJ-BUPnDwN0.js +129 -0
  12. package/assets/c4Diagram-7LVT6UL2-Cmb_dAhd.js +38 -0
  13. package/assets/{catalog-BEvJZYJP.js → catalog-C-y5rMaG.js} +3 -3
  14. package/assets/channel-CkJRUsq_.js +1 -0
  15. package/assets/chunk-2Q5K7J3B-CmS-dWwQ.js +1 -0
  16. package/assets/chunk-5VM5RSS4-Cqs2Ow64.js +15 -0
  17. package/assets/chunk-F27PBJKO-BoMCpJA1.js +1 -0
  18. package/assets/chunk-IMKFNOWR-DlDcPk1x.js +231 -0
  19. package/assets/chunk-JWPE2WC7-DzL_SU8s.js +1 -0
  20. package/assets/chunk-POPQ4Y6H-Cwq2-Vb8.js +1 -0
  21. package/assets/chunk-SVP7TREG-BfRHY5FU.js +88 -0
  22. package/assets/chunk-TICWLB2K-CRP7rHkW.js +206 -0
  23. package/assets/chunk-XXDRQBXY-Bg2jg-98.js +1 -0
  24. package/assets/classDiagram-ZZMXUADV-D4EF3HGj.js +1 -0
  25. package/assets/classDiagram-v2-VYDZK3BY-D4EF3HGj.js +1 -0
  26. package/assets/controls/ckeditor.es.js +9382 -9320
  27. package/assets/controls/handsontable.es.js +11971 -11938
  28. package/assets/controls/jsoneditor.es.js +10442 -10233
  29. package/assets/controls/toast-ui.es.js +1308 -1279
  30. package/assets/cose-bilkent-JH36ORCC-CCJ0SGyO.js +1 -0
  31. package/assets/cynefin-OW5HDTMX-B2Jx5fw3.js +166 -0
  32. package/assets/cynefinDiagram-5FMLGOSQ-DuK0Lb62.js +62 -0
  33. package/assets/cytoscape.esm-DzSz-X2X.js +321 -0
  34. package/assets/dagre-GXQ25YYZ-DnOYexET.js +4 -0
  35. package/assets/dashboard-X8k4X6Sw.js +3 -0
  36. package/assets/debounce-B3mXI_sx.js +1 -0
  37. package/assets/defaultLocale-DX6XiGOO.js +1 -0
  38. package/assets/diagram-S7CK7UJ4-DDgPBHsK.js +30 -0
  39. package/assets/diagram-UQ7AKVKN-qlL0GZs8.js +41 -0
  40. package/assets/diagram-VSXAHHWV-CQbiU1_c.js +3 -0
  41. package/assets/diagram-VX7I27RA-DFDI9_au.js +24 -0
  42. package/assets/diagram-Z3DM3KII-B5C9dtmJ.js +24 -0
  43. package/assets/docs-BiZOD7PP.js +1 -0
  44. package/assets/ebnfDiagram-PWID7BFC-CLMUsXEI.js +1 -0
  45. package/assets/erDiagram-RLTQ6QDP-ax9Xol5-.js +99 -0
  46. package/assets/flowDiagram-HODETNUW-CvaH7l3S.js +168 -0
  47. package/assets/ganttDiagram-EL5Y4UJY-Cga5U19j.js +292 -0
  48. package/assets/gitGraphDiagram-WWUBYQGX-B6o1T3-d.js +106 -0
  49. package/assets/history-CVBJUNSQ.js +1 -0
  50. package/assets/infoDiagram-27XIBGKW-BpWxgBCE.js +2 -0
  51. package/assets/init-Gi6I4Gst.js +1 -0
  52. package/assets/init-user-xSvnFTF0.js +1 -0
  53. package/assets/isObject-DL-BuaBl.js +1 -0
  54. package/assets/ishikawaDiagram-5VMMS53U-zE83db4H.js +70 -0
  55. package/assets/journeyDiagram-3NMN7TZE-DZNVFqrX.js +139 -0
  56. package/assets/kanban-definition-UXKFOSKX-CqxNgk27.js +89 -0
  57. package/assets/katex-HP8lGamR.js +257 -0
  58. package/assets/layout-CN3kt_jt.js +1 -0
  59. package/assets/linear-nvkaQ6BA.js +1 -0
  60. package/assets/{list-DhK0McVn.js → list-CrIU2eZz.js} +2 -2
  61. package/assets/login-CX9xVjCn.js +1 -0
  62. package/assets/manifest.json +834 -58
  63. package/assets/media-manager-BfYftLIH.js +6 -0
  64. package/assets/mermaid.core-C0V1NaVJ.js +310 -0
  65. package/assets/mindmap-definition-YA3MSWOX-Bh7_d9f1.js +96 -0
  66. package/assets/module-DPEvnad3.js +1 -0
  67. package/assets/notification-Dqpt5AIZ.js +20 -0
  68. package/assets/ordinal-Cboi1Yqb.js +1 -0
  69. package/assets/pegDiagram-XKGWAZYB-DMnjm6Z1.js +1 -0
  70. package/assets/pieDiagram-E7YTZNPT-D-RWCgYb.js +39 -0
  71. package/assets/quadrantDiagram-AXDQQJYC-BNBGP7RM.js +7 -0
  72. package/assets/railroadDiagram-O6MQD6OU-BIjVPT01.js +1 -0
  73. package/assets/register-CF4KQ97e.js +1 -0
  74. package/assets/requirementDiagram-BXWQKSXE-VPrtGdjM.js +84 -0
  75. package/assets/ru-CFJ4fysR.js +1 -0
  76. package/assets/sankeyDiagram-P5KCCOFB-ct9mLF3U.js +40 -0
  77. package/assets/sequenceDiagram-WJ2MYXX4-TC06Mbmw.js +162 -0
  78. package/assets/sizeCapture-INFHLROL-BMMwriCf.js +1 -0
  79. package/assets/stateDiagram-D77RDMKH-DtwYRPCX.js +1 -0
  80. package/assets/stateDiagram-v2-MP3YSRHH-C1yB0O5U.js +1 -0
  81. package/assets/swimlanes-42K2YHIH-DE3hRbbr.js +1 -0
  82. package/assets/swimlanesDiagram-VR7AAH4N-Ce4l1sQD.js +8 -0
  83. package/assets/timeline-definition-24CTP7MA-N7s_hfjj.js +120 -0
  84. package/assets/toNumber-DZb-r9sy.js +1 -0
  85. package/assets/use-appearance-DYTIKpT2.js +1 -0
  86. package/assets/user-filters-list-fR0YNJ3l.js +1 -0
  87. package/assets/vennDiagram-4TSXK5OY-Dj5bFQkR.js +34 -0
  88. package/assets/wardleyDiagram-VM6X3IG4-83ax25Z6.js +78 -0
  89. package/assets/welcome-v11MTsRK.js +1 -0
  90. package/assets/with-app-layout-CH6RIib1.js +31 -0
  91. package/assets/xychartDiagram-S5SC5T6Z-CW9Br5Xp.js +7 -0
  92. package/controllers/access-rights/permissionOptions.d.ts +2 -0
  93. package/controllers/access-rights/permissionOptions.js +23 -0
  94. package/controllers/add.js +2 -2
  95. package/controllers/addGroup.js +15 -6
  96. package/controllers/ai/AiAgentController.d.ts +66 -0
  97. package/controllers/ai/AiAgentController.js +435 -0
  98. package/controllers/ai/AiAssistantController.d.ts +0 -4
  99. package/controllers/ai/AiAssistantController.js +4 -78
  100. package/controllers/catalog/FrontendCatalogAdapter.js +7 -8
  101. package/controllers/docs/index.d.ts +22 -0
  102. package/controllers/docs/index.js +126 -0
  103. package/controllers/edit.js +2 -2
  104. package/controllers/editGroup.js +15 -6
  105. package/controllers/filter-fields/savedFilters.js +1 -1
  106. package/controllers/history-actions/HistoryController.js +7 -7
  107. package/controllers/inlineUpdate.js +10 -3
  108. package/controllers/list.js +1 -1
  109. package/controllers/login.js +1 -1
  110. package/controllers/notifications/NotificationController.js +14 -8
  111. package/controllers/view.js +3 -3
  112. package/controllers/widgets/filterInfo.js +1 -1
  113. package/helpers/accessRightsHelper.d.ts +30 -3
  114. package/helpers/accessRightsHelper.js +140 -12
  115. package/helpers/assetVersionHelper.d.ts +14 -0
  116. package/helpers/assetVersionHelper.js +42 -0
  117. package/helpers/configHelper.js +19 -18
  118. package/helpers/controllerHelper.d.ts +1 -0
  119. package/helpers/controllerHelper.js +25 -15
  120. package/helpers/fieldsHelper.d.ts +2 -1
  121. package/helpers/fieldsHelper.js +11 -5
  122. package/helpers/inertiaActionsHelper.d.ts +1 -1
  123. package/helpers/inertiaActionsHelper.js +4 -5
  124. package/helpers/inertiaAddHelper.d.ts +1 -1
  125. package/helpers/inertiaAddHelper.js +7 -6
  126. package/helpers/inertiaGroupHelper.js +10 -2
  127. package/helpers/inertiaListHelper.d.ts +1 -1
  128. package/helpers/inertiaListHelper.js +7 -8
  129. package/helpers/inertiaMenuHelper.d.ts +7 -2
  130. package/helpers/inertiaMenuHelper.js +12 -27
  131. package/helpers/menuHelper.d.ts +11 -1
  132. package/helpers/menuHelper.js +12 -0
  133. package/helpers/modelResourceHelper.d.ts +9 -0
  134. package/helpers/modelResourceHelper.js +43 -0
  135. package/helpers/navigationAccessHelper.d.ts +13 -0
  136. package/helpers/navigationAccessHelper.js +45 -0
  137. package/index.d.ts +11 -0
  138. package/index.js +11 -0
  139. package/interfaces/adminpanelConfig.d.ts +160 -2
  140. package/interfaces/queryCriteria.d.ts +13 -0
  141. package/interfaces/types.d.ts +169 -0
  142. package/lib/Adminizer.d.ts +8 -0
  143. package/lib/Adminizer.js +29 -2
  144. package/lib/DataAccessor.d.ts +100 -3
  145. package/lib/DataAccessor.js +454 -245
  146. package/lib/access-graph/AccessGraphResolver.d.ts +136 -0
  147. package/lib/access-graph/AccessGraphResolver.js +605 -0
  148. package/lib/access-graph/RecordAccessCache.d.ts +23 -0
  149. package/lib/access-graph/RecordAccessCache.js +30 -0
  150. package/lib/access-graph/RecordAccessResolver.d.ts +83 -0
  151. package/lib/access-graph/RecordAccessResolver.js +295 -0
  152. package/lib/access-graph/shared.d.ts +60 -0
  153. package/lib/access-graph/shared.js +121 -0
  154. package/lib/admin-links/AdminLinkHandler.d.ts +72 -0
  155. package/lib/admin-links/AdminLinkHandler.js +183 -0
  156. package/lib/ai-assistant/AbstractAiConversationHistoryService.d.ts +39 -0
  157. package/lib/ai-assistant/AbstractAiConversationHistoryService.js +98 -0
  158. package/lib/ai-assistant/AbstractAiModelService.d.ts +139 -2
  159. package/lib/ai-assistant/AbstractAiModelService.js +154 -1
  160. package/lib/ai-assistant/AiAgentRunManager.d.ts +35 -0
  161. package/lib/ai-assistant/AiAgentRunManager.js +109 -0
  162. package/lib/ai-assistant/AiAssistantAgentSkillHandler.d.ts +61 -0
  163. package/lib/ai-assistant/AiAssistantAgentSkillHandler.js +90 -0
  164. package/lib/ai-assistant/AiAssistantHandler.d.ts +1 -0
  165. package/lib/ai-assistant/AiAssistantHandler.js +4 -0
  166. package/lib/ai-assistant/AiAssistantUiMethodHandler.d.ts +60 -0
  167. package/lib/ai-assistant/AiAssistantUiMethodHandler.js +144 -0
  168. package/lib/ai-assistant/builtinAgentSkills.d.ts +9 -0
  169. package/lib/ai-assistant/builtinAgentSkills.js +259 -0
  170. package/lib/ai-assistant/jsonSafe.d.ts +13 -0
  171. package/lib/ai-assistant/jsonSafe.js +81 -0
  172. package/lib/app-manager/AdminizerApp.d.ts +31 -2
  173. package/lib/app-manager/AppManager.js +43 -42
  174. package/lib/app-manager/ConfigLayerHandler.js +4 -0
  175. package/lib/docs/AbstractDocumentation.d.ts +100 -0
  176. package/lib/docs/AbstractDocumentation.js +108 -0
  177. package/lib/docs/DocumentationHandler.d.ts +100 -0
  178. package/lib/docs/DocumentationHandler.js +198 -0
  179. package/lib/docs/FileDocumentation.d.ts +47 -0
  180. package/lib/docs/FileDocumentation.js +189 -0
  181. package/lib/docs/docsAgentSkills.d.ts +10 -0
  182. package/lib/docs/docsAgentSkills.js +93 -0
  183. package/lib/filters/FilterService.js +4 -3
  184. package/lib/history-actions/AbstractHistoryAdapter.d.ts +26 -1
  185. package/lib/history-actions/AbstractHistoryAdapter.js +155 -25
  186. package/lib/media-manager/helpers/MediaManagerHelper.js +9 -7
  187. package/lib/model/AbstractModel.d.ts +48 -0
  188. package/lib/model/AbstractModel.js +14 -4
  189. package/lib/model/ModelHandler.d.ts +50 -2
  190. package/lib/model/ModelHandler.js +117 -8
  191. package/lib/model/adapter/sequelize.d.ts +43 -3
  192. package/lib/model/adapter/sequelize.js +317 -138
  193. package/lib/model/adapter/typeorm.d.ts +16 -0
  194. package/lib/model/adapter/typeorm.js +136 -9
  195. package/lib/notifications/GeneralNotificationService.js +6 -5
  196. package/lib/notifications/SystemNotificationService.js +1 -1
  197. package/lib/ui-i18n/uiTranslationKeys.d.ts +2 -1
  198. package/lib/ui-i18n/uiTranslationKeys.js +42 -1
  199. package/lib/widgets/widgetHandler.js +10 -6
  200. package/models/Group.d.ts +2 -1
  201. package/package.json +35 -23
  202. package/policies/authPolicies.js +6 -6
  203. package/policies/permissionResolvers.d.ts +1 -0
  204. package/policies/permissionResolvers.js +3 -0
  205. package/system/Router.js +21 -3
  206. package/system/bindAccessRights.js +13 -3
  207. package/system/bindDocs.d.ts +13 -0
  208. package/system/bindDocs.js +77 -0
  209. package/system/bindInertia.js +30 -7
  210. package/system/buildInternalModelAccess.d.ts +8 -0
  211. package/system/buildInternalModelAccess.js +36 -4
  212. package/system/defaults.js +7 -1
  213. package/system/validateSystemModels.js +29 -9
  214. package/assets/add-DED_wpga.js +0 -1
  215. package/assets/add-group-CL80RdYb.js +0 -1
  216. package/assets/add-user-BRmwvQB8.js +0 -1
  217. package/assets/app-DNXiApCk.css +0 -1
  218. package/assets/app-layout-86yxiZOy.js +0 -1
  219. package/assets/app.js +0 -138
  220. package/assets/dashboard-ZP6K2s_8.js +0 -3
  221. package/assets/debounce-BMmXVQ06.js +0 -1
  222. package/assets/history-M61Bcv02.js +0 -1
  223. package/assets/init-user-8aUohVDq.js +0 -1
  224. package/assets/login-CO4laf6m.js +0 -1
  225. package/assets/media-manager-BDbpSSjH.js +0 -6
  226. package/assets/module-DfBneaUl.js +0 -1
  227. package/assets/notification-CVnQf3ef.js +0 -20
  228. package/assets/register-DBYIMXGQ.js +0 -1
  229. package/assets/ru-rYKdQbWF.js +0 -1
  230. package/assets/use-appearance-_EbbjHTh.js +0 -1
  231. package/assets/user-filters-list-DXhGLFTn.js +0 -1
  232. package/assets/welcome-CLxvXkcg.js +0 -1
@@ -0,0 +1,100 @@
1
+ import type { Adminizer } from '../Adminizer.js';
2
+ export interface DocMeta {
3
+ /** Stable document identifier; the only handle that ever leaves the abstraction. */
4
+ id: string;
5
+ /** Title in the requested locale when a translation exists. */
6
+ title: string;
7
+ /** Viewer grouping. */
8
+ section?: string;
9
+ keywords: string[];
10
+ /** Model resource names this document is about. */
11
+ models: string[];
12
+ /** Express-style admin path patterns (`:id` placeholders) the document is bound to. */
13
+ urls: string[];
14
+ /**
15
+ * Per-document access right, required on top of the base documentation
16
+ * token. References an existing token (registered by the host or an app);
17
+ * documentation never creates tokens itself.
18
+ */
19
+ accessRightsToken?: string;
20
+ /** Locales this document is available in. */
21
+ locales?: string[];
22
+ }
23
+ export interface DocContent {
24
+ meta: DocMeta;
25
+ /** Content in the locale that was actually served. */
26
+ markdown: string;
27
+ locale: string;
28
+ }
29
+ export interface DocSearchQuery {
30
+ /** Full-text query; matched against title, keywords and content. */
31
+ query?: string;
32
+ /** Every listed keyword must be present on the document. */
33
+ keywords?: string[];
34
+ /** Narrow the scope to documents bound to this admin path. */
35
+ url?: string;
36
+ /** Narrow the scope to documents bound to this model. */
37
+ model?: string;
38
+ }
39
+ export interface DocSearchResult {
40
+ meta: DocMeta;
41
+ /** Matched lines with a little surrounding context, best matches first. */
42
+ snippets: string[];
43
+ }
44
+ /**
45
+ * The single contract of the documentation subsystem. Everything adminizer
46
+ * does with documentation — listing, reading, searching, resolving links —
47
+ * goes through this class; where documents live, how translations are stored
48
+ * and how search works are private details of the implementation.
49
+ *
50
+ * One implementation is active per adminizer instance (a singleton registered
51
+ * on startup). Implementations know nothing about users or permissions: the
52
+ * access layer is applied by adminizer on top of every result.
53
+ *
54
+ * Only `list` and `get` are required. The base class implements search,
55
+ * context matching, the keyword map and link resolution on top of them; an
56
+ * implementation overrides any of those when it can do better (e.g. database
57
+ * full-text search).
58
+ */
59
+ export declare abstract class AbstractDocumentation {
60
+ /** Set when the implementation is registered; not part of the contract. */
61
+ adminizer: Adminizer;
62
+ /**
63
+ * Metadata of every document, without content, in the requested locale.
64
+ * Falls back to the best available translation per document.
65
+ */
66
+ abstract list(locale?: string): Promise<DocMeta[]>;
67
+ /**
68
+ * One document in the requested locale. How the translation is obtained
69
+ * is up to the implementation; when the locale is unavailable it serves
70
+ * the best available version and reports which locale it actually is.
71
+ */
72
+ abstract get(id: string, locale?: string): Promise<DocContent | undefined>;
73
+ /**
74
+ * Keyword filter plus full-text scan over `list()`/`get()`. Content is
75
+ * only fetched for documents that survive the metadata filters.
76
+ */
77
+ search(query: DocSearchQuery, locale?: string): Promise<DocSearchResult[]>;
78
+ /** Documents bound to a place in the admin panel: a URL and/or a model. */
79
+ forContext(ctx: {
80
+ url?: string;
81
+ model?: string;
82
+ }, locale?: string): Promise<DocMeta[]>;
83
+ /** Keyword → document ids, aggregated from metadata. */
84
+ keywords(locale?: string): Promise<Map<string, string[]>>;
85
+ /**
86
+ * Resolves a reference written inside `fromId` to the id of the target
87
+ * document. How authors spell references is implementation-specific; the
88
+ * default only understands plain document ids (with an optional anchor).
89
+ */
90
+ resolveLink(_fromId: string, ref: string): Promise<string | undefined>;
91
+ /**
92
+ * Change notification: consumers subscribe to drop their caches. Optional;
93
+ * implementations without it are treated as static.
94
+ */
95
+ onChange?(cb: (ids?: string[]) => void): void;
96
+ /** Matches an Express-style pattern (`/model/:name`) against a concrete path. */
97
+ protected matchesUrl(pattern: string, url: string): boolean;
98
+ /** Lines containing the query, trimmed to a little context around the match. */
99
+ protected extractSnippets(markdown: string, query: string): string[];
100
+ }
@@ -0,0 +1,108 @@
1
+ const SNIPPET_LIMIT = 3;
2
+ const SNIPPET_CONTEXT = 120;
3
+ /**
4
+ * The single contract of the documentation subsystem. Everything adminizer
5
+ * does with documentation — listing, reading, searching, resolving links —
6
+ * goes through this class; where documents live, how translations are stored
7
+ * and how search works are private details of the implementation.
8
+ *
9
+ * One implementation is active per adminizer instance (a singleton registered
10
+ * on startup). Implementations know nothing about users or permissions: the
11
+ * access layer is applied by adminizer on top of every result.
12
+ *
13
+ * Only `list` and `get` are required. The base class implements search,
14
+ * context matching, the keyword map and link resolution on top of them; an
15
+ * implementation overrides any of those when it can do better (e.g. database
16
+ * full-text search).
17
+ */
18
+ export class AbstractDocumentation {
19
+ /** Set when the implementation is registered; not part of the contract. */
20
+ adminizer;
21
+ /**
22
+ * Keyword filter plus full-text scan over `list()`/`get()`. Content is
23
+ * only fetched for documents that survive the metadata filters.
24
+ */
25
+ async search(query, locale) {
26
+ const wanted = (query.query ?? '').trim().toLowerCase();
27
+ const keywords = (query.keywords ?? []).map((keyword) => keyword.trim().toLowerCase()).filter(Boolean);
28
+ let candidates = await this.forContext({ url: query.url, model: query.model }, locale);
29
+ if (keywords.length > 0) {
30
+ candidates = candidates.filter((meta) => {
31
+ const present = meta.keywords.map((keyword) => keyword.toLowerCase());
32
+ return keywords.every((keyword) => present.includes(keyword));
33
+ });
34
+ }
35
+ if (!wanted)
36
+ return candidates.map((meta) => ({ meta, snippets: [] }));
37
+ const results = [];
38
+ for (const meta of candidates) {
39
+ const inMeta = meta.title.toLowerCase().includes(wanted)
40
+ || meta.keywords.some((keyword) => keyword.toLowerCase().includes(wanted));
41
+ const content = await this.get(meta.id, locale);
42
+ const snippets = content ? this.extractSnippets(content.markdown, wanted) : [];
43
+ if (inMeta || snippets.length > 0)
44
+ results.push({ meta, snippets });
45
+ }
46
+ return results;
47
+ }
48
+ /** Documents bound to a place in the admin panel: a URL and/or a model. */
49
+ async forContext(ctx, locale) {
50
+ const documents = await this.list(locale);
51
+ const url = ctx.url?.trim();
52
+ const model = ctx.model?.trim();
53
+ if (!url && !model)
54
+ return documents;
55
+ return documents.filter((meta) => (!url || meta.urls.some((pattern) => this.matchesUrl(pattern, url)))
56
+ && (!model || meta.models.includes(model)));
57
+ }
58
+ /** Keyword → document ids, aggregated from metadata. */
59
+ async keywords(locale) {
60
+ const map = new Map();
61
+ for (const meta of await this.list(locale)) {
62
+ for (const raw of meta.keywords) {
63
+ const keyword = raw.trim();
64
+ if (!keyword)
65
+ continue;
66
+ const ids = map.get(keyword) ?? [];
67
+ if (!ids.includes(meta.id))
68
+ ids.push(meta.id);
69
+ map.set(keyword, ids);
70
+ }
71
+ }
72
+ return map;
73
+ }
74
+ /**
75
+ * Resolves a reference written inside `fromId` to the id of the target
76
+ * document. How authors spell references is implementation-specific; the
77
+ * default only understands plain document ids (with an optional anchor).
78
+ */
79
+ async resolveLink(_fromId, ref) {
80
+ const id = ref.trim().replace(/#.*$/, '');
81
+ if (!id)
82
+ return undefined;
83
+ const documents = await this.list();
84
+ return documents.find((meta) => meta.id === id)?.id;
85
+ }
86
+ /** Matches an Express-style pattern (`/model/:name`) against a concrete path. */
87
+ matchesUrl(pattern, url) {
88
+ const path = url.replace(/[?#].*$/, '').replace(/\/+$/, '') || '/';
89
+ const source = pattern.replace(/\/+$/, '') || '/';
90
+ const regex = new RegExp(`^${source.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/:[A-Za-z0-9_]+/g, '[^/]+')}$`);
91
+ return regex.test(path);
92
+ }
93
+ /** Lines containing the query, trimmed to a little context around the match. */
94
+ extractSnippets(markdown, query) {
95
+ const snippets = [];
96
+ for (const line of markdown.split('\n')) {
97
+ const index = line.toLowerCase().indexOf(query);
98
+ if (index === -1)
99
+ continue;
100
+ const start = Math.max(0, index - SNIPPET_CONTEXT / 2);
101
+ const snippet = line.slice(start, start + query.length + SNIPPET_CONTEXT).trim();
102
+ snippets.push(`${start > 0 ? '…' : ''}${snippet}${start + query.length + SNIPPET_CONTEXT < line.length ? '…' : ''}`);
103
+ if (snippets.length >= SNIPPET_LIMIT)
104
+ break;
105
+ }
106
+ return snippets;
107
+ }
108
+ }
@@ -0,0 +1,100 @@
1
+ import type { User } from '../../models/User.js';
2
+ import type { Adminizer } from '../Adminizer.js';
3
+ import type { AbstractDocumentation, DocContent, DocMeta, DocSearchQuery, DocSearchResult } from './AbstractDocumentation.js';
4
+ export declare const DOCUMENTATION_BASE_TOKEN = "read-documentation";
5
+ export declare const DOCUMENTATION_DEPARTMENT = "documentation";
6
+ /** Contextual table of contents handed to the UI as a shared Inertia prop. */
7
+ export interface DocContextSummary {
8
+ count: number;
9
+ items: Array<{
10
+ id: string;
11
+ title: string;
12
+ section?: string;
13
+ }>;
14
+ }
15
+ /**
16
+ * The adminizer-side facade of the documentation subsystem: holds the single
17
+ * active `AbstractDocumentation` implementation and applies access rights to
18
+ * every result before it can reach the UI, the HTTP API or the assistant.
19
+ *
20
+ * Implementations know nothing about users; a document a user may not read
21
+ * simply does not exist for them — it is absent from listings, search results,
22
+ * the keyword map and link resolution.
23
+ *
24
+ * Per-document rights are declarative: the document author references an
25
+ * existing access token in the metadata (a model token like `read-Test-model`
26
+ * via `models`, or any custom token via `accessRightsToken`). The subsystem
27
+ * never creates tokens from documents — a referenced token that nobody
28
+ * registered simply keeps the document admin-only.
29
+ */
30
+ export declare class DocumentationHandler {
31
+ private readonly adminizer;
32
+ private readonly bind?;
33
+ private service?;
34
+ private owner?;
35
+ private bound;
36
+ /**
37
+ * `bind` is the wiring Adminizer installs (token, navigation, skills, HTTP
38
+ * routes); it runs at most once, the moment the feature has both of its
39
+ * switches: `config.documentation.enabled` and a registered implementation.
40
+ */
41
+ constructor(adminizer: Adminizer, bind?: () => void);
42
+ /** Base access token; without it the whole subsystem is invisible. */
43
+ get baseToken(): string;
44
+ get enabled(): boolean;
45
+ isRegistered(): boolean;
46
+ /**
47
+ * Activates the singleton implementation. Registering a second one is an
48
+ * error: replacing the active service must be an explicit `unregister`
49
+ * followed by a new `register`.
50
+ *
51
+ * With `config.documentation.enabled` set, the first registration also
52
+ * brings the subsystem up — token, navigation, skills and HTTP routes.
53
+ * None of it exists before that, so a missing implementation is
54
+ * indistinguishable from a disabled feature.
55
+ */
56
+ register(service: AbstractDocumentation, owner?: string): void;
57
+ /**
58
+ * Runs the installed wiring once both switches of the feature hold. Called
59
+ * from `register()` and again from `Adminizer.init` — that second call
60
+ * covers a service registered before the config was read. Re-registering
61
+ * after an `unregister` reuses the wiring instead of mounting it twice.
62
+ */
63
+ activate(): void;
64
+ unregister(owner?: string): boolean;
65
+ /** The locale documentation is served in when the consumer names none. */
66
+ localeFor(user?: User): string | undefined;
67
+ /** Metadata of every document this user may read. */
68
+ list(user: User, locale?: string): Promise<DocMeta[]>;
69
+ /** One document, or `undefined` when it does not exist for this user. */
70
+ get(user: User, id: string, locale?: string): Promise<DocContent | undefined>;
71
+ search(user: User, query: DocSearchQuery, locale?: string): Promise<DocSearchResult[]>;
72
+ /** Whether the subsystem is up and this user may read documentation at all. */
73
+ canRead(user?: User): Promise<boolean>;
74
+ /**
75
+ * The contextual table of contents of an admin page: metadata only, and
76
+ * `null` when the subsystem is off or the user may not read documentation.
77
+ * Shared with every page as a prop, so it never throws.
78
+ */
79
+ forContextMeta(user: User, url: string, locale?: string): Promise<DocContextSummary | null>;
80
+ /** Documents bound to a place in the admin panel this user may read. */
81
+ forContext(user: User, ctx: {
82
+ url?: string;
83
+ model?: string;
84
+ }, locale?: string): Promise<DocMeta[]>;
85
+ /**
86
+ * Keyword → document ids over accessible documents only: keywords of a
87
+ * hidden document never reach the map, empty keywords are dropped.
88
+ */
89
+ keywords(user: User, locale?: string): Promise<Map<string, string[]>>;
90
+ /** Resolves an in-document reference; a target this user may not read stays unresolved. */
91
+ resolveLink(user: User, fromId: string, ref: string): Promise<string | undefined>;
92
+ private requireService;
93
+ private permitted;
94
+ /**
95
+ * The access decision of the subsystem: the base token, then the per-doc
96
+ * token, then — for model-bound documents — read access to at least one of
97
+ * the models, with the same token DataAccessor gates the data itself.
98
+ */
99
+ private allowed;
100
+ }
@@ -0,0 +1,198 @@
1
+ export const DOCUMENTATION_BASE_TOKEN = 'read-documentation';
2
+ export const DOCUMENTATION_DEPARTMENT = 'documentation';
3
+ /**
4
+ * The adminizer-side facade of the documentation subsystem: holds the single
5
+ * active `AbstractDocumentation` implementation and applies access rights to
6
+ * every result before it can reach the UI, the HTTP API or the assistant.
7
+ *
8
+ * Implementations know nothing about users; a document a user may not read
9
+ * simply does not exist for them — it is absent from listings, search results,
10
+ * the keyword map and link resolution.
11
+ *
12
+ * Per-document rights are declarative: the document author references an
13
+ * existing access token in the metadata (a model token like `read-Test-model`
14
+ * via `models`, or any custom token via `accessRightsToken`). The subsystem
15
+ * never creates tokens from documents — a referenced token that nobody
16
+ * registered simply keeps the document admin-only.
17
+ */
18
+ export class DocumentationHandler {
19
+ adminizer;
20
+ bind;
21
+ service;
22
+ owner;
23
+ bound = false;
24
+ /**
25
+ * `bind` is the wiring Adminizer installs (token, navigation, skills, HTTP
26
+ * routes); it runs at most once, the moment the feature has both of its
27
+ * switches: `config.documentation.enabled` and a registered implementation.
28
+ */
29
+ constructor(adminizer, bind) {
30
+ this.adminizer = adminizer;
31
+ this.bind = bind;
32
+ }
33
+ /** Base access token; without it the whole subsystem is invisible. */
34
+ get baseToken() {
35
+ return DOCUMENTATION_BASE_TOKEN;
36
+ }
37
+ get enabled() {
38
+ return this.adminizer.config.documentation?.enabled === true && Boolean(this.service);
39
+ }
40
+ isRegistered() {
41
+ return Boolean(this.service);
42
+ }
43
+ /**
44
+ * Activates the singleton implementation. Registering a second one is an
45
+ * error: replacing the active service must be an explicit `unregister`
46
+ * followed by a new `register`.
47
+ *
48
+ * With `config.documentation.enabled` set, the first registration also
49
+ * brings the subsystem up — token, navigation, skills and HTTP routes.
50
+ * None of it exists before that, so a missing implementation is
51
+ * indistinguishable from a disabled feature.
52
+ */
53
+ register(service, owner = 'host') {
54
+ if (this.service) {
55
+ throw new Error(`Documentation service is already registered by "${this.owner}"`);
56
+ }
57
+ service.adminizer = this.adminizer;
58
+ this.service = service;
59
+ this.owner = owner;
60
+ this.activate();
61
+ }
62
+ /**
63
+ * Runs the installed wiring once both switches of the feature hold. Called
64
+ * from `register()` and again from `Adminizer.init` — that second call
65
+ * covers a service registered before the config was read. Re-registering
66
+ * after an `unregister` reuses the wiring instead of mounting it twice.
67
+ */
68
+ activate() {
69
+ if (this.bound || !this.bind || !this.service)
70
+ return;
71
+ if (this.adminizer.config?.documentation?.enabled !== true)
72
+ return;
73
+ this.bound = true;
74
+ this.bind();
75
+ }
76
+ unregister(owner) {
77
+ if (!this.service || (owner && this.owner !== owner))
78
+ return false;
79
+ this.service = undefined;
80
+ this.owner = undefined;
81
+ return true;
82
+ }
83
+ /** The locale documentation is served in when the consumer names none. */
84
+ localeFor(user) {
85
+ if (user?.locale)
86
+ return user.locale;
87
+ const translation = this.adminizer.config.translation;
88
+ return typeof translation === 'object' && translation ? translation.defaultLocale : undefined;
89
+ }
90
+ /** Metadata of every document this user may read. */
91
+ async list(user, locale) {
92
+ const service = this.requireService();
93
+ return this.permitted(user, await service.list(locale));
94
+ }
95
+ /** One document, or `undefined` when it does not exist for this user. */
96
+ async get(user, id, locale) {
97
+ const service = this.requireService();
98
+ const content = await service.get(id, locale);
99
+ if (!content || !(await this.allowed(user, content.meta)))
100
+ return undefined;
101
+ return content;
102
+ }
103
+ async search(user, query, locale) {
104
+ const service = this.requireService();
105
+ const results = await service.search(query, locale);
106
+ const filtered = [];
107
+ for (const result of results) {
108
+ if (await this.allowed(user, result.meta))
109
+ filtered.push(result);
110
+ }
111
+ return filtered;
112
+ }
113
+ /** Whether the subsystem is up and this user may read documentation at all. */
114
+ async canRead(user) {
115
+ if (!this.enabled || !user)
116
+ return false;
117
+ return Boolean(await this.adminizer.accessRightsHelper.hasPermission(this.baseToken, user));
118
+ }
119
+ /**
120
+ * The contextual table of contents of an admin page: metadata only, and
121
+ * `null` when the subsystem is off or the user may not read documentation.
122
+ * Shared with every page as a prop, so it never throws.
123
+ */
124
+ async forContextMeta(user, url, locale) {
125
+ if (!(await this.canRead(user)))
126
+ return null;
127
+ const documents = await this.forContext(user, { url }, locale ?? this.localeFor(user));
128
+ return {
129
+ count: documents.length,
130
+ items: documents.map((meta) => ({ id: meta.id, title: meta.title, section: meta.section })),
131
+ };
132
+ }
133
+ /** Documents bound to a place in the admin panel this user may read. */
134
+ async forContext(user, ctx, locale) {
135
+ const service = this.requireService();
136
+ return this.permitted(user, await service.forContext(ctx, locale));
137
+ }
138
+ /**
139
+ * Keyword → document ids over accessible documents only: keywords of a
140
+ * hidden document never reach the map, empty keywords are dropped.
141
+ */
142
+ async keywords(user, locale) {
143
+ const service = this.requireService();
144
+ const visible = new Set((await this.list(user, locale)).map((meta) => meta.id));
145
+ const map = new Map();
146
+ for (const [keyword, ids] of await service.keywords(locale)) {
147
+ const permitted = ids.filter((id) => visible.has(id));
148
+ if (permitted.length > 0)
149
+ map.set(keyword, permitted);
150
+ }
151
+ return map;
152
+ }
153
+ /** Resolves an in-document reference; a target this user may not read stays unresolved. */
154
+ async resolveLink(user, fromId, ref) {
155
+ const service = this.requireService();
156
+ const target = await service.resolveLink(fromId, ref);
157
+ if (!target)
158
+ return undefined;
159
+ const metas = await service.list();
160
+ const meta = metas.find((candidate) => candidate.id === target);
161
+ if (!meta || !(await this.allowed(user, meta)))
162
+ return undefined;
163
+ return target;
164
+ }
165
+ requireService() {
166
+ if (!this.service)
167
+ throw new Error('No documentation service is registered');
168
+ return this.service;
169
+ }
170
+ async permitted(user, metas) {
171
+ const filtered = [];
172
+ for (const meta of metas) {
173
+ if (await this.allowed(user, meta))
174
+ filtered.push(meta);
175
+ }
176
+ return filtered;
177
+ }
178
+ /**
179
+ * The access decision of the subsystem: the base token, then the per-doc
180
+ * token, then — for model-bound documents — read access to at least one of
181
+ * the models, with the same token DataAccessor gates the data itself.
182
+ */
183
+ async allowed(user, meta) {
184
+ const has = (token) => this.adminizer.accessRightsHelper.hasPermission(token, user);
185
+ if (!(await has(this.baseToken)))
186
+ return false;
187
+ if (meta.accessRightsToken && !(await has(meta.accessRightsToken)))
188
+ return false;
189
+ if (meta.models.length > 0) {
190
+ for (const model of meta.models) {
191
+ if (await has(`read-${model}-model`))
192
+ return true;
193
+ }
194
+ return false;
195
+ }
196
+ return true;
197
+ }
198
+ }
@@ -0,0 +1,47 @@
1
+ import { AbstractDocumentation, DocContent, DocMeta } from './AbstractDocumentation.js';
2
+ export interface FileDocumentationOptions {
3
+ /** Directory scanned (recursively) for markdown documents. */
4
+ dir: string;
5
+ /** Locale of files without a locale suffix. @default "en" */
6
+ defaultLocale?: string;
7
+ /** Watch the directory and drop the cache on changes. @default false */
8
+ watch?: boolean;
9
+ }
10
+ /**
11
+ * The reference implementation bundled with adminizer: documents are markdown
12
+ * files with a frontmatter header, translations are sibling files with a
13
+ * locale suffix (`intro.md` + `intro.ru.md` → document `intro` in two
14
+ * locales). Structural metadata (bindings, rights, section) is taken from the
15
+ * default-locale file; title and keywords are translatable per file.
16
+ *
17
+ * Frontmatter is an intentionally small `key: value` subset (quoted strings,
18
+ * `[a, b]` inline lists, `- item` dash lists), not full YAML.
19
+ */
20
+ export declare class FileDocumentation extends AbstractDocumentation {
21
+ private readonly dir;
22
+ private readonly defaultLocale;
23
+ private cache?;
24
+ private readonly changeCallbacks;
25
+ private watcher?;
26
+ private watchDebounce?;
27
+ constructor(options: FileDocumentationOptions);
28
+ list(locale?: string): Promise<DocMeta[]>;
29
+ get(id: string, locale?: string): Promise<DocContent | undefined>;
30
+ onChange(cb: (ids?: string[]) => void): void;
31
+ /** Drops the cache; the next call rescans the directory. */
32
+ reload(): void;
33
+ stopWatcher(): void;
34
+ private startWatcher;
35
+ private scan;
36
+ private scanDirectory;
37
+ /** `<id>.md` is the default locale, `<id>.<locale>.md` a translation. */
38
+ private parseFile;
39
+ private buildMeta;
40
+ /** Exact locale, else the default locale, else whatever exists. */
41
+ private pickTranslation;
42
+ private splitFrontmatter;
43
+ private parseFrontmatter;
44
+ private stringList;
45
+ private unquote;
46
+ private extractTitle;
47
+ }