texlite 0.9.4 → 0.11.0

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 (58) hide show
  1. package/DESIGN.md +12 -14
  2. package/OPERATIONS.md +12 -6
  3. package/README.md +70 -41
  4. package/README.zh-CN.md +59 -34
  5. package/dist/client/assets/{AdminUsers-B12FJUMy.js → AdminUsers-BExOMZsW.js} +1 -1
  6. package/dist/client/assets/{CitationLibraryDialog-BTmnCP4V.js → CitationLibraryDialog-tVD3i2gg.js} +1 -1
  7. package/dist/client/assets/Dashboard-DRt4rjKP.js +2 -0
  8. package/dist/client/assets/{GitDialog--MoUIW0n.js → GitDialog-CjDCi1uh.js} +1 -1
  9. package/dist/client/assets/{HistoryDialog-LgKbjsTZ.js → HistoryDialog-Pa9mK3Ky.js} +1 -1
  10. package/dist/client/assets/{LatexEditor-Dj2zIaQm.js → LatexEditor-8r486VeG.js} +2 -2
  11. package/dist/client/assets/PdfPreview-CMkO72I5.js +1 -0
  12. package/dist/client/assets/{ProjectIconPickerDialog-GzRuM0oQ.js → ProjectIconPickerDialog-B52xbSyr.js} +1 -1
  13. package/dist/client/assets/{ProjectNavigationDialogs-BiArHmQ9.js → ProjectNavigationDialogs-isGMXCfG.js} +1 -1
  14. package/dist/client/assets/ProjectSettings-Br_kJ113.js +1 -0
  15. package/dist/client/assets/ProjectWorkspace-CcbrdCy8.js +32 -0
  16. package/dist/client/assets/{SelectionHistoryDialog-Bpv6nvrC.js → SelectionHistoryDialog-ZvAhIJMy.js} +2 -2
  17. package/dist/client/assets/{SystemMetricsDialog-hJQATmd4.js → SystemMetricsDialog-D4ouU4HI.js} +1 -1
  18. package/dist/client/assets/{TagManagementDialog-BHC8qULq.js → TagManagementDialog-qhZfHgTr.js} +1 -1
  19. package/dist/client/assets/{WordCountDialog-DZhhO94k.js → WordCountDialog-tFhMCGyn.js} +1 -1
  20. package/dist/client/assets/check-DKIW1zpR.js +1 -0
  21. package/dist/client/assets/chktex-tM1XXPk2.js +3 -0
  22. package/dist/client/assets/{hard-drive-BWZnOgnm.js → hard-drive-CkIPwUSV.js} +1 -1
  23. package/dist/client/assets/index-90X2GUd_.css +1 -0
  24. package/dist/client/assets/index-CqBmQAzW.js +10 -0
  25. package/dist/client/assets/{index-kTamGfTl.js → index-DTYEGzB7.js} +1 -1
  26. package/dist/client/assets/{key-round-CoDwyVJc.js → key-round-BEnQo7IM.js} +1 -1
  27. package/dist/client/assets/{minimize-2-D100opW2.js → minimize-2-CLuDohEf.js} +1 -1
  28. package/dist/client/assets/{minus-DDTYB-Oz.js → minus-BpuvzV2m.js} +1 -1
  29. package/dist/client/assets/{plus-BVXOlQXP.js → plus-BFr75l91.js} +1 -1
  30. package/dist/client/assets/{refresh-cw-LtwnqlWh.js → refresh-cw-DyA4NKh7.js} +1 -1
  31. package/dist/client/assets/{search-DXo_aAp0.js → search-Nu4OcY_n.js} +1 -1
  32. package/dist/client/assets/{sigma-BgvHGPto.js → sigma-B-wjepsY.js} +1 -1
  33. package/dist/client/assets/{spellCheck-BtRppKcC.js → spellCheck-C-bNJsIU.js} +1 -1
  34. package/dist/client/assets/{tags-m9kppdi0.js → tags-BSA08jK3.js} +1 -1
  35. package/dist/client/assets/{type-YO4aprqE.js → type-BCdIbDmF.js} +1 -1
  36. package/dist/client/assets/{user-plus-MRmKeDtX.js → user-plus-gFwY-NA3.js} +1 -1
  37. package/dist/client/assets/{x-eisRQJv_.js → x-Cspw_pa4.js} +1 -1
  38. package/dist/client/index.html +2 -2
  39. package/dist/server/app.js +8 -0
  40. package/dist/server/chktex.js +377 -0
  41. package/dist/server/collaboration.js +6 -0
  42. package/dist/server/db.js +6 -1
  43. package/dist/server/environment.js +1 -0
  44. package/dist/server/history.js +4 -4
  45. package/dist/server/i18n.js +12 -2
  46. package/dist/server/projectImportUploads.js +133 -0
  47. package/dist/server/routes/projectFiles.js +3 -0
  48. package/dist/server/routes/projectShared.js +1 -0
  49. package/dist/server/routes/projects.js +87 -15
  50. package/dist/shared/writingChecks.js +4 -0
  51. package/package.json +1 -1
  52. package/dist/client/assets/Dashboard-JA5_M3TK.js +0 -2
  53. package/dist/client/assets/PdfPreview-C_W7w360.js +0 -1
  54. package/dist/client/assets/ProjectSettings-DgJohWUv.js +0 -1
  55. package/dist/client/assets/ProjectWorkspace-C6sYiBF2.js +0 -32
  56. package/dist/client/assets/check-C1db8iNn.js +0 -1
  57. package/dist/client/assets/index-BiwJj0Np.css +0 -1
  58. package/dist/client/assets/index-p1NHkGGG.js +0 -12
@@ -83,6 +83,7 @@ function optionalToolDefinitions(gitCommand, gitTimeoutMs) {
83
83
  { id: "bibtex", name: "BibTeX", command: "bibtex", requirement: "optional", purpose: "BibTeX bibliography builds" },
84
84
  { id: "biber", name: "Biber", command: "biber", requirement: "optional", purpose: "Biber bibliography builds" },
85
85
  { id: "makeindex", name: "MakeIndex", command: "makeindex", requirement: "optional", purpose: "Index generation", versionArgs: [] },
86
+ { id: "chktex", name: "ChkTeX", command: "chktex", requirement: "optional", purpose: "LaTeX writing checks", versionArgs: ["--version"] },
86
87
  { id: "harper-cli", name: "Harper CLI", command: "harper-cli", requirement: "optional", purpose: "TexLite spelling and grammar checks" },
87
88
  { id: "harper-ls", name: "Harper language server", command: "harper-ls", requirement: "optional", purpose: "External editor integration (not used by TexLite)" }
88
89
  ];
@@ -272,8 +272,8 @@ export class ProjectHistoryService {
272
272
  return { restoredPaths: [filePath], manifest };
273
273
  }
274
274
  this.restoreProjectTree(projectId, manifest);
275
- this.db.prepare("UPDATE projects SET main_file = ?, engine = ?, latexmkrc = ? WHERE id = ?")
276
- .run(manifest.settings.mainFile, manifest.settings.engine, manifest.settings.latexmkrc, projectId);
275
+ this.db.prepare("UPDATE projects SET main_file = ?, engine = ?, latexmkrc = ?, chktex_enabled = ? WHERE id = ?")
276
+ .run(manifest.settings.mainFile, manifest.settings.engine, manifest.settings.latexmkrc, manifest.settings.chktexEnabled ? 1 : 0, projectId);
277
277
  return { restoredPaths: Object.keys(manifest.files).sort(), manifest };
278
278
  }
279
279
  assertRestoreTargetIsFile(projectId, filePath) {
@@ -570,7 +570,7 @@ export class ProjectHistoryService {
570
570
  }
571
571
  }
572
572
  function settings(project) {
573
- return { mainFile: project.main_file, engine: project.engine, latexmkrc: project.latexmkrc };
573
+ return { mainFile: project.main_file, engine: project.engine, latexmkrc: project.latexmkrc, chktexEnabled: Boolean(project.chktex_enabled) };
574
574
  }
575
575
  function cloneManifest(manifest) {
576
576
  return { version: 1, files: { ...manifest.files }, settings: { ...manifest.settings } };
@@ -579,7 +579,7 @@ function parseManifest(value) {
579
579
  const parsed = JSON.parse(value);
580
580
  if (parsed.version !== 1 || !parsed.files || !parsed.settings)
581
581
  throw new Error("History manifest is invalid");
582
- return parsed;
582
+ return { ...parsed, settings: { ...parsed.settings, chktexEnabled: parsed.settings.chktexEnabled === true } };
583
583
  }
584
584
  function parseStringArray(value) {
585
585
  try {
@@ -11,6 +11,10 @@ const enMessages = {
11
11
  CITATION_NOT_FOUND: "The citation does not exist.",
12
12
  CITATION_TAG_NOT_FOUND: "The citation tag does not exist.",
13
13
  CITATION_TOO_LARGE: "The BibTeX entry is too large.",
14
+ CHK_TEX_SOURCE_INVALID: "The source to check is invalid.",
15
+ CHK_TEX_SOURCE_TOO_LARGE: "The source to check is too large.",
16
+ CHK_TEX_SUPERSEDED: "A newer ChkTeX check replaced this request.",
17
+ CHK_TEX_UNAVAILABLE: "ChkTeX is not installed or cannot run on this server. Install the TeX Live chktex package and try again.",
14
18
  COMMENT_DELETE_FORBIDDEN: "You can only delete your own comments.",
15
19
  COMMENT_EDIT_FORBIDDEN: "You can only edit your own comments.",
16
20
  COMMENT_FILE_NOT_FOUND: "The file for this comment does not exist.",
@@ -77,6 +81,7 @@ const enMessages = {
77
81
  PROJECT_ICON_INVALID: "Enter a valid installed Lucide icon short name.",
78
82
  PROJECT_NOT_FOUND: "The project does not exist or is no longer accessible.",
79
83
  PROJECT_OWNER_ONLY: "Only the project owner can perform this action.",
84
+ PROJECT_SETTINGS_CONFLICT: "These project settings changed in another session. Refresh the project page before saving again.",
80
85
  PROJECT_TRANSFER_FORBIDDEN: "Only the current project owner can transfer ownership.",
81
86
  PROJECT_TRANSFER_SELF: "You already own this project.",
82
87
  PROJECT_TRANSFER_TARGET_INVALID: "Choose a new project owner.",
@@ -106,7 +111,7 @@ const enMessages = {
106
111
  WORD_COUNT_FAILED: "TeXcount could not count this source.",
107
112
  WORD_COUNT_SOURCE_INVALID: "Select LaTeX source text to count.",
108
113
  WORD_COUNT_SOURCE_TOO_LARGE: "The selected source is too large to count.",
109
- WORD_COUNT_UNAVAILABLE: "TeXcount is not available on this server. Install it from TeX Live and try again.",
114
+ WORD_COUNT_UNAVAILABLE: "The TeX Live texcount package is not installed on this server. Install it and try again.",
110
115
  ZIP_DUPLICATE_ENTRY: "The ZIP archive contains a duplicate filename: {{path}}.",
111
116
  ZIP_ENTRY_READ_FAILED: "A ZIP archive entry could not be read.",
112
117
  ZIP_ENTRY_TOO_LARGE: "The ZIP archive contains a file that exceeds the permitted size.",
@@ -143,6 +148,10 @@ const zhMessages = {
143
148
  CITATION_NOT_FOUND: "引用条目不存在。",
144
149
  CITATION_TAG_NOT_FOUND: "引用标签不存在。",
145
150
  CITATION_TOO_LARGE: "BibTeX 条目过大。",
151
+ CHK_TEX_SOURCE_INVALID: "待检查源码格式不正确。",
152
+ CHK_TEX_SOURCE_TOO_LARGE: "待检查源码过大。",
153
+ CHK_TEX_SUPERSEDED: "更新的 ChkTeX 检查已替代此请求。",
154
+ CHK_TEX_UNAVAILABLE: "服务器未安装 ChkTeX,或 ChkTeX 当前无法运行。请在服务器安装 TeX Live 的 chktex 包后重试。",
146
155
  COMMENT_DELETE_FORBIDDEN: "只能删除自己的批注。",
147
156
  COMMENT_EDIT_FORBIDDEN: "只能编辑自己的批注。",
148
157
  COMMENT_FILE_NOT_FOUND: "批注对应的文件不存在。",
@@ -209,6 +218,7 @@ const zhMessages = {
209
218
  PROJECT_ICON_INVALID: "请输入已安装的有效 Lucide 图标短名称。",
210
219
  PROJECT_NOT_FOUND: "项目不存在或你已无法访问。",
211
220
  PROJECT_OWNER_ONLY: "只有项目所有者可以执行此操作。",
221
+ PROJECT_SETTINGS_CONFLICT: "项目设置已在其他会话中更改。请刷新项目页面后再保存。",
212
222
  PROJECT_TRANSFER_FORBIDDEN: "只有当前项目所有者可以转让项目。",
213
223
  PROJECT_TRANSFER_SELF: "你已经是该项目的所有者。",
214
224
  PROJECT_TRANSFER_TARGET_INVALID: "请选择新的项目所有者。",
@@ -238,7 +248,7 @@ const zhMessages = {
238
248
  WORD_COUNT_FAILED: "TeXcount 无法统计这段源码。",
239
249
  WORD_COUNT_SOURCE_INVALID: "请选择 LaTeX 源码后再统计。",
240
250
  WORD_COUNT_SOURCE_TOO_LARGE: "所选源码过大,无法统计。",
241
- WORD_COUNT_UNAVAILABLE: "服务器未安装 TeXcount,请安装 TeX Live 后重试。",
251
+ WORD_COUNT_UNAVAILABLE: "服务器未安装 TeX Live 的 texcount 包,请安装后重试。",
242
252
  ZIP_DUPLICATE_ENTRY: "ZIP 压缩包包含重复文件名:{{path}}。",
243
253
  ZIP_ENTRY_READ_FAILED: "无法读取 ZIP 压缩包中的文件。",
244
254
  ZIP_ENTRY_TOO_LARGE: "ZIP 压缩包中存在超过限制的文件。",
@@ -0,0 +1,133 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ const CHUNK_SIZE = 4 * 1024 * 1024;
5
+ const SESSION_TTL_MS = 30 * 60 * 1000;
6
+ const CLEANUP_INTERVAL_MS = 5 * 60 * 1000;
7
+ const MAX_ACTIVE_UPLOADS = 8;
8
+ const MAX_ACTIVE_UPLOADS_PER_USER = 2;
9
+ const MAX_IN_FLIGHT_CHUNKS_PER_UPLOAD = 4;
10
+ export class ProjectImportUploadError extends Error {
11
+ code;
12
+ constructor(code) {
13
+ super(code);
14
+ this.code = code;
15
+ this.name = "ProjectImportUploadError";
16
+ }
17
+ }
18
+ /** Bounded, per-user temporary storage for parallel ZIP upload chunks. */
19
+ export class ProjectImportUploads {
20
+ rootDirectory;
21
+ sessions = new Map();
22
+ cleanupTimer;
23
+ constructor(config) {
24
+ this.rootDirectory = path.join(config.dataDir, "tmp", "project-imports");
25
+ this.cleanupTimer = setInterval(() => { void this.cleanupExpired().catch(() => undefined); }, CLEANUP_INTERVAL_MS);
26
+ this.cleanupTimer.unref();
27
+ }
28
+ async initialize() {
29
+ await fs.mkdir(this.rootDirectory, { recursive: true, mode: 0o700 });
30
+ // Any remaining entries are abandoned uploads from a previous server run.
31
+ const entries = await fs.readdir(this.rootDirectory, { withFileTypes: true });
32
+ await Promise.all(entries.map((entry) => fs.rm(path.join(this.rootDirectory, entry.name), { recursive: true, force: true })));
33
+ }
34
+ async start(userId, filename, size, maxBytes) {
35
+ await this.cleanupExpired();
36
+ if (!Number.isSafeInteger(size) || size <= 0 || size > maxBytes)
37
+ throw new ProjectImportUploadError("ZIP_UPLOAD_SIZE_INVALID");
38
+ const active = [...this.sessions.values()];
39
+ const reservedBytes = active.reduce((sum, session) => sum + session.size, 0);
40
+ if (active.length >= MAX_ACTIVE_UPLOADS || active.filter((session) => session.userId === userId).length >= MAX_ACTIVE_UPLOADS_PER_USER || reservedBytes + size > maxBytes * 2) {
41
+ throw new ProjectImportUploadError("ZIP_UPLOAD_LIMIT");
42
+ }
43
+ const id = randomUUID();
44
+ const directory = path.join(this.rootDirectory, id);
45
+ await fs.mkdir(directory, { recursive: false, mode: 0o700 });
46
+ const chunkCount = Math.ceil(size / CHUNK_SIZE);
47
+ const timestamp = Date.now();
48
+ this.sessions.set(id, {
49
+ id, userId, filename, size, chunkCount, directory, received: new Set(), inFlight: new Set(),
50
+ updatedAt: timestamp, completing: false
51
+ });
52
+ return { uploadId: id, chunkSize: CHUNK_SIZE, chunkCount };
53
+ }
54
+ async writeChunk(userId, uploadId, index, bytes) {
55
+ const session = this.getSession(userId, uploadId);
56
+ if (!Number.isSafeInteger(index) || index < 0 || index >= session.chunkCount || session.completing) {
57
+ throw new ProjectImportUploadError("ZIP_UPLOAD_CHUNK_INVALID");
58
+ }
59
+ if (session.inFlight.size >= MAX_IN_FLIGHT_CHUNKS_PER_UPLOAD)
60
+ throw new ProjectImportUploadError("ZIP_UPLOAD_LIMIT");
61
+ if (session.received.has(index) || session.inFlight.has(index))
62
+ throw new ProjectImportUploadError("ZIP_UPLOAD_CHUNK_DUPLICATE");
63
+ const expectedSize = Math.min(CHUNK_SIZE, session.size - index * CHUNK_SIZE);
64
+ if (bytes.length !== expectedSize)
65
+ throw new ProjectImportUploadError("ZIP_UPLOAD_CHUNK_INVALID");
66
+ session.inFlight.add(index);
67
+ try {
68
+ await fs.writeFile(path.join(session.directory, `${index}.part`), bytes, { flag: "wx", mode: 0o600 });
69
+ session.received.add(index);
70
+ session.updatedAt = Date.now();
71
+ }
72
+ finally {
73
+ session.inFlight.delete(index);
74
+ }
75
+ }
76
+ async assemble(userId, uploadId) {
77
+ const session = this.getSession(userId, uploadId);
78
+ if (session.completing || session.inFlight.size > 0 || session.received.size !== session.chunkCount) {
79
+ throw new ProjectImportUploadError("ZIP_UPLOAD_INCOMPLETE");
80
+ }
81
+ session.completing = true;
82
+ try {
83
+ const buffer = Buffer.allocUnsafe(session.size);
84
+ let offset = 0;
85
+ for (let index = 0; index < session.chunkCount; index += 1) {
86
+ const chunk = await fs.readFile(path.join(session.directory, `${index}.part`));
87
+ if (chunk.length !== Math.min(CHUNK_SIZE, session.size - offset))
88
+ throw new ProjectImportUploadError("ZIP_UPLOAD_CHUNK_INVALID");
89
+ chunk.copy(buffer, offset);
90
+ offset += chunk.length;
91
+ }
92
+ if (offset !== session.size)
93
+ throw new ProjectImportUploadError("ZIP_UPLOAD_CHUNK_INVALID");
94
+ return { filename: session.filename, buffer };
95
+ }
96
+ catch (error) {
97
+ if (error instanceof ProjectImportUploadError)
98
+ throw error;
99
+ throw new ProjectImportUploadError("ZIP_UPLOAD_INCOMPLETE");
100
+ }
101
+ finally {
102
+ await this.removeSession(session);
103
+ }
104
+ }
105
+ async cancel(userId, uploadId) {
106
+ const session = this.sessions.get(uploadId);
107
+ if (!session || session.userId !== userId || session.completing || session.inFlight.size > 0)
108
+ return;
109
+ await this.removeSession(session);
110
+ }
111
+ async close() {
112
+ clearInterval(this.cleanupTimer);
113
+ await Promise.all([...this.sessions.values()].map((session) => this.removeSession(session)));
114
+ }
115
+ getSession(userId, uploadId) {
116
+ const session = this.sessions.get(uploadId);
117
+ if (!session || session.userId !== userId || Date.now() - session.updatedAt > SESSION_TTL_MS) {
118
+ if (session && Date.now() - session.updatedAt > SESSION_TTL_MS)
119
+ void this.removeSession(session).catch(() => undefined);
120
+ throw new ProjectImportUploadError("ZIP_UPLOAD_NOT_FOUND");
121
+ }
122
+ return session;
123
+ }
124
+ async cleanupExpired() {
125
+ const expired = [...this.sessions.values()].filter((session) => !session.completing && session.inFlight.size === 0 && Date.now() - session.updatedAt > SESSION_TTL_MS);
126
+ await Promise.all(expired.map((session) => this.removeSession(session)));
127
+ }
128
+ async removeSession(session) {
129
+ if (this.sessions.get(session.id) === session)
130
+ this.sessions.delete(session.id);
131
+ await fs.rm(session.directory, { recursive: true, force: true });
132
+ }
133
+ }
@@ -477,6 +477,9 @@ export function registerProjectFileRoutes(app, context) {
477
477
  throw error;
478
478
  }
479
479
  fs.rmSync(absolute, { recursive: true, force: true });
480
+ if (currentProject.latexmkrc === relative || currentProject.latexmkrc?.startsWith(`${relative}/`)) {
481
+ db.prepare("UPDATE projects SET latexmkrc = NULL WHERE id = ?").run(id);
482
+ }
480
483
  const deleteResult = db.prepare("DELETE FROM comments WHERE project_id = ? AND (file_path = ? OR file_path GLOB ?)").run(id, relative, `${escapeGlobPattern(relative)}/*`);
481
484
  touchProject(db, id, user.id);
482
485
  collaboration.removePath(id, relative);
@@ -97,6 +97,7 @@ export function projectJson(project, tags = [], commentsSummary, unreadMentionCo
97
97
  latexmkrc: project.latexmkrc,
98
98
  engine: project.engine,
99
99
  icon: project.icon,
100
+ chktexEnabled: Boolean(project.chktex_enabled),
100
101
  permission: project.permission,
101
102
  tags,
102
103
  unresolvedCommentCount: commentsSummary?.unresolvedCount ?? 0,
@@ -11,14 +11,15 @@ import { accessibleProject, canEdit } from "../projects.js";
11
11
  import { writeProjectArchive } from "../archive.js";
12
12
  import { extractProjectZip, ZipValidationError } from "../zip.js";
13
13
  import { HarperLintSupersededError, HarperUnavailableError } from "../harper.js";
14
+ import { ChktexLintSupersededError, ChktexUnavailableError } from "../chktex.js";
14
15
  import { digestToken } from "../security.js";
15
- import { supportsWritingChecks } from "../../shared/writingChecks.js";
16
+ import { supportsChktexChecks, supportsWritingChecks } from "../../shared/writingChecks.js";
16
17
  import { unreadMentionCountsForProjects } from "../commentMentions.js";
17
18
  import { commentsSummaryForProject, commentsSummaryForProjects, dictionaryWord, escapeLikePattern, now, projectJson, requireProjectOwnerPermission, tagColors, tagsForProject, tagsForProjects, text } from "./projectShared.js";
18
19
  const clientIdPattern = /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i;
19
20
  /** Register project catalog, metadata, archive, dictionary, tag, export, and deletion routes. */
20
21
  export function registerProjectCatalogRoutes(app, context) {
21
- const { config, db, collaboration, projectMutations, latexCompletions, projectOutlines, harper, recordHistory } = context;
22
+ const { config, db, collaboration, projectMutations, latexCompletions, projectOutlines, harper, chktex, recordHistory } = context;
22
23
  // Advanced project icons are served as cacheable SVG masks. The browser
23
24
  // therefore does not have to bundle Lucide's entire icon catalogue merely
24
25
  // because an owner selected an icon outside the curated picker.
@@ -192,12 +193,12 @@ export function registerProjectCatalogRoutes(app, context) {
192
193
  const body = request.body;
193
194
  const project = {
194
195
  id: randomUUID(), owner_id: user.id, last_modified_by: user.id, name: text(body?.name, 120),
195
- main_file: "main.tex", latexmkrc: null, engine: config.defaultEngine, icon: null, created_at: now(), updated_at: now()
196
+ main_file: "main.tex", latexmkrc: null, engine: config.defaultEngine, icon: null, chktex_enabled: 0, created_at: now(), updated_at: now()
196
197
  };
197
198
  createProjectFiles(config, project.id);
198
199
  try {
199
- db.prepare(`INSERT INTO projects (id, owner_id, last_modified_by, name, main_file, latexmkrc, engine, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
200
- .run(project.id, project.owner_id, project.last_modified_by, project.name, project.main_file, project.latexmkrc, project.engine, project.created_at, project.updated_at);
200
+ db.prepare(`INSERT INTO projects (id, owner_id, last_modified_by, name, main_file, latexmkrc, engine, chktex_enabled, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
201
+ .run(project.id, project.owner_id, project.last_modified_by, project.name, project.main_file, project.latexmkrc, project.engine, project.chktex_enabled, project.created_at, project.updated_at);
201
202
  }
202
203
  catch (error) {
203
204
  removeProjectDirectory(config, project.id);
@@ -224,16 +225,16 @@ export function registerProjectCatalogRoutes(app, context) {
224
225
  const fallbackName = path.basename(part.filename, path.extname(part.filename));
225
226
  const project = {
226
227
  id: randomUUID(), owner_id: user.id, last_modified_by: user.id, name: text(query.name || fallbackName, 120),
227
- main_file: "", latexmkrc: null, engine: config.defaultEngine, icon: null, created_at: now(), updated_at: now()
228
+ main_file: "", latexmkrc: null, engine: config.defaultEngine, icon: null, chktex_enabled: 0, created_at: now(), updated_at: now()
228
229
  };
229
230
  fs.mkdirSync(sourceRoot(config, project.id), { recursive: true, mode: 0o700 });
230
231
  fs.mkdirSync(outputRoot(config, project.id), { recursive: true, mode: 0o700 });
231
232
  try {
232
233
  const extracted = await extractProjectZip(await part.toBuffer(), sourceRoot(config, project.id), config.maxUploadBytes);
233
234
  project.main_file = extracted.mainFile;
234
- db.prepare(`INSERT INTO projects (id, owner_id, last_modified_by, name, main_file, latexmkrc, engine, created_at, updated_at)
235
- VALUES (?, ?, ?, ?, ?, NULL, ?, ?, ?)`)
236
- .run(project.id, project.owner_id, project.last_modified_by, project.name, project.main_file, project.engine, project.created_at, project.updated_at);
235
+ db.prepare(`INSERT INTO projects (id, owner_id, last_modified_by, name, main_file, latexmkrc, engine, chktex_enabled, created_at, updated_at)
236
+ VALUES (?, ?, ?, ?, ?, NULL, ?, ?, ?, ?)`)
237
+ .run(project.id, project.owner_id, project.last_modified_by, project.name, project.main_file, project.engine, project.chktex_enabled, project.created_at, project.updated_at);
237
238
  }
238
239
  catch (error) {
239
240
  removeProjectDirectory(config, project.id);
@@ -262,7 +263,7 @@ export function registerProjectCatalogRoutes(app, context) {
262
263
  const requestedName = typeof body?.name === "string" && body.name.trim() ? body.name : `${source.name.slice(0, 115)} (1)`;
263
264
  const project = {
264
265
  id: randomUUID(), owner_id: user.id, last_modified_by: user.id, name: text(requestedName, 120),
265
- main_file: source.main_file, latexmkrc: source.latexmkrc, engine: source.engine, icon: source.icon, created_at: now(), updated_at: now()
266
+ main_file: source.main_file, latexmkrc: source.latexmkrc, engine: source.engine, icon: source.icon, chktex_enabled: source.chktex_enabled, created_at: now(), updated_at: now()
266
267
  };
267
268
  try {
268
269
  // Duplicate the source tree only after flushing the live Yjs room and
@@ -276,9 +277,9 @@ export function registerProjectCatalogRoutes(app, context) {
276
277
  }
277
278
  }
278
279
  });
279
- db.prepare(`INSERT INTO projects (id, owner_id, last_modified_by, name, main_file, latexmkrc, engine, icon, created_at, updated_at)
280
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
281
- .run(project.id, project.owner_id, project.last_modified_by, project.name, project.main_file, project.latexmkrc, project.engine, project.icon, project.created_at, project.updated_at);
280
+ db.prepare(`INSERT INTO projects (id, owner_id, last_modified_by, name, main_file, latexmkrc, engine, icon, chktex_enabled, created_at, updated_at)
281
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
282
+ .run(project.id, project.owner_id, project.last_modified_by, project.name, project.main_file, project.latexmkrc, project.engine, project.icon, project.chktex_enabled, project.created_at, project.updated_at);
282
283
  }
283
284
  catch (error) {
284
285
  removeProjectDirectory(config, project.id);
@@ -417,6 +418,58 @@ export function registerProjectCatalogRoutes(app, context) {
417
418
  return apiError(reply, 503, "HARPER_UNAVAILABLE");
418
419
  }
419
420
  });
421
+ app.post("/api/projects/:id/chktex", async (request, reply) => {
422
+ const user = requireUser(request, reply, db);
423
+ if (!user)
424
+ return;
425
+ const { id } = request.params;
426
+ const project = accessibleProject(db, id, user);
427
+ if (!project)
428
+ return apiError(reply, 404, "PROJECT_NOT_FOUND");
429
+ if (!project.chktex_enabled)
430
+ return { issues: [] };
431
+ const body = request.body;
432
+ const source = body?.source;
433
+ const sourcePath = body?.path;
434
+ const clientId = body?.clientId;
435
+ const sequence = body?.sequence;
436
+ if (typeof source !== "string" || typeof sourcePath !== "string")
437
+ return apiError(reply, 400, "CHK_TEX_SOURCE_INVALID");
438
+ if (clientId !== undefined && (typeof clientId !== "string" || !clientIdPattern.test(clientId))) {
439
+ return apiError(reply, 400, "CHK_TEX_SOURCE_INVALID");
440
+ }
441
+ if (sequence !== undefined && (typeof sequence !== "number" || !Number.isSafeInteger(sequence) || sequence <= 0 || clientId === undefined)) {
442
+ return apiError(reply, 400, "CHK_TEX_SOURCE_INVALID");
443
+ }
444
+ if (Buffer.byteLength(source, "utf8") > maxCollaborativeFileBytes(config)) {
445
+ return apiError(reply, 413, "CHK_TEX_SOURCE_TOO_LARGE");
446
+ }
447
+ let filePath;
448
+ try {
449
+ filePath = safeRelativePath(sourcePath);
450
+ }
451
+ catch {
452
+ return apiError(reply, 400, "CHK_TEX_SOURCE_INVALID");
453
+ }
454
+ if (!supportsChktexChecks(filePath))
455
+ return { issues: [] };
456
+ const laneClientId = clientId ?? `legacy:${digestToken(request.cookies.texlite_session ?? "")}`;
457
+ const lane = `${id}\0${user.id}\0${laneClientId}\0${filePath}`;
458
+ try {
459
+ return { issues: await chktex.lint(source, filePath, lane, typeof sequence === "number" ? sequence : undefined) };
460
+ }
461
+ catch (error) {
462
+ if (error instanceof ChktexLintSupersededError)
463
+ return apiError(reply, 409, "CHK_TEX_SUPERSEDED");
464
+ if (error instanceof ChktexUnavailableError) {
465
+ request.log.debug({ projectId: id }, "Optional ChkTeX command unavailable");
466
+ }
467
+ else {
468
+ request.log.error({ err: error, projectId: id }, "ChkTeX writing check failed");
469
+ }
470
+ return apiError(reply, 503, "CHK_TEX_UNAVAILABLE");
471
+ }
472
+ });
420
473
  app.post("/api/projects/:id/dictionary", async (request, reply) => {
421
474
  const user = requireUser(request, reply, db);
422
475
  if (!user)
@@ -469,6 +522,21 @@ export function registerProjectCatalogRoutes(app, context) {
469
522
  if (!currentProject || currentProject.permission !== "owner") {
470
523
  return apiError(reply, 403, "PROJECT_OWNER_ONLY");
471
524
  }
525
+ if (body.expectedSettings !== undefined) {
526
+ if (!body.expectedSettings || typeof body.expectedSettings !== "object" || Array.isArray(body.expectedSettings)) {
527
+ return apiError(reply, 400, "REQUEST_INVALID");
528
+ }
529
+ const expected = body.expectedSettings;
530
+ const currentSettings = {
531
+ name: currentProject.name, mainFile: currentProject.main_file, engine: currentProject.engine,
532
+ latexmkrc: currentProject.latexmkrc, chktexEnabled: Boolean(currentProject.chktex_enabled)
533
+ };
534
+ for (const field of Object.keys(currentSettings)) {
535
+ if (Object.hasOwn(body, field) && (!Object.hasOwn(expected, field) || expected[field] !== currentSettings[field])) {
536
+ return apiError(reply, 409, "PROJECT_SETTINGS_CONFLICT");
537
+ }
538
+ }
539
+ }
472
540
  const name = typeof body.name === "string" ? text(body.name, 120) : currentProject.name;
473
541
  let mainFile = currentProject.main_file;
474
542
  if (body.mainFile !== undefined) {
@@ -479,6 +547,7 @@ export function registerProjectCatalogRoutes(app, context) {
479
547
  }
480
548
  const engine = typeof body.engine === "string" && config.allowedEngines.includes(body.engine)
481
549
  ? body.engine : currentProject.engine;
550
+ const chktexEnabled = typeof body.chktexEnabled === "boolean" ? body.chktexEnabled : Boolean(currentProject.chktex_enabled);
482
551
  const latexmkrc = body.latexmkrc === null || body.latexmkrc === ""
483
552
  ? null
484
553
  : typeof body.latexmkrc === "string" ? safeRelativePath(body.latexmkrc) : currentProject.latexmkrc;
@@ -520,8 +589,11 @@ export function registerProjectCatalogRoutes(app, context) {
520
589
  return apiError(reply, 400, "LATEXMKRC_INVALID", { path: latexmkrc });
521
590
  }
522
591
  }
523
- db.prepare("UPDATE projects SET name = ?, main_file = ?, latexmkrc = ?, engine = ?, updated_at = ?, last_modified_by = ? WHERE id = ?")
524
- .run(name, mainFile, latexmkrc, engine, now(), user.id, id);
592
+ const chktexSettingChanged = Boolean(currentProject.chktex_enabled) !== chktexEnabled;
593
+ db.prepare("UPDATE projects SET name = ?, main_file = ?, latexmkrc = ?, engine = ?, chktex_enabled = ?, updated_at = ?, last_modified_by = ? WHERE id = ?")
594
+ .run(name, mainFile, latexmkrc, engine, chktexEnabled ? 1 : 0, now(), user.id, id);
595
+ if (chktexSettingChanged)
596
+ collaboration.signalChktexSetting(id);
525
597
  recordHistory(id, user.id, "settings", []);
526
598
  return {
527
599
  project: projectJson(accessibleProject(db, id, user), tagsForProject(db, id, user.id), commentsSummaryForProject(db, id))
@@ -2,3 +2,7 @@
2
2
  export function supportsWritingChecks(path) {
3
3
  return !/\.(?:bib|bst)$/i.test(path.trim());
4
4
  }
5
+ /** ChkTeX checks LaTeX source files, not arbitrary project assets. */
6
+ export function supportsChktexChecks(path) {
7
+ return /\.(?:tex|sty|cls)$/i.test(path.trim());
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "texlite",
3
- "version": "0.9.4",
3
+ "version": "0.11.0",
4
4
  "description": "A lightweight local-first collaborative LaTeX workspace",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -1,2 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./SystemMetricsDialog-hJQATmd4.js","./react-BVUjbTTn.js","./index-p1NHkGGG.js","./index-BiwJj0Np.css","./refresh-cw-LtwnqlWh.js","./hard-drive-BWZnOgnm.js","./x-eisRQJv_.js","./sigma-BgvHGPto.js","./tags-m9kppdi0.js","./CitationLibraryDialog-BTmnCP4V.js","./citationLibrary-ZeQPCv5O.js","./plus-BVXOlQXP.js","./check-C1db8iNn.js","./search-DXo_aAp0.js","./AdminUsers-B12FJUMy.js","./user-plus-MRmKeDtX.js","./key-round-CoDwyVJc.js","./TagManagementDialog-BHC8qULq.js","./ProjectIconPickerDialog-GzRuM0oQ.js"])))=>i.map(i=>d[i]);
2
- import{c as r,a as Je,s as Fe,S as ha,L as ma,b as Tt,M as se,d as _e,e as Ze,C as ua,f as ya,g as C,h as D,_ as me,i as Ke}from"./index-p1NHkGGG.js";import{j as e,r as i,u as ka}from"./react-BVUjbTTn.js";import{H as ga}from"./hard-drive-BWZnOgnm.js";import{G as ja,F as xa,L as fa,N as va,a as ba,b as Ma,c as Ca,S as wa,B as Et,d as Na,e as za,P as Sa,D as Ta,T as Pa,M as Ft,A as It,f as Pt,U as Aa,g as At,h as La,C as Lt,i as Da,j as Ea,X as Fa,k as Ia}from"./x-eisRQJv_.js";import{S as qa}from"./sigma-BgvHGPto.js";import{T as We}from"./tags-m9kppdi0.js";const $a=r("Activity",[["path",{d:"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2",key:"169zse"}]]);const Ba=r("ArchiveRestore",[["rect",{width:"20",height:"5",x:"2",y:"3",rx:"1",key:"1wp1u1"}],["path",{d:"M4 8v11a2 2 0 0 0 2 2h2",key:"tvwodi"}],["path",{d:"M20 8v11a2 2 0 0 1-2 2h-2",key:"1gkqxj"}],["path",{d:"m9 15 3-3 3 3",key:"1pd0qc"}],["path",{d:"M12 12v9",key:"192myk"}]]);const Qe=r("Archive",[["rect",{width:"20",height:"5",x:"2",y:"3",rx:"1",key:"1wp1u1"}],["path",{d:"M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8",key:"1s80jp"}],["path",{d:"M10 12h4",key:"a56b0p"}]]);const Va=r("ArrowDownUp",[["path",{d:"m3 16 4 4 4-4",key:"1co6wj"}],["path",{d:"M7 20V4",key:"1yoxec"}],["path",{d:"m21 8-4-4-4 4",key:"1c9v7m"}],["path",{d:"M17 4v16",key:"7dpous"}]]);const qt=r("ArrowRightLeft",[["path",{d:"m16 3 4 4-4 4",key:"1x1c3m"}],["path",{d:"M20 7H4",key:"zbl0bi"}],["path",{d:"m8 21-4-4 4-4",key:"h9nckh"}],["path",{d:"M4 17h16",key:"g4d7ey"}]]);const Oa=r("Atom",[["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}],["path",{d:"M20.2 20.2c2.04-2.03.02-7.36-4.5-11.9-4.54-4.52-9.87-6.54-11.9-4.5-2.04 2.03-.02 7.36 4.5 11.9 4.54 4.52 9.87 6.54 11.9 4.5Z",key:"1l2ple"}],["path",{d:"M15.7 15.7c4.52-4.54 6.54-9.87 4.5-11.9-2.03-2.04-7.36-.02-11.9 4.5-4.52 4.54-6.54 9.87-4.5 11.9 2.03 2.04 7.36.02 11.9-4.5Z",key:"1wam0m"}]]);const Ua=r("Beaker",[["path",{d:"M4.5 3h15",key:"c7n0jr"}],["path",{d:"M6 3v16a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V3",key:"m1uhx7"}],["path",{d:"M6 14h12",key:"4cwo0f"}]]);const Ha=r("Bookmark",[["path",{d:"m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z",key:"1fy3hk"}]]);const Ra=r("Bot",[["path",{d:"M12 8V4H8",key:"hb8ula"}],["rect",{width:"16",height:"12",x:"4",y:"8",rx:"2",key:"enze0r"}],["path",{d:"M2 14h2",key:"vft8re"}],["path",{d:"M20 14h2",key:"4cs60a"}],["path",{d:"M15 13v2",key:"1xurst"}],["path",{d:"M9 13v2",key:"rq6x2g"}]]);const _a=r("Braces",[["path",{d:"M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1",key:"ezmyqa"}],["path",{d:"M16 21h1a2 2 0 0 0 2-2v-5c0-1.1.9-2 2-2a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1",key:"e1hn23"}]]);const Za=r("BrainCircuit",[["path",{d:"M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z",key:"l5xja"}],["path",{d:"M9 13a4.5 4.5 0 0 0 3-4",key:"10igwf"}],["path",{d:"M6.003 5.125A3 3 0 0 0 6.401 6.5",key:"105sqy"}],["path",{d:"M3.477 10.896a4 4 0 0 1 .585-.396",key:"ql3yin"}],["path",{d:"M6 18a4 4 0 0 1-1.967-.516",key:"2e4loj"}],["path",{d:"M12 13h4",key:"1ku699"}],["path",{d:"M12 18h6a2 2 0 0 1 2 2v1",key:"105ag5"}],["path",{d:"M12 8h8",key:"1lhi5i"}],["path",{d:"M16 8V5a2 2 0 0 1 2-2",key:"u6izg6"}],["circle",{cx:"16",cy:"13",r:".5",key:"ry7gng"}],["circle",{cx:"18",cy:"3",r:".5",key:"1aiba7"}],["circle",{cx:"20",cy:"21",r:".5",key:"yhc1fs"}],["circle",{cx:"20",cy:"8",r:".5",key:"1e43v0"}]]);const Ka=r("Brain",[["path",{d:"M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z",key:"l5xja"}],["path",{d:"M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z",key:"ep3f8r"}],["path",{d:"M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4",key:"1p4c4q"}],["path",{d:"M17.599 6.5a3 3 0 0 0 .399-1.375",key:"tmeiqw"}],["path",{d:"M6.003 5.125A3 3 0 0 0 6.401 6.5",key:"105sqy"}],["path",{d:"M3.477 10.896a4 4 0 0 1 .585-.396",key:"ql3yin"}],["path",{d:"M19.938 10.5a4 4 0 0 1 .585.396",key:"1qfode"}],["path",{d:"M6 18a4 4 0 0 1-1.967-.516",key:"2e4loj"}],["path",{d:"M19.967 17.484A4 4 0 0 1 18 18",key:"159ez6"}]]);const Wa=r("Calculator",[["rect",{width:"16",height:"20",x:"4",y:"2",rx:"2",key:"1nb95v"}],["line",{x1:"8",x2:"16",y1:"6",y2:"6",key:"x4nwl0"}],["line",{x1:"16",x2:"16",y1:"14",y2:"18",key:"wjye3r"}],["path",{d:"M16 10h.01",key:"1m94wz"}],["path",{d:"M12 10h.01",key:"1nrarc"}],["path",{d:"M8 10h.01",key:"19clt8"}],["path",{d:"M12 14h.01",key:"1etili"}],["path",{d:"M8 14h.01",key:"6423bh"}],["path",{d:"M12 18h.01",key:"mhygvu"}],["path",{d:"M8 18h.01",key:"lrp35t"}]]);const $t=r("CalendarDays",[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M16 2v4",key:"4m81vk"}],["rect",{width:"18",height:"18",x:"3",y:"4",rx:"2",key:"1hopcy"}],["path",{d:"M3 10h18",key:"8toen8"}],["path",{d:"M8 14h.01",key:"6423bh"}],["path",{d:"M12 14h.01",key:"1etili"}],["path",{d:"M16 14h.01",key:"1gbofw"}],["path",{d:"M8 18h.01",key:"lrp35t"}],["path",{d:"M12 18h.01",key:"mhygvu"}],["path",{d:"M16 18h.01",key:"kzsmim"}]]);const Ga=r("ChartColumn",[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16",key:"c24i48"}],["path",{d:"M18 17V9",key:"2bz60n"}],["path",{d:"M13 17V5",key:"1frdt8"}],["path",{d:"M8 17v-3",key:"17ska0"}]]);const Ja=r("ChartLine",[["path",{d:"M3 3v16a2 2 0 0 0 2 2h16",key:"c24i48"}],["path",{d:"m19 9-5 5-4-4-3 3",key:"2osh9i"}]]);const Qa=r("ChartNoAxesCombined",[["path",{d:"M12 16v5",key:"zza2cw"}],["path",{d:"M16 14v7",key:"1g90b9"}],["path",{d:"M20 10v11",key:"1iqoj0"}],["path",{d:"m22 3-8.646 8.646a.5.5 0 0 1-.708 0L9.354 8.354a.5.5 0 0 0-.707 0L2 15",key:"1fw8x9"}],["path",{d:"M4 18v3",key:"1yp0dc"}],["path",{d:"M8 14v7",key:"n3cwzv"}]]);const Xa=r("ChartPie",[["path",{d:"M21 12c.552 0 1.005-.449.95-.998a10 10 0 0 0-8.953-8.951c-.55-.055-.998.398-.998.95v8a1 1 0 0 0 1 1z",key:"pzmjnu"}],["path",{d:"M21.21 15.89A10 10 0 1 1 8 2.83",key:"k2fpak"}]]);const Ya=r("ChartScatter",[["circle",{cx:"7.5",cy:"7.5",r:".5",fill:"currentColor",key:"kqv944"}],["circle",{cx:"18.5",cy:"5.5",r:".5",fill:"currentColor",key:"lysivs"}],["circle",{cx:"11.5",cy:"11.5",r:".5",fill:"currentColor",key:"byv1b8"}],["circle",{cx:"7.5",cy:"16.5",r:".5",fill:"currentColor",key:"nkw3mc"}],["circle",{cx:"17.5",cy:"14.5",r:".5",fill:"currentColor",key:"1gjh6j"}],["path",{d:"M3 3v16a2 2 0 0 0 2 2h16",key:"c24i48"}]]);const es=r("CircuitBoard",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M11 9h4a2 2 0 0 0 2-2V3",key:"1ve2rv"}],["circle",{cx:"9",cy:"9",r:"2",key:"af1f0g"}],["path",{d:"M7 21v-4a2 2 0 0 1 2-2h4",key:"1fwkro"}],["circle",{cx:"15",cy:"15",r:"2",key:"3i40o0"}]]);const ts=r("Cloud",[["path",{d:"M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z",key:"p7xjir"}]]);const as=r("CodeXml",[["path",{d:"m18 16 4-4-4-4",key:"1inbqp"}],["path",{d:"m6 8-4 4 4 4",key:"15zrgr"}],["path",{d:"m14.5 4-5 16",key:"e7oirm"}]]);const ss=r("Copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]);const ns=r("Cpu",[["rect",{width:"16",height:"16",x:"4",y:"4",rx:"2",key:"14l7u7"}],["rect",{width:"6",height:"6",x:"9",y:"9",rx:"1",key:"5aljv4"}],["path",{d:"M15 2v2",key:"13l42r"}],["path",{d:"M15 20v2",key:"15mkzm"}],["path",{d:"M2 15h2",key:"1gxd5l"}],["path",{d:"M2 9h2",key:"1bbxkp"}],["path",{d:"M20 15h2",key:"19e6y8"}],["path",{d:"M20 9h2",key:"19tzq7"}],["path",{d:"M9 2v2",key:"165o2o"}],["path",{d:"M9 20v2",key:"i2bqo8"}]]);const rs=r("Database",[["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3",key:"msslwz"}],["path",{d:"M3 5V19A9 3 0 0 0 21 19V5",key:"1wlel7"}],["path",{d:"M3 12A9 3 0 0 0 21 12",key:"mv7ke4"}]]);const os=r("Dna",[["path",{d:"m10 16 1.5 1.5",key:"11lckj"}],["path",{d:"m14 8-1.5-1.5",key:"1ohn8i"}],["path",{d:"M15 2c-1.798 1.998-2.518 3.995-2.807 5.993",key:"80uv8i"}],["path",{d:"m16.5 10.5 1 1",key:"696xn5"}],["path",{d:"m17 6-2.891-2.891",key:"xu6p2f"}],["path",{d:"M2 15c6.667-6 13.333 0 20-6",key:"1pyr53"}],["path",{d:"m20 9 .891.891",key:"3xwk7g"}],["path",{d:"M3.109 14.109 4 15",key:"q76aoh"}],["path",{d:"m6.5 12.5 1 1",key:"cs35ky"}],["path",{d:"m7 18 2.891 2.891",key:"1sisit"}],["path",{d:"M9 22c1.798-1.998 2.518-3.995 2.807-5.993",key:"q3hbxp"}]]);const is=r("Earth",[["path",{d:"M21.54 15H17a2 2 0 0 0-2 2v4.54",key:"1djwo0"}],["path",{d:"M7 3.34V5a3 3 0 0 0 3 3a2 2 0 0 1 2 2c0 1.1.9 2 2 2a2 2 0 0 0 2-2c0-1.1.9-2 2-2h3.17",key:"1tzkfa"}],["path",{d:"M11 21.95V18a2 2 0 0 0-2-2a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05",key:"14pb5j"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]]);const cs=r("Ellipsis",[["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}],["circle",{cx:"19",cy:"12",r:"1",key:"1wjl8i"}],["circle",{cx:"5",cy:"12",r:"1",key:"1pcz8c"}]]);const ls=r("FileChartColumnIncreasing",[["path",{d:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z",key:"1rqfz7"}],["path",{d:"M14 2v4a2 2 0 0 0 2 2h4",key:"tnqrlb"}],["path",{d:"M8 18v-2",key:"qcmpov"}],["path",{d:"M12 18v-4",key:"q1q25u"}],["path",{d:"M16 18v-6",key:"15y0np"}]]);const ds=r("FilePenLine",[["path",{d:"m18 5-2.414-2.414A2 2 0 0 0 14.172 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2",key:"142zxg"}],["path",{d:"M21.378 12.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z",key:"2t3380"}],["path",{d:"M8 18h1",key:"13wk12"}]]);const ps=r("FileStack",[["path",{d:"M21 7h-3a2 2 0 0 1-2-2V2",key:"9rb54x"}],["path",{d:"M21 6v6.5c0 .8-.7 1.5-1.5 1.5h-7c-.8 0-1.5-.7-1.5-1.5v-9c0-.8.7-1.5 1.5-1.5H17Z",key:"1059l0"}],["path",{d:"M7 8v8.8c0 .3.2.6.4.8.2.2.5.4.8.4H15",key:"16874u"}],["path",{d:"M3 12v8.8c0 .3.2.6.4.8.2.2.5.4.8.4H11",key:"k2ox98"}]]);const hs=r("FlaskConical",[["path",{d:"M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2",key:"18mbvz"}],["path",{d:"M6.453 15h11.094",key:"3shlmq"}],["path",{d:"M8.5 2h7",key:"csnxdl"}]]);const ms=r("FolderArchive",[["circle",{cx:"15",cy:"19",r:"2",key:"u2pros"}],["path",{d:"M20.9 19.8A2 2 0 0 0 22 18V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h5.1",key:"1jj40k"}],["path",{d:"M15 11v-1",key:"cntcp"}],["path",{d:"M15 17v-2",key:"1279jj"}]]);const us=r("FolderGit2",[["path",{d:"M9 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v5",key:"1w6njk"}],["circle",{cx:"13",cy:"12",r:"2",key:"1j92g6"}],["path",{d:"M18 19c-2.8 0-5-2.2-5-5v8",key:"pkpw2h"}],["circle",{cx:"20",cy:"19",r:"2",key:"1obnsp"}]]);const ys=r("FolderKanban",[["path",{d:"M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z",key:"1fr9dc"}],["path",{d:"M8 10v4",key:"tgpxqk"}],["path",{d:"M12 10v2",key:"hh53o1"}],["path",{d:"M16 10v6",key:"1d6xys"}]]);const Ge=r("FolderOpen",[["path",{d:"m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2",key:"usdka0"}]]);const ks=r("GraduationCap",[["path",{d:"M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z",key:"j76jl0"}],["path",{d:"M22 10v6",key:"1lu8f3"}],["path",{d:"M6 12.5V16a6 3 0 0 0 12 0v-3.5",key:"1r8lef"}]]);const gs=r("History",[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M12 7v5l4 2",key:"1fdv2h"}]]);const js=r("Image",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2",key:"1m3agn"}],["circle",{cx:"9",cy:"9",r:"2",key:"af1f0g"}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21",key:"1xmnt7"}]]);const xs=r("Languages",[["path",{d:"m5 8 6 6",key:"1wu5hv"}],["path",{d:"m4 14 6-6 2-3",key:"1k1g8d"}],["path",{d:"M2 5h12",key:"or177f"}],["path",{d:"M7 2h1",key:"1t2jsx"}],["path",{d:"m22 22-5-10-5 10",key:"don7ne"}],["path",{d:"M14 18h6",key:"1m8k6r"}]]);const fs=r("Library",[["path",{d:"m16 6 4 14",key:"ji33uf"}],["path",{d:"M12 6v14",key:"1n7gus"}],["path",{d:"M8 8v12",key:"1gg7y9"}],["path",{d:"M4 4v16",key:"6qkkli"}]]);const vs=r("Lightbulb",[["path",{d:"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5",key:"1gvzjb"}],["path",{d:"M9 18h6",key:"x1upvd"}],["path",{d:"M10 22h4",key:"ceow96"}]]);const bs=r("Map",[["path",{d:"M14.106 5.553a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619v12.764a1 1 0 0 1-.553.894l-4.553 2.277a2 2 0 0 1-1.788 0l-4.212-2.106a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0z",key:"169xi5"}],["path",{d:"M15 5.764v15",key:"1pn4in"}],["path",{d:"M9 3.236v15",key:"1uimfh"}]]);const Ms=r("Microscope",[["path",{d:"M6 18h8",key:"1borvv"}],["path",{d:"M3 22h18",key:"8prr45"}],["path",{d:"M14 22a7 7 0 1 0 0-14h-1",key:"1jwaiy"}],["path",{d:"M9 14h2",key:"197e7h"}],["path",{d:"M9 12a2 2 0 0 1-2-2V6h6v4a2 2 0 0 1-2 2Z",key:"1bmzmy"}],["path",{d:"M12 6V3a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v3",key:"1drr47"}]]);const Cs=r("Network",[["rect",{x:"16",y:"16",width:"6",height:"6",rx:"1",key:"4q2zg0"}],["rect",{x:"2",y:"16",width:"6",height:"6",rx:"1",key:"8cvhb9"}],["rect",{x:"9",y:"2",width:"6",height:"6",rx:"1",key:"1egb70"}],["path",{d:"M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3",key:"1jsf9p"}],["path",{d:"M12 12V8",key:"2874zd"}]]);const ws=r("Newspaper",[["path",{d:"M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-2 2Zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2",key:"7pis2x"}],["path",{d:"M18 14h-8",key:"sponae"}],["path",{d:"M15 18h-5",key:"95g1m2"}],["path",{d:"M10 6h8v4h-8V6Z",key:"smlsk5"}]]);const Ns=r("NotebookPen",[["path",{d:"M13.4 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.4",key:"re6nr2"}],["path",{d:"M2 6h4",key:"aawbzj"}],["path",{d:"M2 10h4",key:"l0bgd4"}],["path",{d:"M2 14h4",key:"1gsvsf"}],["path",{d:"M2 18h4",key:"1bu2t1"}],["path",{d:"M21.378 5.626a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z",key:"pqwjuv"}]]);const zs=r("PenTool",[["path",{d:"M15.707 21.293a1 1 0 0 1-1.414 0l-1.586-1.586a1 1 0 0 1 0-1.414l5.586-5.586a1 1 0 0 1 1.414 0l1.586 1.586a1 1 0 0 1 0 1.414z",key:"nt11vn"}],["path",{d:"m18 13-1.375-6.874a1 1 0 0 0-.746-.776L3.235 2.028a1 1 0 0 0-1.207 1.207L5.35 15.879a1 1 0 0 0 .776.746L13 18",key:"15qc1e"}],["path",{d:"m2.3 2.3 7.286 7.286",key:"1wuzzi"}],["circle",{cx:"11",cy:"11",r:"2",key:"xmgehs"}]]);const Ss=r("Presentation",[["path",{d:"M2 3h20",key:"91anmk"}],["path",{d:"M21 3v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V3",key:"2k9sn8"}],["path",{d:"m7 21 5-5 5 5",key:"bip4we"}]]);const Ts=r("Quote",[["path",{d:"M16 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z",key:"rib7q0"}],["path",{d:"M5 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z",key:"1ymkrd"}]]);const Ps=r("Regex",[["path",{d:"M17 3v10",key:"15fgeh"}],["path",{d:"m12.67 5.5 8.66 5",key:"1gpheq"}],["path",{d:"m12.67 10.5 8.66-5",key:"1dkfa6"}],["path",{d:"M9 17a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2z",key:"swwfx4"}]]);const As=r("Rocket",[["path",{d:"M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z",key:"m3kijz"}],["path",{d:"m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z",key:"1fmvmk"}],["path",{d:"M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0",key:"1f8sc4"}],["path",{d:"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5",key:"qeys4"}]]);const Ls=r("Server",[["rect",{width:"20",height:"8",x:"2",y:"2",rx:"2",ry:"2",key:"ngkwjq"}],["rect",{width:"20",height:"8",x:"2",y:"14",rx:"2",ry:"2",key:"iecqi9"}],["line",{x1:"6",x2:"6.01",y1:"6",y2:"6",key:"16zg32"}],["line",{x1:"6",x2:"6.01",y1:"18",y2:"18",key:"nzw8ys"}]]);const Ds=r("ShieldCheck",[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]]);const Bt=r("Sparkles",[["path",{d:"M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z",key:"4pj2yx"}],["path",{d:"M20 3v4",key:"1olli1"}],["path",{d:"M22 5h-4",key:"1gvqau"}],["path",{d:"M4 17v2",key:"vumght"}],["path",{d:"M5 18H3",key:"zchphs"}]]);const Es=r("StickyNote",[["path",{d:"M16 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8Z",key:"qazsjp"}],["path",{d:"M15 3v4a2 2 0 0 0 2 2h4",key:"40519r"}]]);const Fs=r("Target",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"6",key:"1vlfrh"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}]]);const Is=r("Telescope",[["path",{d:"m10.065 12.493-6.18 1.318a.934.934 0 0 1-1.108-.702l-.537-2.15a1.07 1.07 0 0 1 .691-1.265l13.504-4.44",key:"k4qptu"}],["path",{d:"m13.56 11.747 4.332-.924",key:"19l80z"}],["path",{d:"m16 21-3.105-6.21",key:"7oh9d"}],["path",{d:"M16.485 5.94a2 2 0 0 1 1.455-2.425l1.09-.272a1 1 0 0 1 1.212.727l1.515 6.06a1 1 0 0 1-.727 1.213l-1.09.272a2 2 0 0 1-2.425-1.455z",key:"m7xp4m"}],["path",{d:"m6.158 8.633 1.114 4.456",key:"74o979"}],["path",{d:"m8 21 3.105-6.21",key:"1fvxut"}],["circle",{cx:"12",cy:"13",r:"2",key:"1c1ljs"}]]);const qs=r("Terminal",[["polyline",{points:"4 17 10 11 4 5",key:"akl6gq"}],["line",{x1:"12",x2:"20",y1:"19",y2:"19",key:"q2wloq"}]]);const $s=r("TestTubeDiagonal",[["path",{d:"M21 7 6.82 21.18a2.83 2.83 0 0 1-3.99-.01a2.83 2.83 0 0 1 0-4L17 3",key:"1ub6xw"}],["path",{d:"m16 2 6 6",key:"1gw87d"}],["path",{d:"M12 16H4",key:"1cjfip"}]]);const Bs=r("TestTubes",[["path",{d:"M9 2v17.5A2.5 2.5 0 0 1 6.5 22A2.5 2.5 0 0 1 4 19.5V2",key:"1hjrqt"}],["path",{d:"M20 2v17.5a2.5 2.5 0 0 1-2.5 2.5a2.5 2.5 0 0 1-2.5-2.5V2",key:"16lc8n"}],["path",{d:"M3 2h7",key:"7s29d5"}],["path",{d:"M14 2h7",key:"7sicin"}],["path",{d:"M9 16H4",key:"1bfye3"}],["path",{d:"M20 16h-5",key:"ddnjpe"}]]);const Vs=r("University",[["circle",{cx:"12",cy:"10",r:"1",key:"1gnqs8"}],["path",{d:"M22 20V8h-4l-6-4-6 4H2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2",key:"1qj5sn"}],["path",{d:"M6 17v.01",key:"roodi6"}],["path",{d:"M6 13v.01",key:"67c122"}],["path",{d:"M18 17v.01",key:"12ktxm"}],["path",{d:"M18 13v.01",key:"tn1rt1"}],["path",{d:"M14 22v-5a2 2 0 0 0-2-2a2 2 0 0 0-2 2v5",key:"11g7fi"}]]);const Os=r("UsersRound",[["path",{d:"M18 21a8 8 0 0 0-16 0",key:"3ypg7q"}],["circle",{cx:"10",cy:"8",r:"5",key:"o932ke"}],["path",{d:"M22 20c0-3.37-2-6.5-4-8a5 5 0 0 0-.45-8.3",key:"10s06x"}]]);const Us=r("Workflow",[["rect",{width:"8",height:"8",x:"3",y:"3",rx:"2",key:"by2w9f"}],["path",{d:"M7 11v4a2 2 0 0 0 2 2h4",key:"xkn7yn"}],["rect",{width:"8",height:"8",x:"13",y:"13",rx:"2",key:"1cgmvn"}]]),Vt=["file-text","book-open","notebook-pen","book-marked","scroll-text","library","graduation-cap","presentation","image","folder-kanban","microscope","flask-conical","test-tube-diagonal","atom","dna","telescope","earth","map","languages","university","code-2","file-code-2","database","server","network","circuit-board","brain","brain-circuit","bot","shield-check","workflow","sigma","calculator","chart-no-axes-combined","line-chart","bar-chart-3","pie-chart","chart-scatter","file-spreadsheet","file-chart-column-increasing","lightbulb","rocket","target","sparkles","file-pen-line","file-stack","folder","folder-open","folder-archive","archive","bookmark","newspaper","notepad-text","sticky-note","pen-tool","quote","list-tree","file-search","beaker","test-tubes","folder-git-2","git-branch","terminal","braces","regex","cpu","hard-drive","cloud","users-round","calendar-days"],Hs=new Set(Vt);function Rs(s){return typeof s=="string"&&Hs.has(s)}function _s(s){if(typeof s!="string")return null;const p=s.trim().replace(/^lucide:/i,"");return!p||p.length>96?null:/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(p)?p:null}const Zs={"file-text":za,"book-open":Na,"notebook-pen":Ns,"book-marked":Et,"scroll-text":wa,library:fs,"graduation-cap":ks,presentation:Ss,image:js,"folder-kanban":ys,microscope:Ms,"flask-conical":hs,"test-tube-diagonal":$s,atom:Oa,dna:os,telescope:Is,earth:is,map:bs,languages:xs,university:Vs,"code-2":as,"file-code-2":Ca,database:rs,server:Ls,network:Cs,"circuit-board":es,brain:Ka,"brain-circuit":Za,bot:Ra,"shield-check":Ds,workflow:Us,sigma:qa,calculator:Wa,"chart-no-axes-combined":Qa,"line-chart":Ja,"bar-chart-3":Ga,"pie-chart":Xa,"chart-scatter":Ya,"file-spreadsheet":Ma,"file-chart-column-increasing":ls,lightbulb:vs,rocket:As,target:Fs,sparkles:Bt,"file-pen-line":ds,"file-stack":ps,folder:ba,"folder-open":Ge,"folder-archive":ms,archive:Qe,bookmark:Ha,newspaper:ws,"notepad-text":va,"sticky-note":Es,"pen-tool":zs,quote:Ts,"list-tree":fa,"file-search":xa,beaker:Ua,"test-tubes":Bs,"folder-git-2":us,"git-branch":ja,terminal:qs,braces:_a,regex:Ps,cpu:ns,"hard-drive":ga,cloud:ts,"users-round":Os,"calendar-days":$t},hn=Vt.map(s=>({name:s}));function Ks(s){const p=Array.from(s.trim())[0];return p?p.toLocaleUpperCase():"?"}function Ws({icon:s,fallback:p,size:x=18}){const f=Rs(s)?Zs[s]:null;if(f)return e.jsx(f,{"aria-hidden":"true",size:x,strokeWidth:1.9});const v=_s(s);if(v){const a=`url(${Je(`/api/project-icons/${encodeURIComponent(v)}`)})`,k={maskImage:a,WebkitMaskImage:a};return e.jsx("span",{"aria-hidden":"true",className:"project-icon-external-glyph",style:k})}return e.jsx("span",{"aria-hidden":"true",children:p})}function Ot({icon:s,projectName:p,editable:x=!1,onEdit:f,editLabel:v,title:c,className:a=""}){const k=e.jsx(Ws,{icon:s,fallback:Ks(p)}),m=`project-icon-avatar${x?" editable":""}${a?` ${a}`:""}`;return x&&f?e.jsx("button",{type:"button",className:m,title:v,"aria-label":v,onClick:f,children:k}):e.jsx("span",{className:m,title:c,"aria-hidden":"true",children:k})}function Ut({project:s,currentUser:p,showArchived:x,archiveBusy:f,menuOpen:v,t:c,variant:a,onToggleMenu:k,onCloseMenu:m,onAssignTags:y,onRename:E,onDuplicate:d,onArchive:z,onTransfer:B,onDelete:S}){const b=i.useRef(null),T=s.permission==="owner",F=p.role==="admin"||p.canCreateProjects,R=s.ownerId===p.id,V=s.permission==="owner";i.useEffect(()=>{if(!v)return;const l=L=>{L.target instanceof Node&&!b.current?.contains(L.target)&&m()},A=L=>{L.key==="Escape"&&m()};return document.addEventListener("pointerdown",l),document.addEventListener("keydown",A),()=>{document.removeEventListener("pointerdown",l),document.removeEventListener("keydown",A)}},[v,m]);const P=[{id:"tags",label:c("tags.assign"),icon:e.jsx(We,{"aria-hidden":!0,size:15}),section:"main",onSelect:y},...T?[{id:"rename",label:c("projects.rename"),icon:e.jsx(Sa,{"aria-hidden":!0,size:15}),section:"main",onSelect:E}]:[],...F?[{id:"duplicate",label:c("projects.duplicate"),icon:e.jsx(ss,{"aria-hidden":!0,size:15}),section:"main",onSelect:d}]:[],{id:"download",label:c("projects.download"),icon:e.jsx(Ta,{"aria-hidden":!0,size:15}),section:"main",href:Je(`/api/projects/${s.id}/download`)},{id:"archive",label:c(x?"projects.unarchive":"projects.archive"),icon:x?e.jsx(Ba,{"aria-hidden":!0,size:15}):e.jsx(Qe,{"aria-hidden":!0,size:15}),section:"main",disabled:f,onSelect:z},...R?[{id:"transfer",label:c("projects.transfer"),icon:e.jsx(qt,{"aria-hidden":!0,size:15}),section:"main",onSelect:B}]:[],...V?[{id:"delete",label:c("common.delete"),icon:e.jsx(Pa,{"aria-hidden":!0,size:15}),section:"danger",onSelect:S}]:[]],_=P.filter(l=>l.section==="main"),M=P.filter(l=>l.section==="danger"),ne=l=>{l.disabled||(m(),l.onSelect?.())};return e.jsxs("div",{ref:b,className:`project-action-menu-root project-action-menu-${a}`,onClick:l=>l.stopPropagation(),children:[e.jsx("button",{type:"button",className:"project-action-menu-trigger","aria-label":c("projects.projectActions",{project:s.name}),"aria-expanded":v,"aria-controls":`project-actions-${s.id}`,onClick:l=>{l.stopPropagation(),k()},children:e.jsx(cs,{"aria-hidden":!0,size:19})}),v&&e.jsxs("div",{id:`project-actions-${s.id}`,className:"project-action-menu",role:"group","aria-label":c("projects.projectActions",{project:s.name}),children:[_.map(l=>l.href?e.jsxs("a",{href:l.href,download:!0,onClick:A=>{A.stopPropagation(),m()},children:[l.icon,l.label]},l.id):e.jsxs("button",{type:"button",disabled:l.disabled,onClick:A=>{A.stopPropagation(),ne(l)},children:[l.icon,l.label]},l.id)),M.length>0&&e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"project-action-menu-separator","aria-hidden":"true"}),M.map(l=>e.jsxs("button",{type:"button",className:"danger",onClick:A=>{A.stopPropagation(),ne(l)},children:[l.icon,l.label]},l.id))]})]})]})}function Gs({project:s,currentUser:p,showArchived:x,archiveBusy:f,menuOpen:v,t:c,formatCreatedDate:a,formatUpdatedTime:k,formatExactTime:m,onOpenProject:y,onOpenMention:E,onToggleMenu:d,onCloseMenu:z,onAssignTags:B,onRename:S,onDuplicate:b,onArchive:T,onTransfer:F,onDelete:R,onChooseIcon:V}){const P=s.ownerDisplayName??s.ownerUsername??c("projects.deletedUser"),_=s.lastModifiedDisplayName??s.lastModifiedUsername??c("projects.deletedUser");return e.jsxs("article",{className:`project-card project-list-row${s.ownerId===p.id?" owned-project":""}${v?" project-list-menu-open":""}`,onClick:M=>{M.target===M.currentTarget&&y()},children:[e.jsx(Ot,{icon:s.icon,projectName:s.name,title:s.name,editable:s.permission==="owner",editLabel:c("projectIcons.change"),onEdit:V,className:"project-list-avatar"}),e.jsx("span",{className:"project-list-summary",children:e.jsx("button",{type:"button",className:"project-list-cell-open project-list-summary-open",onClick:y,"aria-label":c("projects.openProject",{project:s.name}),children:e.jsxs("span",{className:"project-list-title-line",children:[e.jsx("strong",{className:"project-list-title",title:s.name,children:s.name}),!!(s.unresolvedCommentCount&&s.unresolvedCommentCount>0)&&e.jsxs("span",{className:"project-comments-badge unresolved",title:c("projects.unresolvedCommentsTooltip",{unresolved:s.unresolvedCommentCount,total:s.commentCount??s.unresolvedCommentCount}),children:[e.jsx(Ft,{"aria-hidden":!0,size:10}),e.jsx("span",{children:c("projects.unresolvedCount",{count:s.unresolvedCommentCount})})]}),s.tags?.length>0&&e.jsx("span",{className:"project-list-tags",children:s.tags.map(M=>e.jsx("span",{className:`tag tag-${M.color} project-list-tag`,title:M.name,children:M.name},M.id))})]})})}),e.jsxs("button",{type:"button",className:"project-list-cell-open project-list-owner",onClick:y,title:P,"aria-label":c("projects.openProject",{project:s.name}),children:[e.jsxs("span",{className:"sr-only",children:[c("projects.owner"),": "]}),P]}),e.jsxs("button",{type:"button",className:"project-list-cell-open project-list-created",onClick:y,title:m(s.createdAt),"aria-label":c("projects.openProject",{project:s.name}),children:[e.jsxs("span",{className:"sr-only",children:[c("projects.created"),": "]}),e.jsx("time",{dateTime:s.createdAt,children:a(s.createdAt)})]}),e.jsxs("button",{type:"button",className:"project-list-cell-open project-list-modified",onClick:y,title:c("projects.modifiedByUser",{time:m(s.updatedAt),user:_}),"aria-label":c("projects.openProject",{project:s.name}),children:[e.jsxs("span",{className:"sr-only",children:[c("projects.modified"),": "]}),e.jsx("time",{dateTime:s.updatedAt,children:k(s.updatedAt)}),e.jsx("small",{children:c("projects.byUser",{user:_})})]}),s.unreadMentionCount&&s.unreadMentionCount>0?e.jsxs("button",{className:"project-mentions-badge project-list-mentions",type:"button",title:c("projects.unreadMentionsTooltip",{count:s.unreadMentionCount}),onClick:E,children:[e.jsx(It,{"aria-hidden":!0,size:10}),e.jsx("span",{children:s.unreadMentionCount})]}):e.jsx("span",{className:"project-list-mentions"}),e.jsx(Ut,{variant:"list",project:s,currentUser:p,showArchived:x,archiveBusy:f,menuOpen:v,t:c,onToggleMenu:d,onCloseMenu:z,onAssignTags:B,onRename:S,onDuplicate:b,onArchive:T,onTransfer:F,onDelete:R})]})}const Js=i.lazy(()=>me(()=>import("./SystemMetricsDialog-hJQATmd4.js"),__vite__mapDeps([0,1,2,3,4,5,6,7,8]),import.meta.url).then(s=>({default:s.SystemMetricsDialog}))),Qs=i.lazy(()=>me(()=>import("./CitationLibraryDialog-BTmnCP4V.js"),__vite__mapDeps([9,1,2,3,10,6,11,12,13,8]),import.meta.url).then(s=>({default:s.CitationLibraryDialog}))),Xs=i.lazy(()=>me(()=>import("./AdminUsers-B12FJUMy.js"),__vite__mapDeps([14,1,2,3,6,15,16,5,7,8]),import.meta.url).then(s=>({default:s.AdminUsers}))),Ys=i.lazy(()=>me(()=>import("./TagManagementDialog-BHC8qULq.js"),__vite__mapDeps([17,1,2,3,11,6,12]),import.meta.url).then(s=>({default:s.TagManagementDialog}))),en=i.lazy(()=>me(()=>import("./ProjectIconPickerDialog-GzRuM0oQ.js"),__vite__mapDeps([18,1,2,3,13,5,6,7,8]),import.meta.url).then(s=>({default:s.ProjectIconPickerDialog})));function tn(s){return s instanceof Error&&s.name==="AbortError"}function an(s,p,x,f){const v=new Date(s).getTime();if(!Number.isFinite(v))return s;const c=v-x,a=Math.abs(c),k=60*1e3,m=60*k,y=24*m,E=3*y;if(a>E){const S=Math.floor(a/y),b=c<0?"Past":"Future";return f(`projects.relativeDays${b}`,{days:S,hours:0,minutes:0})}if(a>=y){const S=Math.floor(a/y),b=Math.floor(a%y/m),T=c<0?"Past":"Future",F=b>0?"DaysHours":"Days";return f(`projects.relative${F}${T}`,{days:S,hours:b,minutes:0})}if(a>=m){const S=Math.floor(a/m),b=Math.floor(a%m/k),T=c<0?"Past":"Future",F=b>0?"HoursMinutes":"Hours";return f(`projects.relative${F}${T}`,{days:0,hours:S,minutes:b})}const d="minute",z=k,B=Math.sign(c)*Math.max(1,Math.floor(a/z));return new Intl.RelativeTimeFormat(p,{numeric:"always"}).format(B,d)}function sn(s){return s<1024?`${s} B`:s<1024*1024?`${(s/1024).toFixed(1)} KB`:`${(s/1024/1024).toFixed(1)} MB`}function nn({site:s,user:p,initialData:x,onDataChange:f,onUser:v,onOpenProject:c}){const{t:a}=ka(),[k,m]=i.useState(()=>x?.projects??[]),[y,E]=i.useState(()=>x?.tags??[]),[d,z]=i.useState(()=>x?.pagination??{page:1,pageSize:20,total:x?.projects.length??0,totalPages:x?.projects.length?1:0}),[B,S]=i.useState(!!x),[b,T]=i.useState(!1),[F,R]=i.useState(!1),[V,P]=i.useState(!1),[_,M]=i.useState(""),[ne,l]=i.useState(""),[A,L]=i.useState(""),[Xe,Z]=i.useState(""),[Ye,K]=i.useState(""),[Ht,re]=i.useState(""),[Rt,ue]=i.useState(!1),[ye,et]=i.useState(""),[W,tt]=i.useState(!1),[_t,ke]=i.useState(!1),[Ie,qe]=i.useState(""),[I,at]=i.useState(null),[st,O]=i.useState(""),ge=i.useRef(null),nt=i.useRef(null),[rt,ot]=i.useState(!1),[U,Zt]=i.useState(""),[w,it]=i.useState(""),[je,$e]=i.useState(!1),[Kt,Be]=i.useState(!1),[G,J]=i.useState(null),[ct,xe]=i.useState(null),[Ve,oe]=i.useState(null),[fe,ve]=i.useState(""),[Q,lt]=i.useState(!1),[be,ie]=i.useState(null),[Me,Y]=i.useState(""),[Ce,dt]=i.useState(!1),[ce,we]=i.useState(null),[Ne,pt]=i.useState(!1),[ze,Se]=i.useState(null),[Oe,ht]=i.useState([]),[Te,le]=i.useState(""),[ee,Pe]=i.useState(!1),[mt,te]=i.useState(""),[de,Wt]=i.useState(()=>localStorage.getItem(Fe("texlite-project-view"))==="list"?"list":"grid"),[q,Gt]=i.useState(()=>localStorage.getItem(Fe("texlite-project-sort"))==="created"?"created":"updated"),[u,Jt]=i.useState(!1),[ut,yt]=i.useState(""),[Ae,H]=i.useState(null),[N,$]=i.useState(1),[Qt,Xt]=i.useState(()=>Date.now()),[kt,Yt]=i.useState(""),Ue=i.useRef(0),pe=i.useRef(null),gt=(t,n,o,h,g)=>`${t?"archived":"active"}|${n}|${o}|${h}|${g}`,jt=gt(u,N,U,w,q),he=(t,n,o,h,g)=>{const j=new URLSearchParams({page:String(n),pageSize:"20",sort:g});t&&j.set("archived","1"),o.trim()&&j.set("search",o.trim()),h&&j.set("tag",h);const X=gt(t,n,o,h,g),De=++Ue.current;pe.current?.abort();const Ee=new AbortController;return pe.current=Ee,Promise.all([C(`/api/projects?${j.toString()}`,{signal:Ee.signal}),C("/api/tags",{signal:Ee.signal})]).then(([ae,pa])=>{De===Ue.current&&(m(ae.projects),E(pa.tags),z(ae.pagination),$(ae.pagination.page),S(!0),Yt(X))}).catch(ae=>{!tn(ae)&&De===Ue.current&&M(D(ae))}).finally(()=>{pe.current===Ee&&(pe.current=null)})};i.useEffect(()=>(he(u,N,U,w,q),()=>pe.current?.abort()),[u,N,U,w,q]),i.useEffect(()=>{B&&!u&&kt===jt&&f(k,y,d)},[k,y,d,B,kt,jt,u]),i.useEffect(()=>{const t=window.setInterval(()=>Xt(Date.now()),6e4);return()=>window.clearInterval(t)},[]),i.useEffect(()=>{if(!je)return;const t=o=>{o.target instanceof Node&&!nt.current?.contains(o.target)&&$e(!1)},n=o=>{o.key==="Escape"&&$e(!1)};return document.addEventListener("pointerdown",t),document.addEventListener("keydown",n),()=>{document.removeEventListener("pointerdown",t),document.removeEventListener("keydown",n)}},[je]);const xt=t=>{H(null),Wt(t),localStorage.setItem(Fe("texlite-project-view"),t)},ea=t=>{Gt(t),$(1),localStorage.setItem(Fe("texlite-project-sort"),t)},ft=t=>{Jt(t),$(1)},vt=t=>{it(t),$(1)},bt=async()=>{if(!(!ye.trim()||W)){tt(!0),l("");try{const{project:t}=await C("/api/projects",{method:"POST",body:JSON.stringify({name:ye})});ue(!1),et("");const n=u||N!==1?k:[t,...k].slice(0,d.pageSize),o=u?d:{...d,total:d.total+1,totalPages:Math.ceil((d.total+1)/d.pageSize)};m(n),z(o),u||f(n,y,o),c(t.id)}catch(t){l(D(t))}finally{tt(!1)}}},ta=async()=>{if(!I)return;const t=s.maxUploadSizeMB;if(I.size>t*1024*1024)return O(a("errors.fileTooLarge",{size:t}));ot(!0),O("");const n=new FormData;n.append("file",I);try{const{project:o}=await C(`/api/projects/import?name=${encodeURIComponent(Ie.trim())}`,{method:"POST",body:n}),h=u||N!==1?k:[o,...k].slice(0,d.pageSize),g=u?d:{...d,total:d.total+1,totalPages:Math.ceil((d.total+1)/d.pageSize)};m(h),z(g),u||f(h,y,g),ke(!1),at(null),qe(""),c(o.id)}catch(o){O(D(o))}finally{ot(!1)}},He=t=>{if(t&&!t.name.toLocaleLowerCase().endsWith(".zip")){O(a("errors.zipOnly"));return}O(""),at(t),t&&!Ie&&qe(t.name.replace(/\.zip$/i,""))},aa=async()=>{await C("/api/auth/logout",{method:"POST"}),v(null)},sa=async t=>{if(!G)return;const n=G.tags.some(o=>o.id===t.id);L("");try{const h=(await C(n?`/api/projects/${G.id}/tags/${t.id}`:`/api/projects/${G.id}/tags`,n?{method:"DELETE"}:{method:"POST",body:JSON.stringify({tagId:t.id})})).project;J(h),m(g=>g.map(j=>j.id===h.id?h:j))}catch(o){L(D(o))}},na=t=>{E(n=>[...n,t].sort((o,h)=>o.name.localeCompare(h.name)))},ra=t=>{const n=o=>o.map(h=>h.id===t.id?t:h);E(o=>n(o).sort((h,g)=>h.name.localeCompare(g.name))),m(o=>o.map(h=>({...h,tags:n(h.tags)}))),J(o=>o?{...o,tags:n(o.tags)}:null)},oa=t=>{E(n=>n.filter(o=>o.id!==t)),m(n=>n.map(o=>({...o,tags:o.tags.filter(h=>h.id!==t)}))),J(n=>n?{...n,tags:n.tags.filter(o=>o.id!==t)}:null),it(n=>n===t?"":n)},ia=async(t,n)=>{const o=await C(`/api/projects/${t}/icon`,{method:"PATCH",body:JSON.stringify({icon:n})});m(h=>{const g=h.map(j=>j.id===o.project.id?o.project:j);return q==="updated"?g.sort((j,X)=>X.updatedAt.localeCompare(j.updatedAt)||j.name.localeCompare(X.name)):g})},Mt=async()=>{if(!(!Ve||!fe.trim()||Q)){lt(!0),Z("");try{const t=await C(`/api/projects/${Ve.id}`,{method:"PATCH",body:JSON.stringify({name:fe})});m(n=>n.map(o=>o.id===t.project.id?t.project:o)),oe(null),ve("")}catch(t){Z(D(t))}finally{lt(!1)}}},Ct=async()=>{if(!(!be||!Me.trim())){dt(!0),K("");try{await C(`/api/projects/${be.id}/duplicate`,{method:"POST",body:JSON.stringify({name:Me})}),ie(null),Y(""),he(u,N,U,w,q)}catch(t){K(D(t))}finally{dt(!1)}}},ca=async()=>{if(!(!ce||Ne)){pt(!0),re("");try{await C(`/api/projects/${ce.id}`,{method:"DELETE"}),m(g=>g.filter(j=>j.id!==ce.id));const t=Math.max(0,d.total-1),n=Math.ceil(t/d.pageSize),o=n===0?1:Math.min(N,n),h={...d,page:o,total:t,totalPages:n};z(h),we(null),o!==N?$(o):he(u,N,U,w,q)}catch(t){re(D(t))}finally{pt(!1)}}},wt=async t=>{Se(t),le(""),ht([]),te(""),Pe(!0);try{const n=await C("/api/users");ht(n.users.filter(o=>o.id!==t.ownerId))}catch(n){te(D(n))}finally{Pe(!1)}},la=async()=>{if(!(!ze||!Te)){Pe(!0),te("");try{await C(`/api/projects/${ze.id}/owner`,{method:"PUT",body:JSON.stringify({userId:Te})}),Se(null),le(""),he(u,N,U,w,q)}catch(t){te(D(t))}finally{Pe(!1)}}},Nt=async t=>{const n=!u;yt(t.id),M("");try{await C(`/api/projects/${t.id}/archive`,{method:n?"PUT":"DELETE"});const o=k.filter(De=>De.id!==t.id);m(o);const h=Math.max(0,d.total-1),g=Math.ceil(h/d.pageSize),j=g===0?1:Math.min(N,g),X={...d,page:j,total:h,totalPages:g};z(X),u||f(o,y,X),j!==N?$(j):he(u,N,U,w,q)}catch(o){M(D(o))}finally{yt("")}},zt=async t=>{try{const o=(await C(`/api/projects/${t.id}/mentions?unread=1&limit=1`)).mentions[0];c(t.id,o?.id)}catch(n){M(D(n))}},Le=k,Re=t=>new Date(t).toLocaleString(Ke.resolvedLanguage),da=t=>{const n=new Date(t);return Number.isFinite(n.getTime())?new Intl.DateTimeFormat(Ke.resolvedLanguage??"en",{year:"numeric",month:"2-digit",day:"2-digit"}).format(n):t},St=t=>an(t,Ke.resolvedLanguage??"en",Qt,(n,o)=>a(n,o));return e.jsxs("div",{className:"page",children:[e.jsxs("header",{className:"topbar",children:[e.jsxs("a",{className:"brand-link",href:Je("/"),"aria-label":s.siteName,children:[e.jsx("span",{className:"site-title",children:s.siteName}),e.jsx(ha,{siteName:s.siteName})]}),e.jsxs("div",{className:"top-actions",children:[p.role==="admin"&&e.jsxs(e.Fragment,{children:[e.jsxs("button",{className:"ghost",onClick:()=>R(!0),children:[e.jsx($a,{"aria-hidden":!0,size:14}),a("metrics.title")]}),e.jsxs("button",{className:`ghost${b?" top-return-action":""}`,onClick:()=>{T(t=>!t),P(!1)},children:[b?e.jsx(Pt,{"aria-hidden":!0,size:14}):e.jsx(Aa,{"aria-hidden":!0,size:14}),a(b?"users.back":"users.manage")]})]}),e.jsxs("button",{className:`ghost${V?" top-return-action":""}`,onClick:()=>{T(!1),P(t=>!t)},children:[V?e.jsx(Pt,{"aria-hidden":!0,size:14}):e.jsx(Et,{"aria-hidden":!0,size:14}),a(V?"users.back":"citationLibrary.title")]}),e.jsx(ma,{compact:!0}),e.jsxs("span",{className:"top-user-identity",children:[e.jsx("strong",{children:p.displayName}),e.jsxs("small",{children:["@",p.username]})]}),e.jsx("button",{className:"ghost",onClick:aa,children:a("auth.logout")})]})]}),b?e.jsx("main",{className:"dashboard",children:e.jsx(Tt,{onClose:()=>T(!1),children:e.jsx(Xs,{currentUser:p,minPasswordLength:s.minPasswordLength})})}):V?e.jsx("main",{className:"dashboard citation-library-page-shell",children:e.jsx(Tt,{onClose:()=>P(!1),children:e.jsx(Qs,{page:!0,open:!0,onOpenChange:P,onBack:()=>P(!1),currentUserId:p.id,maxBibtexBytes:s.maxCitationBibtexBytes})})}):e.jsxs("main",{className:"dashboard",children:[e.jsxs("div",{className:"section-title",children:[e.jsxs("div",{children:[e.jsxs("h1",{children:[e.jsx(Ge,{"aria-hidden":!0,size:25}),a("projects.title")]}),e.jsx("p",{className:"muted",children:p.canCreateProjects?a("projects.subtitle"):a("projects.restricted")})]}),e.jsxs("div",{className:"section-actions",children:[e.jsxs("button",{onClick:()=>Be(!0),children:[e.jsx(We,{"aria-hidden":!0,size:15}),a("tags.manage")]}),p.canCreateProjects&&e.jsxs(e.Fragment,{children:[e.jsxs("button",{onClick:()=>{O(""),ke(!0)},children:[e.jsx(At,{"aria-hidden":!0,size:15}),a("projects.upload")]}),e.jsxs("button",{className:"primary",onClick:()=>{l(""),ue(!0)},children:[e.jsx(La,{"aria-hidden":!0,size:15}),a("projects.new")]})]})]})]}),_&&e.jsx("p",{className:"error",children:_}),e.jsxs("div",{className:"project-catalog-layout",children:[e.jsxs("aside",{ref:nt,className:`project-tag-sidebar${w?" has-active-filter":""}`,children:[e.jsxs("button",{type:"button",className:"project-tag-toggle",title:a("projects.filterTags"),"aria-label":a("projects.filterTags"),"aria-expanded":je,"aria-controls":"dashboard-tag-filters",onClick:()=>$e(t=>!t),children:[e.jsxs("span",{className:"project-tag-toggle-copy",children:[e.jsx(We,{"aria-hidden":!0,size:15}),e.jsx("span",{children:a("projects.filterTags")})]}),e.jsx(Lt,{className:"project-tag-toggle-chevron","aria-hidden":!0,size:15})]}),je&&e.jsxs("div",{id:"dashboard-tag-filters",className:"project-tag-filters",role:"group","aria-label":a("projects.filterTags"),children:[e.jsx("button",{type:"button",className:w?"":"active","aria-pressed":!w,onClick:()=>vt(""),children:a("projects.allTags")}),y.length>0?y.map(t=>e.jsxs("button",{type:"button",className:w===t.id?"active":"",title:t.name,"aria-pressed":w===t.id,onClick:()=>vt(w===t.id?"":t.id),children:[e.jsx(Dt,{color:t.color}),e.jsx("span",{children:t.name})]},t.id)):e.jsx("p",{className:"project-tag-empty",children:a("tags.none")})]})]}),e.jsxs("div",{className:"project-catalog-content",children:[e.jsxs("div",{className:"project-toolbar",children:[e.jsx("input",{type:"search",placeholder:a("projects.search"),value:U,onChange:t=>{Zt(t.target.value),$(1)}}),e.jsxs("div",{className:"project-scope",role:"tablist","aria-label":a("projects.scope"),children:[e.jsxs("button",{className:u?"":"active",onClick:()=>ft(!1),role:"tab","aria-selected":!u,children:[e.jsx(Ge,{size:14}),a("projects.active")]}),e.jsxs("button",{className:u?"active":"",onClick:()=>ft(!0),role:"tab","aria-selected":u,children:[e.jsx(Qe,{size:14}),a("projects.archived")]})]}),e.jsxs("label",{className:"project-sort",children:[e.jsx(Va,{size:14}),e.jsx("span",{children:a("projects.sortBy")}),e.jsxs("select",{value:q,onChange:t=>ea(t.target.value),children:[e.jsx("option",{value:"updated",children:a("projects.sortModified")}),e.jsx("option",{value:"created",children:a("projects.sortCreated")})]})]}),e.jsxs("div",{className:"view-toggle",children:[e.jsx("button",{className:de==="grid"?"active":"",onClick:()=>xt("grid"),title:a("projects.grid"),children:"▦"}),e.jsx("button",{className:de==="list"?"active":"",onClick:()=>xt("list"),title:a("projects.list"),children:"☷"})]})]}),e.jsxs("div",{className:`project-grid ${de==="list"?"list-view":""}${Ae?" project-list-menu-active":""}`,children:[de==="list"&&Le.length>0&&e.jsxs("div",{className:"project-list-header","aria-hidden":"true",children:[e.jsx("span",{className:"project-list-header-avatar"}),e.jsx("span",{children:a("projects.title")}),e.jsx("span",{children:a("projects.owner")}),e.jsx("span",{className:"project-list-header-created",children:a("projects.created")}),e.jsx("span",{children:a("projects.modified")}),e.jsx("span",{className:"project-list-header-mentions",children:a("projects.mentionsColumn")}),e.jsx("span",{className:"project-list-header-actions",children:a("projects.actions")})]}),de==="list"?Le.map(t=>e.jsx(Gs,{project:t,currentUser:p,showArchived:u,archiveBusy:ut===t.id,menuOpen:Ae===t.id,t:a,formatCreatedDate:da,formatUpdatedTime:St,formatExactTime:Re,onOpenProject:()=>{H(null),c(t.id)},onOpenMention:()=>{zt(t)},onToggleMenu:()=>H(n=>n===t.id?null:t.id),onCloseMenu:()=>H(n=>n===t.id?null:n),onAssignTags:()=>{L(""),J(t)},onRename:()=>{Z(""),oe(t),ve(t.name)},onDuplicate:()=>{K(""),ie(t),Y(`${t.name} (1)`)},onArchive:()=>{Nt(t)},onTransfer:()=>{wt(t)},onDelete:()=>{we(t),re("")},onChooseIcon:()=>xe(t)},t.id)):Le.map(t=>e.jsxs("article",{className:`project-card project-grid-card${t.ownerId===p.id?" owned-project":""}${Ae===t.id?" project-card-menu-open":""}`,role:"button",tabIndex:0,"aria-label":a("projects.openProject",{project:t.name}),onClick:()=>{H(null),c(t.id)},onKeyDown:n=>{n.target!==n.currentTarget||n.key!=="Enter"&&n.key!==" "||(n.preventDefault(),H(null),c(t.id))},children:[e.jsxs("div",{className:"project-card-meta-row",children:[e.jsxs("div",{className:"project-card-badges",children:[e.jsx("span",{className:"owner-badge",title:t.ownerDisplayName??t.ownerUsername??a("projects.deletedUser"),children:t.ownerDisplayName??t.ownerUsername??a("projects.deletedUser")}),!!(t.unreadMentionCount&&t.unreadMentionCount>0)&&e.jsxs("button",{className:"project-mentions-badge",type:"button",title:a("projects.unreadMentionsTooltip",{count:t.unreadMentionCount}),"aria-label":a("projects.unreadMentionsTooltip",{count:t.unreadMentionCount}),onClick:n=>{n.stopPropagation(),zt(t)},children:[e.jsx(It,{"aria-hidden":!0,size:11}),e.jsx("span",{children:t.unreadMentionCount})]})]}),e.jsx(Ut,{variant:"grid",project:t,currentUser:p,showArchived:u,archiveBusy:ut===t.id,menuOpen:Ae===t.id,t:a,onToggleMenu:()=>H(n=>n===t.id?null:t.id),onCloseMenu:()=>H(n=>n===t.id?null:n),onAssignTags:()=>{L(""),J(t)},onRename:()=>{Z(""),oe(t),ve(t.name)},onDuplicate:()=>{K(""),ie(t),Y(`${t.name} (1)`)},onArchive:()=>{Nt(t)},onTransfer:()=>{wt(t)},onDelete:()=>{we(t),re("")}})]}),e.jsxs("div",{className:"project-card-title-row",children:[e.jsx("span",{className:"project-card-icon-control",onClick:t.permission==="owner"?n=>n.stopPropagation():void 0,children:e.jsx(Ot,{icon:t.icon,projectName:t.name,title:t.name,editable:t.permission==="owner",editLabel:a("projectIcons.change"),onEdit:()=>xe(t),className:"project-card-icon"})}),e.jsx("strong",{className:"project-card-title",title:t.name,children:t.name})]}),e.jsx("div",{className:"project-card-tags","aria-label":a("tags.assignTitle",{project:t.name}),title:t.tags?.map(n=>n.name).join(", ")||void 0,children:t.tags?.map(n=>e.jsx("span",{className:`tag tag-${n.color}`,title:n.name,children:n.name},n.id))}),e.jsxs("dl",{className:"project-meta",children:[e.jsxs("div",{children:[e.jsxs("dt",{className:"project-meta-created-label",children:[e.jsxs("span",{children:[e.jsx($t,{"aria-hidden":!0,size:13}),a("projects.created")]}),!!(t.unresolvedCommentCount&&t.unresolvedCommentCount>0)&&e.jsxs("span",{className:"project-comments-badge unresolved",title:a("projects.unresolvedCommentsTooltip",{unresolved:t.unresolvedCommentCount,total:t.commentCount??t.unresolvedCommentCount}),children:[e.jsx(Ft,{"aria-hidden":!0,size:11}),e.jsx("span",{children:a("projects.unresolvedCount",{count:t.unresolvedCommentCount})})]})]}),e.jsx("dd",{children:e.jsx("time",{dateTime:t.createdAt,children:Re(t.createdAt)})})]}),e.jsxs("div",{children:[e.jsxs("dt",{children:[e.jsx(gs,{"aria-hidden":!0,size:13}),a("projects.modified")]}),e.jsxs("dd",{title:a("projects.modifiedByUser",{time:Re(t.updatedAt),user:t.lastModifiedDisplayName??t.lastModifiedUsername??a("projects.deletedUser")}),children:[e.jsx("time",{dateTime:t.updatedAt,children:St(t.updatedAt)}),e.jsxs("span",{className:"project-modified-by",children:[" · ",a("projects.byUser",{user:t.lastModifiedDisplayName??t.lastModifiedUsername??a("projects.deletedUser")})]})]})]})]})]},t.id)),Le.length===0&&(k.length===0?u?e.jsx("div",{className:"empty",children:a("projects.noArchived")}):e.jsxs("div",{className:"project-empty",children:[e.jsx("span",{className:"project-empty-icon",children:e.jsx(Bt,{size:28})}),e.jsx("h2",{children:a("projects.emptyTitle")}),e.jsx("p",{children:p.canCreateProjects?a("projects.emptyDescription"):a("projects.emptyRestricted")})]}):e.jsx("div",{className:"empty",children:a("projects.noMatches")}))]}),d.totalPages>1&&e.jsxs("nav",{className:"project-pagination","aria-label":a("projects.pagination"),children:[e.jsxs("button",{disabled:d.page<=1,onClick:()=>$(t=>Math.max(1,t-1)),title:a("projects.previousPage"),children:[e.jsx(Da,{size:15}),a("projects.previousPage")]}),e.jsx("span",{children:a("projects.pageOf",{page:d.page,totalPages:d.totalPages,count:d.total})}),e.jsxs("button",{disabled:d.page>=d.totalPages,onClick:()=>$(t=>Math.min(d.totalPages,t+1)),title:a("projects.nextPage"),children:[e.jsx(Lt,{size:15}),a("projects.nextPage")]})]})]})]}),e.jsx(se,{open:Rt,title:a("projects.new"),description:a("projects.newDescription"),onOpenChange:t=>{!t&&W||(ue(t),t||l(""))},footer:e.jsxs(e.Fragment,{children:[e.jsx("button",{disabled:W,onClick:()=>ue(!1),children:a("common.cancel")}),e.jsxs("button",{className:"primary",disabled:W||!ye.trim(),"aria-busy":W,onClick:()=>{bt()},children:[W&&e.jsx(_e,{className:"spin",size:14}),a(W?"common.loading":"common.create")]})]}),children:e.jsxs(e.Fragment,{children:[ne&&e.jsx("p",{className:"error dialog-error",children:ne}),e.jsxs("label",{className:"form-field",children:[a("projects.name"),e.jsx("input",{autoFocus:!0,value:ye,onChange:t=>{et(t.target.value),l("")},onKeyDown:t=>{t.key==="Enter"&&bt()}})]})]})}),e.jsx(se,{open:_t,title:a("projects.upload"),description:a("projects.uploadDescription",{size:s.maxUploadSizeMB}),onOpenChange:t=>{ke(t),t||O("")},footer:e.jsxs(e.Fragment,{children:[e.jsx("button",{onClick:()=>{ke(!1),O("")},children:a("common.cancel")}),e.jsx("button",{className:"primary",disabled:!I||rt,onClick:()=>{ta()},children:a(rt?"projects.importing":"projects.import")})]}),children:e.jsxs("div",{className:"form-stack",children:[st&&e.jsx("p",{className:"error import-error",children:st}),e.jsxs("div",{className:`upload-picker${I?" has-file":""}`,onDragOver:t=>{t.preventDefault(),t.dataTransfer.dropEffect="copy"},onDrop:t=>{t.preventDefault(),He(t.dataTransfer.files[0]??null)},children:[e.jsx("input",{ref:ge,className:"sr-only",type:"file",accept:".zip,application/zip",onChange:t=>He(t.target.files?.[0]??null)}),e.jsx(Ea,{size:34}),e.jsxs("div",{className:"upload-picker-copy",children:[e.jsx("strong",{children:I?.name??a("projects.chooseZip")}),e.jsx("span",{children:I?a("projects.selectedFileSize",{size:sn(I.size)}):a("projects.dropZip")})]}),e.jsxs("button",{type:"button",onClick:()=>ge.current?.click(),children:[e.jsx(At,{size:15}),a("projects.browse")]}),I&&e.jsx("button",{className:"upload-clear",type:"button",title:a("projects.clearFile"),"aria-label":a("projects.clearFile"),onClick:()=>{He(null),ge.current&&(ge.current.value="")},children:e.jsx(Fa,{size:14})})]}),e.jsxs("label",{className:"form-field",children:[a("projects.name"),e.jsx("input",{value:Ie,onChange:t=>qe(t.target.value)})]})]})}),Kt&&e.jsx(Ze,{title:a("tags.manage"),onClose:()=>Be(!1),children:e.jsx(Ys,{open:!0,onOpenChange:Be,onTagCreated:na,onTagUpdated:ra,onTagDeleted:oa})}),ct&&e.jsx(Ze,{title:a("projectIcons.title"),onClose:()=>xe(null),children:e.jsx(en,{project:ct,open:!0,onOpenChange:t=>{t||xe(null)},onSave:ia})}),e.jsx(se,{open:!!G,title:a("tags.assignTitle",{project:G?.name??""}),description:a("tags.assignDescription"),onOpenChange:t=>{t||(J(null),L(""))},footer:e.jsx("button",{onClick:()=>{J(null),L("")},children:a("common.close")}),children:e.jsxs("div",{className:"tag-assignment-list",children:[A&&e.jsx("p",{className:"error dialog-error",children:A}),y.map(t=>e.jsxs("label",{children:[e.jsx("input",{type:"checkbox",checked:!!G?.tags.some(n=>n.id===t.id),onChange:()=>{sa(t)}}),e.jsx(Dt,{color:t.color}),e.jsx("span",{children:t.name})]},t.id)),y.length===0&&e.jsx("p",{className:"muted",children:a("tags.empty")})]})}),e.jsx(se,{open:!!Ve,title:a("projects.renameTitle"),onOpenChange:t=>{!t&&Q||t||(oe(null),Z(""))},footer:e.jsxs(e.Fragment,{children:[e.jsx("button",{disabled:Q,onClick:()=>{oe(null),Z("")},children:a("common.cancel")}),e.jsxs("button",{className:"primary",disabled:Q||!fe.trim(),"aria-busy":Q,onClick:()=>{Mt()},children:[Q&&e.jsx(_e,{className:"spin",size:14}),a(Q?"common.loading":"projects.rename")]})]}),children:e.jsxs(e.Fragment,{children:[Xe&&e.jsx("p",{className:"error dialog-error",children:Xe}),e.jsxs("label",{className:"form-field",children:[a("projects.name"),e.jsx("input",{autoFocus:!0,value:fe,onChange:t=>{ve(t.target.value),Z("")},onKeyDown:t=>{t.key==="Enter"&&Mt()}})]})]})}),e.jsx(se,{open:!!be,title:a("projects.duplicateTitle"),description:a("projects.duplicateDescription",{project:be?.name??""}),onOpenChange:t=>{!t&&!Ce&&(ie(null),Y(""),K(""))},footer:e.jsxs(e.Fragment,{children:[e.jsx("button",{disabled:Ce,onClick:()=>{ie(null),Y(""),K("")},children:a("common.cancel")}),e.jsx("button",{className:"primary",disabled:Ce||!Me.trim(),onClick:()=>{Ct()},children:a(Ce?"projects.duplicating":"projects.duplicate")})]}),children:e.jsxs(e.Fragment,{children:[Ye&&e.jsx("p",{className:"error dialog-error",children:Ye}),e.jsxs("label",{className:"form-field",children:[a("projects.name"),e.jsx("input",{autoFocus:!0,value:Me,onChange:t=>{Y(t.target.value),K("")},onKeyDown:t=>{t.key==="Enter"&&Ct()}})]})]})}),e.jsx(se,{open:!!ze,title:a("projects.transferOwnership"),description:a("projects.transferDescription",{project:ze?.name??""}),onOpenChange:t=>{!t&&!ee&&(Se(null),le(""),te(""))},footer:e.jsxs(e.Fragment,{children:[e.jsx("button",{disabled:ee,onClick:()=>{Se(null),le(""),te("")},children:a("common.cancel")}),e.jsxs("button",{className:"primary",disabled:ee||!Te,onClick:()=>{la()},children:[ee?e.jsx(_e,{className:"spin",size:14}):e.jsx(qt,{size:14}),a("projects.transfer")]})]}),children:e.jsxs("div",{className:"form-stack",children:[mt&&e.jsx("p",{className:"error dialog-error",children:mt}),e.jsxs("label",{className:"form-field",children:[a("projects.newOwner"),e.jsxs("select",{disabled:ee||Oe.length===0,value:Te,onChange:t=>le(t.target.value),children:[e.jsx("option",{value:"",children:ee?a("common.loading"):Oe.length>0?a("projects.chooseNewOwner"):a("projects.noTransferUsers")}),Oe.map(t=>e.jsxs("option",{value:t.id,children:[t.displayName??t.username," (@",t.username,")"]},t.id))]})]}),e.jsxs("p",{className:"warning",children:[e.jsx(Ia,{size:15}),a("projects.transferWarning")]})]})}),e.jsx(ua,{open:!!ce,title:a("projects.deleteTitle"),description:a("projects.deleteDescription",{project:ce?.name??""}),confirmLabel:a(Ne?"common.loading":"common.delete"),danger:!0,busy:Ne,error:Ht,onCancel:()=>{Ne||(we(null),re(""))},onConfirm:()=>{ca()}})]}),F&&e.jsx(Ze,{title:a("metrics.title"),onClose:()=>R(!1),children:e.jsx(Js,{open:!0,onOpenChange:R})}),e.jsx(ya,{})]})}function Dt({color:s}){return e.jsx("span",{className:`tag-dot tag-${s}`})}const mn=Object.freeze(Object.defineProperty({__proto__:null,Dashboard:nn},Symbol.toStringTag,{value:"Module"}));export{$a as A,mn as D,Ws as P,Ds as S,Ks as a,Rs as i,_s as n,hn as p};