ework-web 0.10.41 → 0.10.43

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.10.41",
3
+ "version": "0.10.43",
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/build.ts CHANGED
@@ -3,7 +3,7 @@ import { join } from "path";
3
3
 
4
4
  const STATIC = join(__dirname, "static");
5
5
  let m = 0;
6
- for (const f of ["app.js", "session.js", "file.js", "tts.js", "highlight.css"]) {
6
+ for (const f of ["app.js", "session.js", "file.js", "tts.js", "highlight.css", "label-picker.js", "project-labels.js", "daemon-groups.js", "db-wizard.js", "daemon-mgr.js"]) {
7
7
  try {
8
8
  const s = statSync(join(STATIC, f));
9
9
  if (s.mtimeMs > m) m = s.mtimeMs;
package/src/index.ts CHANGED
@@ -467,6 +467,7 @@ async function handle(req: Request, url: URL, ip: string, ctx: { authed: boolean
467
467
  if (url.pathname === "/static/daemon-mgr.js") return staticAsset("daemon-mgr.js", "text/javascript; charset=utf-8", req);
468
468
  if (url.pathname === "/static/daemon-groups.js") return staticAsset("daemon-groups.js", "text/javascript; charset=utf-8", req);
469
469
  if (url.pathname === "/static/label-picker.js") return staticAsset("label-picker.js", "text/javascript; charset=utf-8", req);
470
+ if (url.pathname === "/static/project-labels.js") return staticAsset("project-labels.js", "text/javascript; charset=utf-8", req);
470
471
  if (url.pathname === "/static/session.js") return staticAsset("session.js", "text/javascript; charset=utf-8", req);
471
472
  if (url.pathname === "/static/file.js") return staticAsset("file.js", "text/javascript; charset=utf-8", req);
472
473
  if (url.pathname === "/static/tts.js") return staticAsset("tts.js", "text/javascript; charset=utf-8", req);
@@ -1837,7 +1838,7 @@ async function handle(req: Request, url: URL, ip: string, ctx: { authed: boolean
1837
1838
  const project = await getProject(owner, repo);
1838
1839
  if (!project) return html(errorPage("项目不存在", ""), 404);
1839
1840
  const back = `${url.origin}/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/settings/labels`;
1840
- if (!(await canAdminProject(project.id, ctx.user))) {
1841
+ if (!(await canWriteProject(project.id, ctx.user))) {
1841
1842
  return Response.redirect(`${back}?err=${encodeURIComponent("无权限")}`, 303);
1842
1843
  }
1843
1844
  const form = await req.formData().catch(() => new FormData());
@@ -191,17 +191,18 @@ function renderFileContent(
191
191
  const ext = (rawPath.split(".").pop() || "").toLowerCase();
192
192
  const isMd = ext === "md" || ext === "markdown";
193
193
  const lang = extToLang(rawPath);
194
- const fullText = data.rows.map((r) => r.t).join("\n");
195
- const shownBytes = data.rows.reduce((s, r) => s + r.t.length + 1, 0);
194
+ const sorted = [...data.rows].sort((a, b) => a.n - b.n);
195
+ const fullText = sorted.map((r) => r.t).join("\n");
196
+ const shownBytes = sorted.reduce((s, r) => s + r.t.length + 1, 0);
196
197
 
197
198
  let body: string;
198
199
  if (isMd) {
199
200
  body = `<div class="md-render">${renderMarkdown(fullText, "")}</div>`;
200
201
  } else if (lang && shownBytes <= 256000) {
201
- const nums = data.rows.map((r) => r.n).join("\n");
202
+ const nums = sorted.map((r) => r.n).join("\n");
202
203
  body = `<div class="fv-code"><pre class="fv-gutter">${escapeHtml(nums)}</pre><pre class="fv-src"><code class="hljs language-${escapeHtml(lang)}">${hljsHighlight(fullText, lang)}</code></pre></div>`;
203
204
  } else {
204
- body = `<pre class="fv-plain"><code>${data.rows.map((r) => `<span class="ln"><span class="lnn">${r.n}</span><span class="lnc">${escapeHtml(r.t)}</span></span>`).join("")}</code></pre>`;
205
+ body = `<pre class="fv-plain"><code>${sorted.map((r) => `<span class="ln"><span class="lnn">${r.n}</span><span class="lnc">${escapeHtml(r.t)}</span></span>`).join("")}</code></pre>`;
205
206
  }
206
207
 
207
208
  return `<!doctype html>
@@ -141,7 +141,7 @@ export function renderLayout(props: LayoutProps, inner: string, initialItems: st
141
141
  const repoIssuesHref = `/${encodeURIComponent(repoOwner ?? "")}/${encodeURIComponent(repoName ?? "")}/issues`;
142
142
  const op = props.operatorLogin ?? "operator";
143
143
  const labelsHtml = (props.labels ?? []).length
144
- ? props.labels!.map((l) => `<span class="issue-label" style="border-color:${escapeAttr(l.color)};color:${escapeAttr(l.color)}">${escapeHtml(l.name)}</span>`).join("")
144
+ ? props.labels!.map((l) => `<a class="issue-label" href="${repoIssuesHref}?state=all&label=${encodeURIComponent(l.name)}" style="border-color:${escapeAttr(l.color)};color:${escapeAttr(l.color)}">${escapeHtml(l.name)}</a>`).join("")
145
145
  : "";
146
146
  const labelPickerBtn = props.canEditLabels
147
147
  ? `<button type="button" class="label-edit-btn" id="labelEditBtn" title="管理标签">🏷️</button>`
@@ -0,0 +1,53 @@
1
+ (function () {
2
+ var dataEl = document.getElementById('label-data');
3
+ if (!dataEl) return;
4
+ var data = JSON.parse(dataEl.textContent || '{}');
5
+ var palette = data.palette || [];
6
+ var LABELS = data.labels || [];
7
+
8
+ function bindSwatches(container, colorInput) {
9
+ if (!container || !colorInput) return;
10
+ container.innerHTML = palette.map(function (c) {
11
+ return '<button type="button" class="swatch" data-color="' + c + '" style="background:' + c + '" aria-label="' + c + '"></button>';
12
+ }).join('');
13
+ function sync() {
14
+ container.querySelectorAll('.swatch').forEach(function (b) {
15
+ b.classList.toggle('sel', b.dataset.color.toLowerCase() === colorInput.value.toLowerCase());
16
+ });
17
+ }
18
+ sync();
19
+ container.addEventListener('click', function (e) {
20
+ var b = e.target.closest('.swatch');
21
+ if (!b) return;
22
+ colorInput.value = b.dataset.color;
23
+ sync();
24
+ });
25
+ colorInput.addEventListener('input', sync);
26
+ }
27
+
28
+ bindSwatches(document.getElementById('palette'), document.getElementById('f-color'));
29
+
30
+ var dlg = document.getElementById('dlg');
31
+ if (!dlg) return;
32
+ var ef = document.getElementById('edit-form');
33
+ var dp = document.getElementById('dlg-palette');
34
+ var dlgColor;
35
+
36
+ document.addEventListener('click', function (e) {
37
+ var btn = e.target.closest('[data-edit-btn]');
38
+ if (!btn) return;
39
+ var id = Number(btn.dataset.editBtn);
40
+ var l = LABELS.find(function (x) { return x.id === id; });
41
+ if (!l) return;
42
+ ef.action = btn.form.action;
43
+ ef.querySelector('[name=name]').value = l.name;
44
+ ef.querySelector('[name=color]').value = l.color;
45
+ dlgColor = ef.querySelector('[name=color]');
46
+ ef.querySelector('[name=description]').value = l.description;
47
+ ef.querySelector('[name=exclusive]').checked = l.exclusive === 1;
48
+ bindSwatches(dp, dlgColor);
49
+ dlg.showModal();
50
+ });
51
+
52
+ document.getElementById('dlg-cancel').addEventListener('click', function () { dlg.close(); });
53
+ })();
@@ -1,4 +1,5 @@
1
1
  import { THEME_CSS, escapeHtml, escapeAttr } from "../render/layout";
2
+ import { BUILD_ID } from "../build";
2
3
  import { projectSettingsTabsHTML } from "./projectUpstreams";
3
4
  import { listLabels, type ProjectRow, type UserRow, type LabelRow } from "../store";
4
5
 
@@ -58,7 +59,6 @@ export async function buildProjectLabelsPage(
58
59
  const flashHtml = flash ? `<div class="flash ${flash.kind}">${escapeHtml(flash.msg)}</div>` : "";
59
60
  const createAction = `/${encodeURIComponent(project.owner)}/${encodeURIComponent(project.name)}/settings/labels/add`;
60
61
  const paletteHtml = PALETTE.map((c) => `<button type="button" class="swatch" data-color="${c}" style="background:${c}" aria-label="${c}"></button>`).join("");
61
- const labelsJson = JSON.stringify(labels.map((l) => ({ id: l.id, name: l.name, color: l.color, description: l.description, exclusive: l.exclusive, is_archived: l.is_archived })));
62
62
 
63
63
  return `<!doctype html>
64
64
  <html lang="zh"><head><meta charset="utf-8">
@@ -157,34 +157,7 @@ ${rowsHtml}
157
157
  </div>
158
158
  </form>
159
159
  </dialog>
160
- <script>
161
- const LABELS = ${labelsJson};
162
- const palette = ${JSON.stringify(PALETTE)};
163
- function bindSwatches(container, colorInput){
164
- container.innerHTML = palette.map(c => '<button type="button" class="swatch" data-color="'+c+'" style="background:'+c+'" aria-label="'+c+'"></button>').join('');
165
- const sync = ()=>{ container.querySelectorAll('.swatch').forEach(b=>b.classList.toggle('sel', b.dataset.color.toLowerCase()===colorInput.value.toLowerCase())); };
166
- sync();
167
- container.addEventListener('click', e=>{ const b=e.target.closest('.swatch'); if(!b)return; colorInput.value=b.dataset.color; sync(); });
168
- colorInput.addEventListener('input', sync);
169
- }
170
- bindSwatches(document.getElementById('palette'), document.getElementById('f-color'));
171
- const dlg=document.getElementById('dlg'), ef=document.getElementById('edit-form'), dp=document.getElementById('dlg-palette');
172
- let dlgColor;
173
- document.addEventListener('click', e=>{
174
- const btn=e.target.closest('[data-edit-btn]');
175
- if(!btn)return;
176
- const id=Number(btn.dataset.editBtn);
177
- const l=LABELS.find(x=>x.id===id); if(!l)return;
178
- ef.action=btn.form.action;
179
- ef.querySelector('[name=name]').value=l.name;
180
- ef.querySelector('[name=color]').value=l.color;
181
- dlgColor=ef.querySelector('[name=color]');
182
- ef.querySelector('[name=description]').value=l.description;
183
- ef.querySelector('[name=exclusive]').checked=l.exclusive===1;
184
- bindSwatches(dp, dlgColor);
185
- dlg.showModal();
186
- });
187
- document.getElementById('dlg-cancel').addEventListener('click', ()=>dlg.close());
188
- </script>
160
+ <script type="application/json" id="label-data">${JSON.stringify({ labels: labels.map((l) => ({ id: l.id, name: l.name, color: l.color, description: l.description, exclusive: l.exclusive, is_archived: l.is_archived })), palette: PALETTE })}</script>
161
+ <script src="/static/project-labels.js?v=${BUILD_ID}" defer></script>
189
162
  </body></html>`;
190
163
  }