diff --git a/web/static/css/style.css b/web/static/css/style.css index eb3bc9fe..693ffe1f 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -22684,18 +22684,29 @@ button.chat-files-dropdown-item:hover:not(:disabled) { } .projects-detail-title-row { display: flex; - align-items: center; + align-items: flex-start; flex-wrap: wrap; gap: 10px; } .projects-detail-title { + flex: 1; + min-width: 0; margin: 0; font-size: 1.375rem; font-weight: 600; color: #0f172a; letter-spacing: -0.02em; + line-height: 1.35; + word-break: break-word; + overflow-wrap: anywhere; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; } .projects-status-pill { + flex-shrink: 0; + margin-top: 4px; display: inline-flex; align-items: center; font-size: 0.6875rem; @@ -22717,56 +22728,18 @@ button.chat-files-dropdown-item:hover:not(:disabled) { font-size: 0.8125rem; color: #94a3b8; } -.projects-detail-desc-block { +.projects-detail-desc { margin: 10px 0 0; max-width: min(640px, 100%); -} -.projects-detail-desc { - margin: 0; font-size: 0.875rem; color: #475569; line-height: 1.55; - white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere; -} -.projects-detail-desc.is-collapsed { - max-height: 4.65em; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; overflow: hidden; - position: relative; -} -.projects-detail-desc.is-collapsed::after { - content: ''; - position: absolute; - left: 0; - right: 0; - bottom: 0; - height: 1.4em; - background: linear-gradient(to bottom, rgba(255, 255, 255, 0), #fff 85%); - pointer-events: none; -} -.projects-detail-desc.is-expanded { - max-height: min(240px, 32vh); - overflow-y: auto; - overscroll-behavior: contain; - padding-right: 4px; -} -.projects-detail-desc-toggle { - display: inline-block; - margin-top: 6px; - padding: 0; - border: none; - background: none; - color: #0066ff; - font-size: 0.8125rem; - font-weight: 500; - cursor: pointer; - line-height: 1.4; -} -.projects-detail-desc-toggle:hover, -.projects-detail-desc-toggle:focus-visible { - text-decoration: underline; - outline: none; } .projects-description-textarea { max-height: 200px; diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index 7a72abd8..b94c1c59 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -286,6 +286,8 @@ "status": "Status", "modalNewTitle": "New project", "modalNewSubtitle": "After creation, bind conversations to share fact board across chats", + "modalEditTitle": "Edit project", + "modalEditSubtitle": "Update project name and description", "projectName": "Project name", "projectNamePlaceholder": "e.g. Client A Web pentest", "projectDescription": "Project description", @@ -411,6 +413,7 @@ "dangerZoneHint": "Archived projects are hidden unless 'Show archived' is enabled; deletion removes all facts permanently.", "archiveRestore": "Archive / Restore", "archiveProject": "Archive", + "editProject": "Edit", "restoreProjectActive": "Restore to active", "projectActions": "Project actions", "deleteProject": "Delete project", diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index 5396c9e1..c7529210 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -274,6 +274,8 @@ "status": "状态", "modalNewTitle": "新建项目", "modalNewSubtitle": "创建后可绑定对话,跨会话共享事实黑板", + "modalEditTitle": "编辑项目", + "modalEditSubtitle": "修改项目名称与描述", "projectName": "项目名称", "projectNamePlaceholder": "例如:某客户 Web 渗透", "projectDescription": "项目描述", @@ -399,6 +401,7 @@ "dangerZoneHint": "归档后需在列表勾选「显示已归档」才能查看;删除将清除全部事实且不可恢复。", "archiveRestore": "归档 / 恢复", "archiveProject": "归档", + "editProject": "编辑", "restoreProjectActive": "恢复为进行中", "projectActions": "项目操作", "deleteProject": "删除项目", diff --git a/web/static/js/projects.js b/web/static/js/projects.js index 4da8fd14..57985dc6 100644 --- a/web/static/js/projects.js +++ b/web/static/js/projects.js @@ -12,7 +12,6 @@ let _projectsFetchPromise = null; const PROJECT_ACTIVE_KEY = 'cyberstrike.activeProjectId'; const PROJECT_DESCRIPTION_MAX_LENGTH = 4000; -const PROJECT_DESC_COLLAPSE_THRESHOLD = 180; function tp(key, opts) { if (typeof window.t === 'function') return window.t(key, opts); @@ -625,57 +624,27 @@ function clampProjectDescription(text) { return s.slice(0, PROJECT_DESCRIPTION_MAX_LENGTH); } -function projectDescriptionNeedsToggle(text) { - const s = (text || '').trim(); - if (!s) return false; - if (s.length > PROJECT_DESC_COLLAPSE_THRESHOLD) return true; - return s.split('\n').length > 3; +function renderProjectDetailTitle(name) { + const titleEl = document.getElementById('projects-detail-title'); + if (!titleEl) return; + const text = (name || '').trim() || tp('projects.defaultProjectName'); + titleEl.textContent = text; + titleEl.title = text; } function renderProjectDetailDesc(desc) { - const blockEl = document.getElementById('projects-detail-desc-block'); const descEl = document.getElementById('projects-detail-desc'); - const toggleEl = document.getElementById('projects-detail-desc-toggle'); - if (!descEl || !blockEl) return; + if (!descEl) return; const text = (desc || '').trim(); if (!text) { - blockEl.hidden = true; + descEl.hidden = true; descEl.textContent = ''; - descEl.className = 'projects-detail-desc is-collapsed'; - if (toggleEl) { - toggleEl.hidden = true; - toggleEl.dataset.expanded = 'false'; - } + descEl.removeAttribute('title'); return; } descEl.textContent = text; - blockEl.hidden = false; - descEl.classList.remove('is-expanded'); - descEl.classList.add('is-collapsed'); - if (toggleEl) { - const needsToggle = projectDescriptionNeedsToggle(text); - toggleEl.hidden = !needsToggle; - toggleEl.textContent = tp('projects.descExpand'); - toggleEl.dataset.expanded = 'false'; - } -} - -function toggleProjectDetailDesc() { - const descEl = document.getElementById('projects-detail-desc'); - const toggleEl = document.getElementById('projects-detail-desc-toggle'); - if (!descEl || !toggleEl || toggleEl.hidden) return; - const expanded = toggleEl.dataset.expanded === 'true'; - if (expanded) { - descEl.classList.add('is-collapsed'); - descEl.classList.remove('is-expanded'); - toggleEl.textContent = tp('projects.descExpand'); - toggleEl.dataset.expanded = 'false'; - } else { - descEl.classList.remove('is-collapsed'); - descEl.classList.add('is-expanded'); - toggleEl.textContent = tp('projects.descCollapse'); - toggleEl.dataset.expanded = 'true'; - } + descEl.title = text; + descEl.hidden = false; } function updateProjectStatusPill(status) { @@ -731,8 +700,7 @@ async function selectProject(id) { const res = await apiFetch(`/api/projects/${id}`); if (!res.ok) throw new Error(tp('projects.projectNotFound')); const p = await res.json(); - const titleEl = document.getElementById('projects-detail-title'); - if (titleEl) titleEl.textContent = p.name || tp('projects.defaultProjectName'); + renderProjectDetailTitle(p.name); document.getElementById('project-edit-name').value = p.name || ''; document.getElementById('project-edit-description').value = p.description || ''; document.getElementById('project-edit-scope').value = p.scope_json || ''; @@ -743,8 +711,7 @@ async function selectProject(id) { updateProjectStatusPill(p.status || 'active'); const metaEl = document.getElementById('projects-detail-meta'); if (metaEl) metaEl.textContent = tpFmt('projects.updatedPrefix', `Updated ${formatProjectTime(p.updated_at)}`, { time: formatProjectTime(p.updated_at) }); - const descEl = document.getElementById('projects-detail-desc'); - if (descEl) renderProjectDetailDesc(p.description); + renderProjectDetailDesc(p.description); projectNameById[p.id] = p.name || p.id; } catch (e) { console.warn(e); @@ -1246,6 +1213,33 @@ function showNewProjectModal() { openProjectsOverlay('project-modal'); } +async function showEditProjectModal(projectId) { + if (!projectId) return; + window._projectModalFromChat = false; + window._projectModalEditId = projectId; + document.getElementById('project-modal-title').textContent = tp('projects.modalEditTitle'); + const sub = document.getElementById('project-modal-subtitle'); + if (sub) sub.textContent = tp('projects.modalEditSubtitle'); + const submitBtn = document.getElementById('project-modal-submit-btn'); + if (submitBtn) submitBtn.textContent = tp('projects.saveChanges'); + let p = findProjectById(projectId); + if (!p) { + try { + const res = await apiFetch(`/api/projects/${encodeURIComponent(projectId)}`); + if (!res.ok) throw new Error(tp('projects.projectNotFound')); + p = await res.json(); + } catch (e) { + alert(e.message || tp('projects.projectNotFound')); + window._projectModalEditId = null; + return; + } + } + document.getElementById('project-modal-name').value = p.name || ''; + document.getElementById('project-modal-description').value = p.description || ''; + openProjectsOverlay('project-modal'); + setTimeout(() => document.getElementById('project-modal-name')?.focus(), 0); +} + /** 从对话区「选择项目」面板打开新建项目,创建成功后自动绑定当前对话 */ function showNewProjectModalFromChat() { closeChatProjectPanel(); @@ -1285,6 +1279,7 @@ async function saveProjectModal() { function closeProjectModal() { window._projectModalFromChat = false; + window._projectModalEditId = null; closeProjectsOverlay('project-modal'); } @@ -1390,8 +1385,10 @@ function showProjectListActionMenu(event, projectId) { const p = findProjectById(projectId); if (!p) return; _projectListMenuTargetId = projectId; + const editText = document.getElementById('projects-list-menu-edit-text'); const archiveText = document.getElementById('projects-list-menu-archive-text'); const deleteText = document.getElementById('projects-list-menu-delete-text'); + if (editText) editText.textContent = tp('projects.editProject'); if (archiveText) { archiveText.textContent = p.status === 'archived' ? tp('projects.restoreProjectActive') @@ -1461,6 +1458,13 @@ async function toggleProjectArchiveFromListMenu() { await toggleProjectArchiveById(projectId); } +function editProjectFromListMenu() { + const projectId = _projectListMenuTargetId; + closeProjectListActionMenu(); + if (!projectId) return; + showEditProjectModal(projectId); +} + async function deleteProjectFromListMenu() { const projectId = _projectListMenuTargetId; closeProjectListActionMenu(); @@ -1899,6 +1903,7 @@ if (document.readyState === 'loading') { window.initProjectsPage = initProjectsPage; window.showNewProjectModal = showNewProjectModal; +window.showEditProjectModal = showEditProjectModal; window.showNewProjectModalFromChat = showNewProjectModalFromChat; window.saveProjectModal = saveProjectModal; window.closeProjectModal = closeProjectModal; @@ -1914,6 +1919,7 @@ window.saveProjectSettings = saveProjectSettings; window.archiveCurrentProject = archiveCurrentProject; window.deleteCurrentProject = deleteCurrentProject; window.showProjectListActionMenu = showProjectListActionMenu; +window.editProjectFromListMenu = editProjectFromListMenu; window.toggleProjectArchiveFromListMenu = toggleProjectArchiveFromListMenu; window.deleteProjectFromListMenu = deleteProjectFromListMenu; window.refreshChatProjectSelector = refreshChatProjectSelector; diff --git a/web/templates/index.html b/web/templates/index.html index 28fa0ef4..5c320a4d 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -1476,10 +1476,7 @@ 进行中

- +
0 条事实 0 个漏洞 @@ -3783,6 +3780,9 @@