ework-web 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-web",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "ework-web — standalone multi-project issue tracker. Local SQLite-backed, no external API dependency. Bun + TypeScript + SSR HTML.",
6
6
  "license": "MIT",
package/src/config.ts CHANGED
@@ -164,7 +164,7 @@ export function loadConfig(): Config {
164
164
  }
165
165
 
166
166
  export function resolveTtsBackend(cfg: Config, id?: string): TtsBackend | null {
167
- const list = cfg.ttsBackends;
167
+ const list = cfg.ttsBackends.filter((b) => b.url.trim() !== "");
168
168
  if (list.length === 0) return null;
169
169
  return list.find((b) => b.id === id) ?? list.find((b) => b.id === cfg.ttsDefaultBackend) ?? list[0] ?? null;
170
170
  }
package/src/index.ts CHANGED
@@ -534,7 +534,7 @@ async function handle(req: Request, url: URL, ip: string, ctx: { authed: boolean
534
534
  }
535
535
 
536
536
  if (req.method === "GET" && url.pathname === "/api/tts/backends") {
537
- return json(cfg.ttsBackends.map((b) => ({ id: b.id, label: b.label, voice: b.voice })));
537
+ return json(cfg.ttsBackends.filter((b) => b.url.trim() !== "").map((b) => ({ id: b.id, label: b.label, voice: b.voice })));
538
538
  }
539
539
 
540
540
  if (req.method === "POST" && url.pathname === "/api/tts") {
@@ -44,7 +44,7 @@ export function relTime(iso: string): string {
44
44
  }
45
45
 
46
46
  // Must match app.js cardHTML so client hydration/polling produce identical markup.
47
- export function renderCommentCard(c: CommentView): string {
47
+ export function renderCommentCard(c: CommentView, cfg?: { translateUrl?: string; ttsBackends?: { url: string }[] }): string {
48
48
  const tag = c.tag || "human";
49
49
  const label = TAG_LABEL[tag] || "👤";
50
50
  const rx =
@@ -53,6 +53,8 @@ export function renderCommentCard(c: CommentView): string {
53
53
  .map((r) => `<span class="rxc">${r.e}<span class="rxn">${r.n}</span></span>`)
54
54
  .join("")}</span>`
55
55
  : "";
56
+ const translateEnabled = cfg ? !!cfg.translateUrl && cfg.translateUrl.trim() !== "" : true;
57
+ const ttsEnabled = cfg ? !!(cfg.ttsBackends && cfg.ttsBackends.some((b) => b.url && b.url.trim() !== "")) : true;
56
58
  return (
57
59
  `<div class="item item-${esc(tag)}" id="comment-${c.id}" data-id="${c.id}">` +
58
60
  `<div class="card"><div class="card-h">` +
@@ -60,7 +62,7 @@ export function renderCommentCard(c: CommentView): string {
60
62
  `<span class="who">${esc(c.login)}</span>` +
61
63
  `<span class="when" data-ts="${esc(c.created_at)}" title="${esc(c.created_at)}">${relTime(c.created_at)}</span>` +
62
64
  rx +
63
- `<span class="card-actions">` + actionBarHTML({ cid: String(c.id), copy: true, link: true, translate: true, tts: true }) + `</span>` +
65
+ `<span class="card-actions">` + actionBarHTML({ cid: String(c.id), copy: true, link: true, translate: true, tts: true, translateEnabled, ttsEnabled }) + `</span>` +
64
66
  `</div><div class="card-b">${c.body_html}</div></div></div>`
65
67
  );
66
68
  }
@@ -12,6 +12,8 @@ export interface LayoutProps {
12
12
  writesEnabled?: boolean;
13
13
  operatorLogin?: string;
14
14
  upstreamWebUrl?: string | null;
15
+ translateEnabled?: boolean;
16
+ ttsEnabled?: boolean;
15
17
  }
16
18
 
17
19
  export const THEME_CSS = `
@@ -143,7 +145,7 @@ export function renderLayout(props: LayoutProps, inner: string, initialItems: st
143
145
  </div>
144
146
  </div>
145
147
  ${props.descriptionHtml.trim() ? `<div class="desc-wrap">
146
- <div class="desc-actions">${actionBarHTML({ copy: true, link: true, translate: true, tts: true })}</div>
148
+ <div class="desc-actions">${actionBarHTML({ copy: true, link: true, translate: true, tts: true, translateEnabled: props.translateEnabled !== false, ttsEnabled: props.ttsEnabled !== false })}</div>
147
149
  <div class="desc${props.descriptionCollapsed ? " collapsed" : ""}" id="issueDesc">${props.descriptionHtml}</div>
148
150
  ${props.descriptionCollapsed ? `<button type="button" class="desc-toggle" id="descToggle">显示详情 ▾</button>` : ""}
149
151
  </div>` : ""}
@@ -226,14 +228,18 @@ export interface ActionBarOpts {
226
228
  link?: boolean;
227
229
  translate?: boolean;
228
230
  tts?: boolean;
231
+ translateEnabled?: boolean;
232
+ ttsEnabled?: boolean;
229
233
  }
230
234
 
231
235
  export function actionBarHTML(o: ActionBarOpts): string {
232
236
  const d = o.cid != null ? ` data-cid="${escapeAttr(o.cid)}"` : "";
233
237
  const cpy = o.copy ? `<button type="button" class="cbtn"${d} title="复制">📋</button>` : "";
234
238
  const lnk = o.link ? `<button type="button" class="clink"${d} title="复制楼层链接">🔗</button>` : "";
235
- const tr = o.translate ? `<button type="button" class="tbtn"${d} title="翻译">翻译</button>` : "";
236
- const tts = o.tts
239
+ const tr = (o.translate && o.translateEnabled !== false)
240
+ ? `<button type="button" class="tbtn"${d} title="翻译">翻译</button>`
241
+ : "";
242
+ const tts = (o.tts && o.ttsEnabled !== false)
237
243
  ? `<button type="button" class="ttsstop"${d} title="停止朗读">⏹</button><button type="button" class="ttsbtn"${d} title="朗读">🔊</button>`
238
244
  : "";
239
245
  return cpy + lnk + tr + tts;
@@ -124,9 +124,11 @@ export function buildIssueThread(
124
124
  writesEnabled: cfg.writesEnabled,
125
125
  operatorLogin: viewerLogin ?? cfg.operatorLogin,
126
126
  upstreamWebUrl,
127
+ translateEnabled: !!cfg.translateUrl && cfg.translateUrl.trim() !== "",
128
+ ttsEnabled: cfg.ttsBackends.some((b) => b.url && b.url.trim() !== ""),
127
129
  },
128
130
  safeJsonEmbed(payload),
129
- displayViews.map(renderCommentCard).join("")
131
+ displayViews.map((v) => renderCommentCard(v, cfg)).join("")
130
132
  );
131
133
  return { html };
132
134
  }