alemonjs 2.1.83-alpha.7 → 2.1.83-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,4 @@
1
+ import type { RuntimeAppRecord } from '../../store.js';
2
+ export declare const renderHelloHtml: (apps: RuntimeAppRecord[]) => string;
1
3
  declare const _default: string;
2
4
  export default _default;
@@ -1,31 +1,390 @@
1
- import { Component, Head, Title, Style, Body, H1, P, A, Div, Html } from '../../../../common/react.js';
2
-
3
- class App extends Component {
4
- render() {
5
- const style = `
1
+ const escapeHtml = (value) => String(value)
2
+ .replace(/&/g, '&')
3
+ .replace(/</g, '&lt;')
4
+ .replace(/>/g, '&gt;')
5
+ .replace(/"/g, '&quot;')
6
+ .replace(/'/g, '&#39;');
7
+ const appHref = (app) => {
8
+ return app.kind === 'main' ? '/app' : `/apps/${app.name}`;
9
+ };
10
+ const appTags = (app) => {
11
+ const tags = [app.kind === 'main' ? '主应用' : '插件'];
12
+ if (app.capabilities.web) {
13
+ tags.push('页面');
14
+ }
15
+ if (app.capabilities.httpApi) {
16
+ tags.push('接口');
17
+ }
18
+ if (app.capabilities.event) {
19
+ tags.push('事件');
20
+ }
21
+ if (app.capabilities.expose) {
22
+ tags.push('Expose');
23
+ }
24
+ return tags;
25
+ };
26
+ const renderCard = (app) => {
27
+ const href = appHref(app);
28
+ const tags = appTags(app)
29
+ .map(tag => `<span class="app-tag">${escapeHtml(tag)}</span>`)
30
+ .join('');
31
+ const desc = app.kind === 'main'
32
+ ? '访问主应用页面、接口与默认资源。'
33
+ : '访问插件页面、接口与公开入口。';
34
+ return `
35
+ <a
36
+ class="app-card"
37
+ href="${escapeHtml(href)}"
38
+ data-app-id="${escapeHtml(app.name)}"
39
+ data-app-kind="${escapeHtml(app.kind)}"
40
+ data-app-href="${escapeHtml(href)}"
41
+ >
42
+ <div class="app-card__top">
43
+ <div>
44
+ <p class="app-eyebrow">${app.kind === 'main' ? 'Main App' : 'Plugin App'}</p>
45
+ <h2 class="app-title">${escapeHtml(app.name)}</h2>
46
+ </div>
47
+ <div class="app-rank">
48
+ <span class="app-rank__label">热度</span>
49
+ <span class="app-rank__value" data-click-count>0</span>
50
+ </div>
51
+ </div>
52
+ <p class="app-desc">${escapeHtml(desc)}</p>
53
+ <div class="app-tags">${tags}</div>
54
+ <div class="app-card__bottom">
55
+ <span class="app-link">${escapeHtml(href)}</span>
56
+ <span class="app-action">进入</span>
57
+ </div>
58
+ </a>
59
+ `;
60
+ };
61
+ const renderEmpty = () => {
62
+ return `
63
+ <div class="empty-state">
64
+ <p class="empty-state__title">当前没有可展示的应用。</p>
65
+ <p class="empty-state__desc">请先加载主应用或插件,再刷新本页查看入口。</p>
66
+ </div>
67
+ `;
68
+ };
69
+ const renderHelloHtml = (apps) => {
70
+ const visibleApps = apps
71
+ .filter(app => app.enabled && app.status === 'ready' && (app.capabilities.web || app.capabilities.httpApi))
72
+ .sort((left, right) => {
73
+ if (left.kind === 'main' && right.kind !== 'main') {
74
+ return -1;
75
+ }
76
+ if (left.kind !== 'main' && right.kind === 'main') {
77
+ return 1;
78
+ }
79
+ return left.name.localeCompare(right.name);
80
+ });
81
+ const cards = visibleApps.length ? visibleApps.map(renderCard).join('') : renderEmpty();
82
+ const payload = JSON.stringify(visibleApps.map(app => ({
83
+ id: app.name,
84
+ href: appHref(app),
85
+ kind: app.kind
86
+ })));
87
+ return `<!DOCTYPE html>
88
+ <html lang="zh-CN">
89
+ <head>
90
+ <meta charset="utf-8" />
91
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
92
+ <title>ALemonJS 应用入口</title>
93
+ <style>
94
+ :root {
95
+ --bg: #f3efe5;
96
+ --panel: rgba(255, 251, 245, 0.88);
97
+ --panel-strong: #fffaf2;
98
+ --text: #17212b;
99
+ --muted: #6a7684;
100
+ --line: rgba(23, 33, 43, 0.08);
101
+ --accent: #d96c28;
102
+ --accent-strong: #a54a13;
103
+ --shadow: 0 24px 60px rgba(47, 35, 20, 0.12);
104
+ }
105
+ * { box-sizing: border-box; }
106
+ html, body { margin: 0; min-height: 100%; }
6
107
  body {
7
- width: 35em;
108
+ font-family: "SF Pro Display", "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
109
+ color: var(--text);
110
+ background:
111
+ radial-gradient(circle at top left, rgba(217, 108, 40, 0.18), transparent 28%),
112
+ radial-gradient(circle at right center, rgba(78, 140, 124, 0.14), transparent 26%),
113
+ linear-gradient(180deg, #f8f4ec 0%, var(--bg) 100%);
114
+ }
115
+ .page {
116
+ width: min(1680px, calc(100vw - 32px));
8
117
  margin: 0 auto;
9
- font-family: Tahoma, Verdana, Arial, sans-serif;
10
- background-color: #f8f8f8;
11
- color: #333;
12
- }
13
- h1 {
14
- color: #0099cc;
15
- margin-top: 2em;
16
- }
17
- .footer {
18
- margin-top: 3em;
19
- font-size: 0.9em;
20
- color: #888;
21
- }
22
- a { color: #0099cc; }
23
- `;
24
- const head = Head(null, Title('欢迎使用 ALemonJS!'), Style(style));
25
- const body = Body(null, H1('ALemonJS 启动成功!'), P(null, '已成功通过 ', A({ href: 'https://alemonjs.com', target: '_blank' }, 'ALemonJS 框架'), ' 启动。'), P(null, '如果想访问主应用,请访问, ', A({ href: '/app', target: '_blank' }, '/app'), '(对应根目录index.html)'), P(null, '如果想访问其他应用,请访问 ', A({ href: '/apps/[package-name]', target: '_blank' }, '/apps/[package-name]'), '。(对应/packages/[package-name]/index.html)'), Div({ className: 'footer' }, '— 感谢选择 ALemonJS。'));
26
- return Html(null, head, body);
27
- }
28
- }
29
- var hello = App.renderToHtml();
118
+ padding: clamp(28px, 4vw, 56px) 0 64px;
119
+ }
120
+ .hero {
121
+ position: relative;
122
+ overflow: hidden;
123
+ padding: clamp(28px, 4vw, 56px);
124
+ border-radius: 32px;
125
+ background: linear-gradient(135deg, rgba(255, 250, 242, 0.95), rgba(255, 244, 231, 0.88));
126
+ border: 1px solid rgba(255, 255, 255, 0.72);
127
+ box-shadow: var(--shadow);
128
+ }
129
+ .hero::after {
130
+ content: "";
131
+ position: absolute;
132
+ inset: auto -8% -48% auto;
133
+ width: min(38vw, 520px);
134
+ aspect-ratio: 1;
135
+ border-radius: 999px;
136
+ background: radial-gradient(circle, rgba(217, 108, 40, 0.18), transparent 62%);
137
+ pointer-events: none;
138
+ }
139
+ .hero-kicker {
140
+ margin: 0 0 12px;
141
+ font-size: clamp(14px, 1.2vw, 18px);
142
+ letter-spacing: 0.18em;
143
+ text-transform: uppercase;
144
+ color: var(--accent-strong);
145
+ }
146
+ .hero-title {
147
+ margin: 0;
148
+ max-width: 12ch;
149
+ font-size: clamp(40px, 7vw, 86px);
150
+ line-height: 0.95;
151
+ letter-spacing: -0.04em;
152
+ }
153
+ .hero-desc {
154
+ margin: 18px 0 0;
155
+ max-width: 56rem;
156
+ font-size: clamp(18px, 2vw, 28px);
157
+ line-height: 1.6;
158
+ color: var(--muted);
159
+ }
160
+ .hero-meta {
161
+ display: flex;
162
+ flex-wrap: wrap;
163
+ gap: 14px;
164
+ margin-top: 28px;
165
+ }
166
+ .hero-pill {
167
+ display: inline-flex;
168
+ align-items: center;
169
+ gap: 10px;
170
+ padding: 12px 18px;
171
+ border-radius: 999px;
172
+ background: rgba(255, 255, 255, 0.78);
173
+ border: 1px solid rgba(23, 33, 43, 0.08);
174
+ font-size: clamp(14px, 1.4vw, 18px);
175
+ }
176
+ .hero-pill strong {
177
+ color: var(--accent-strong);
178
+ }
179
+ .section-head {
180
+ display: flex;
181
+ align-items: end;
182
+ justify-content: space-between;
183
+ gap: 20px;
184
+ margin: 28px 0 18px;
185
+ padding: 0 6px;
186
+ }
187
+ .section-title {
188
+ margin: 0;
189
+ font-size: clamp(28px, 3vw, 42px);
190
+ }
191
+ .section-note {
192
+ margin: 0;
193
+ font-size: clamp(14px, 1.3vw, 18px);
194
+ color: var(--muted);
195
+ }
196
+ .app-grid {
197
+ display: grid;
198
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
199
+ gap: 18px;
200
+ }
201
+ .app-card {
202
+ display: flex;
203
+ flex-direction: column;
204
+ gap: 18px;
205
+ min-height: 280px;
206
+ padding: 24px;
207
+ border-radius: 28px;
208
+ text-decoration: none;
209
+ color: inherit;
210
+ background: var(--panel);
211
+ border: 1px solid var(--line);
212
+ box-shadow: 0 14px 40px rgba(30, 37, 44, 0.08);
213
+ transition: transform 180ms ease, box-shadow 180ms ease, border-color 180ms ease;
214
+ }
215
+ .app-card:hover {
216
+ transform: translateY(-4px);
217
+ border-color: rgba(217, 108, 40, 0.28);
218
+ box-shadow: 0 24px 50px rgba(30, 37, 44, 0.12);
219
+ }
220
+ .app-card__top,
221
+ .app-card__bottom {
222
+ display: flex;
223
+ align-items: center;
224
+ justify-content: space-between;
225
+ gap: 16px;
226
+ }
227
+ .app-eyebrow {
228
+ margin: 0 0 8px;
229
+ font-size: 13px;
230
+ letter-spacing: 0.16em;
231
+ text-transform: uppercase;
232
+ color: var(--accent-strong);
233
+ }
234
+ .app-title {
235
+ margin: 0;
236
+ font-size: clamp(28px, 2.8vw, 38px);
237
+ line-height: 1;
238
+ }
239
+ .app-desc {
240
+ margin: 0;
241
+ font-size: clamp(16px, 1.4vw, 20px);
242
+ line-height: 1.7;
243
+ color: var(--muted);
244
+ }
245
+ .app-tags {
246
+ display: flex;
247
+ flex-wrap: wrap;
248
+ gap: 10px;
249
+ }
250
+ .app-tag {
251
+ padding: 8px 12px;
252
+ border-radius: 999px;
253
+ background: rgba(217, 108, 40, 0.1);
254
+ color: var(--accent-strong);
255
+ font-size: 14px;
256
+ }
257
+ .app-rank {
258
+ min-width: 84px;
259
+ text-align: right;
260
+ }
261
+ .app-rank__label,
262
+ .app-link {
263
+ display: block;
264
+ font-size: 13px;
265
+ color: var(--muted);
266
+ }
267
+ .app-rank__value {
268
+ font-size: clamp(24px, 2vw, 30px);
269
+ font-weight: 700;
270
+ }
271
+ .app-action {
272
+ padding: 12px 18px;
273
+ border-radius: 999px;
274
+ background: #1d2d38;
275
+ color: #fff;
276
+ font-size: 15px;
277
+ }
278
+ .empty-state {
279
+ padding: 32px;
280
+ border-radius: 28px;
281
+ background: var(--panel-strong);
282
+ border: 1px dashed rgba(23, 33, 43, 0.16);
283
+ }
284
+ .empty-state__title {
285
+ margin: 0;
286
+ font-size: 24px;
287
+ }
288
+ .empty-state__desc {
289
+ margin: 10px 0 0;
290
+ color: var(--muted);
291
+ font-size: 16px;
292
+ }
293
+ @media (max-width: 820px) {
294
+ .page { width: min(100vw - 20px, 100%); padding-top: 18px; }
295
+ .hero { border-radius: 24px; padding: 22px; }
296
+ .section-head { align-items: start; flex-direction: column; }
297
+ .app-card { min-height: 0; padding: 20px; border-radius: 22px; }
298
+ .app-card__top,
299
+ .app-card__bottom { align-items: start; flex-direction: column; }
300
+ .app-rank { text-align: left; min-width: 0; }
301
+ }
302
+ </style>
303
+ </head>
304
+ <body>
305
+ <main class="page">
306
+ <section class="hero">
307
+ <p class="hero-kicker">ALemonJS Launchpad</p>
308
+ <h1 class="hero-title">欢迎使用阿柠檬机器人应用服务</h1>
309
+ <p class="hero-desc">这里会自动列出当前已准备好的主应用与插件。</p>
310
+ <div class="hero-meta">
311
+ <span class="hero-pill"><strong>${visibleApps.length}</strong> 个可访问入口</span>
312
+ <span class="hero-pill">主应用统一在 <strong>/app</strong></span>
313
+ <span class="hero-pill">插件统一在 <strong>/apps/&lt;name&gt;</strong></span>
314
+ </div>
315
+ </section>
316
+ <section>
317
+ <div class="section-head">
318
+ <div>
319
+ <h2 class="section-title">应用列表</h2>
320
+ <p class="section-note">点击次数越高,卡片排序越靠前。排序记录仅保存在当前浏览器本地。</p>
321
+ </div>
322
+ </div>
323
+ <div class="app-grid" id="app-grid">${cards}</div>
324
+ </section>
325
+ </main>
326
+ <script>
327
+ (() => {
328
+ const storageKey = 'alemonjs:launchpad:clicks';
329
+ const apps = ${payload};
330
+ const grid = document.getElementById('app-grid');
331
+ if (!grid) return;
332
+
333
+ const readClicks = () => {
334
+ try {
335
+ return JSON.parse(localStorage.getItem(storageKey) || '{}');
336
+ } catch {
337
+ return {};
338
+ }
339
+ };
340
+
341
+ const writeClicks = (value) => {
342
+ localStorage.setItem(storageKey, JSON.stringify(value));
343
+ };
344
+
345
+ const scoreOf = (id, clicks) => Number(clicks[id] || 0);
346
+ const clicks = readClicks();
347
+ const cards = Array.from(grid.querySelectorAll('[data-app-id]'));
348
+
349
+ const refreshCounts = () => {
350
+ cards.forEach((card) => {
351
+ const id = card.getAttribute('data-app-id') || '';
352
+ const node = card.querySelector('[data-click-count]');
353
+ if (node) {
354
+ node.textContent = String(scoreOf(id, clicks));
355
+ }
356
+ });
357
+ };
358
+
359
+ const sortCards = () => {
360
+ cards
361
+ .sort((left, right) => {
362
+ const leftId = left.getAttribute('data-app-id') || '';
363
+ const rightId = right.getAttribute('data-app-id') || '';
364
+ const diff = scoreOf(rightId, clicks) - scoreOf(leftId, clicks);
365
+ if (diff !== 0) return diff;
366
+ return leftId.localeCompare(rightId);
367
+ })
368
+ .forEach(card => grid.appendChild(card));
369
+ };
370
+
371
+ cards.forEach((card) => {
372
+ card.addEventListener('click', () => {
373
+ const id = card.getAttribute('data-app-id') || '';
374
+ clicks[id] = scoreOf(id, clicks) + 1;
375
+ writeClicks(clicks);
376
+ refreshCounts();
377
+ sortCards();
378
+ });
379
+ });
380
+
381
+ refreshCounts();
382
+ sortCards();
383
+ })();
384
+ </script>
385
+ </body>
386
+ </html>`;
387
+ };
388
+ var hello_html = renderHelloHtml([]);
30
389
 
31
- export { hello as default };
390
+ export { hello_html as default, renderHelloHtml };
@@ -2,7 +2,7 @@ import KoaRouter from 'koa-router';
2
2
  import fs__default, { existsSync } from 'fs';
3
3
  import path__default, { join, dirname } from 'path';
4
4
  import mime from 'mime-types';
5
- import hello from './hello.html.js';
5
+ import { renderHelloHtml } from './hello.html.js';
6
6
  import { safePath, getModuelFile, formatPath, isValidPackageName } from './utils.js';
7
7
  import { collectMiddlewares, runMiddlewares } from './middleware.js';
8
8
  import module$1 from 'module';
@@ -155,9 +155,7 @@ const dispatchRegisteredKoaRouters = async (ctx) => {
155
155
  }
156
156
  const registerRouters = getChildrenApp(appName)?.register?.koaRouter;
157
157
  const storedRouters = getRuntimeAppKoaRouters(appName);
158
- const routers = (storedRouters.length
159
- ? storedRouters
160
- : (Array.isArray(registerRouters) ? registerRouters : registerRouters ? [registerRouters] : [])).filter(Boolean);
158
+ const routers = (storedRouters.length ? storedRouters : Array.isArray(registerRouters) ? registerRouters : registerRouters ? [registerRouters] : []).filter(Boolean);
161
159
  const aliasBases = appName === 'main' ? ['', '/app'] : ['', `/apps/${appName}`];
162
160
  for (const koaRouter of routers) {
163
161
  for (const basePath of aliasBases) {
@@ -228,7 +226,7 @@ const dispatchRegisteredKoaRouters = async (ctx) => {
228
226
  router.get('/', ctx => {
229
227
  ctx.status = 200;
230
228
  ctx.set('Content-Type', 'text/html; charset=utf-8');
231
- ctx.body = hello;
229
+ ctx.body = renderHelloHtml(listRuntimeApps());
232
230
  });
233
231
  router.get('api/online', ctx => {
234
232
  ctx.status = 200;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alemonjs",
3
- "version": "2.1.83-alpha.7",
3
+ "version": "2.1.83-alpha.8",
4
4
  "description": "bot script",
5
5
  "author": "lemonade",
6
6
  "license": "MIT",