From 7d1d770abd1a0c8535e8c58129877c5b12078f30 Mon Sep 17 00:00:00 2001 From: JunXiaoRuo <47996900+JunXiaoRuo@users.noreply.github.com> Date: Thu, 17 Sep 2026 18:58:33 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B6=85=E9=95=BF?= =?UTF-8?q?=E5=8D=95=E8=A1=8C=E5=AE=9A=E4=BD=8D=E5=B9=B6=E5=87=86=E5=A4=87?= =?UTF-8?q?=20v1.7.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/release-notes/v1.7.3.md | 41 ++++++++++++++++ docs/update.md | 48 +++++++++++++++++- package-lock.json | 4 +- package.json | 2 +- public/app-sftp-editor.js | 9 ++-- public/app-sftp-svg-source.js | 87 ++++++++++++++++++++++++++++----- public/app.css | 5 ++ scripts/regression-check.js | 1 + scripts/ui-smoke-electron.js | 39 ++++++++++++++- 9 files changed, 216 insertions(+), 20 deletions(-) create mode 100644 .github/release-notes/v1.7.3.md diff --git a/.github/release-notes/v1.7.3.md b/.github/release-notes/v1.7.3.md new file mode 100644 index 0000000..74a7120 --- /dev/null +++ b/.github/release-notes/v1.7.3.md @@ -0,0 +1,41 @@ +[English](#english) · [简体中文](#简体中文) + + +### English + +> This maintenance release makes internal editor navigation reliable for very long single-line files and makes selections easier to see, including SVG source locating. + +#### Important fixes + +- Text search in any file opened by the internal text editor now scrolls horizontally to matches inside very long single lines instead of only moving to the line. +- Editor selections are easier to see in both light and dark themes, and SVG preview-to-source navigation selects and clearly marks the complete target tag. + +#### Known issue + +- macOS packages are currently not code-signed or notarized, so Gatekeeper may report that Terma is damaged or cannot be opened. See [Issue #36](https://github.com/zmide/Terma/issues/36) for a temporary workaround. + +#### Changes in this release + +- No pull requests were merged after v1.7.2; these user-visible changes were committed directly by the project maintainer. + +**Full Changelog**: [v1.7.2...v1.7.3](https://github.com/zmide/Terma/compare/v1.7.2...v1.7.3) + + +### 简体中文 + +> 本维护版本改进超长单行文件的编辑器定位,并让选中内容更容易看清,包括 SVG 源码定位。 + +#### 重要修复 + +- 所有通过内部文本编辑器打开的文件,在超长单行中查找内容时都会横向滚动到实际匹配位置,不再只定位到同一行。 +- 编辑器选区在浅色和深色主题下都更容易看清;从 SVG 预览定位源码时,会选中并醒目标记完整的目标标签。 + +#### 已知问题 + +- macOS 发布包目前尚未完成代码签名和公证,Gatekeeper 可能提示 Terma 已损坏或无法打开。临时处理方法见 [Issue #36](https://github.com/zmide/Terma/issues/36)。 + +#### 本次变更 + +- v1.7.2 之后没有合并 Pull Request;本版用户可见变化均由项目维护者直接提交。 + +**完整变更**:[v1.7.2...v1.7.3](https://github.com/zmide/Terma/compare/v1.7.2...v1.7.3) diff --git a/docs/update.md b/docs/update.md index 8d5e6b2..4feb894 100644 --- a/docs/update.md +++ b/docs/update.md @@ -7,7 +7,7 @@ ### English -> The next release will focus on recoverable terminal sessions, broader terminal AI interoperability, and cross-platform remote desktop acceptance. +> The next release will continue recoverable terminal, interoperability, and cross-platform remote desktop work. #### Planned @@ -18,7 +18,7 @@ ### 简体中文 -> 下一版本将重点推进可恢复终端、更多终端 AI 互操作场景和跨平台远程桌面验收。 +> 下一版本继续推进可恢复终端、互操作场景和跨平台远程桌面验收。 #### 计划 @@ -26,6 +26,50 @@ - 使用更多 OpenAI 兼容网关扩展终端 AI 互操作测试,同时保持命令执行和 MCP 确认边界不变。 - 完成 TigerVNC、noVNC、系统客户端在三种桌面平台上的文本、中文和图片剪贴板验收。 +## v1.7.3 + +[English](#english) · [简体中文](#简体中文) + + +### English + +> This maintenance release makes internal editor navigation reliable for very long single-line files and makes selections easier to see, including SVG source locating. + +#### Important fixes + +- Text search in any file opened by the internal text editor now scrolls horizontally to matches inside very long single lines instead of only moving to the line. +- Editor selections are easier to see in both light and dark themes, and SVG preview-to-source navigation selects and clearly marks the complete target tag. + +#### Known issue + +- macOS packages are currently not code-signed or notarized, so Gatekeeper may report that Terma is damaged or cannot be opened. See [Issue #36](https://github.com/zmide/Terma/issues/36) for a temporary workaround. + +#### Changes in this release + +- No pull requests were merged after v1.7.2; these user-visible changes were committed directly by the project maintainer. + +**Full Changelog**: [v1.7.2...v1.7.3](https://github.com/zmide/Terma/compare/v1.7.2...v1.7.3) + + +### 简体中文 + +> 本维护版本改进超长单行文件的编辑器定位,并让选中内容更容易看清,包括 SVG 源码定位。 + +#### 重要修复 + +- 所有通过内部文本编辑器打开的文件,在超长单行中查找内容时都会横向滚动到实际匹配位置,不再只定位到同一行。 +- 编辑器选区在浅色和深色主题下都更容易看清;从 SVG 预览定位源码时,会选中并醒目标记完整的目标标签。 + +#### 已知问题 + +- macOS 发布包目前尚未完成代码签名和公证,Gatekeeper 可能提示 Terma 已损坏或无法打开。临时处理方法见 [Issue #36](https://github.com/zmide/Terma/issues/36)。 + +#### 本次变更 + +- v1.7.2 之后没有合并 Pull Request;本版用户可见变化均由项目维护者直接提交。 + +**完整变更**:[v1.7.2...v1.7.3](https://github.com/zmide/Terma/compare/v1.7.2...v1.7.3) + ## v1.7.2 [English](#english) · [简体中文](#简体中文) diff --git a/package-lock.json b/package-lock.json index 51980c2..f7f0b84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "terma", - "version": "1.7.2", + "version": "1.7.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "terma", - "version": "1.7.2", + "version": "1.7.3", "license": "GPL-3.0-only", "dependencies": { "@novnc/novnc": "^1.7.0", diff --git a/package.json b/package.json index afe44f3..8a34036 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "terma", - "version": "1.7.2", + "version": "1.7.3", "releaseRevision": 1, "private": true, "license": "GPL-3.0-only", diff --git a/public/app-sftp-editor.js b/public/app-sftp-editor.js index 7d98fb5..10a371c 100644 --- a/public/app-sftp-editor.js +++ b/public/app-sftp-editor.js @@ -1017,12 +1017,14 @@ function sftpTextModal(title, content, size=0, limit=5*1024*1024, encoding="utf8 const startPosition = aceDocument.indexToPosition(start, 0); const endPosition = aceDocument.indexToPosition(end, 0); aceEditor.selection.setRange(new Range(startPosition.row, startPosition.column, endPosition.row, endPosition.column), false); - aceEditor.scrollToLine(startPosition.row, true, true); + aceEditor.scrollToLine?.(startPosition.row, true, true); + aceEditor.renderer?.scrollCursorIntoView?.(startPosition, 0.45); if (!keepSearchFocus) aceEditor.focus(); } else { fallbackEditor.setSelectionRange(start, end); - const line = editorSearchSource().slice(0, start).split("\n").length - 1; - fallbackEditor.scrollTop = Math.max(0, line * 20 - fallbackEditor.clientHeight / 2); + const source = editorSearchSource(), lineStart = source.lastIndexOf("\n", Math.max(0, start - 1)) + 1, column = Math.max(0, start - lineStart), line = source.slice(0, start).split("\n").length - 1; + const fontSize = Number.parseFloat(getComputedStyle(fallbackEditor).fontSize || "14") || 14, lineHeight = Number.parseFloat(getComputedStyle(fallbackEditor).lineHeight || "") || fontSize * 1.45, approximateCharWidth = Math.max(6, fontSize * .62); + fallbackEditor.scrollTop = Math.max(0, line * lineHeight - fallbackEditor.clientHeight * .4); fallbackEditor.scrollLeft = Math.max(0, Math.min(Math.max(0, fallbackEditor.scrollWidth - fallbackEditor.clientWidth), column * approximateCharWidth - fallbackEditor.clientWidth * .42)); if (!keepSearchFocus) fallbackEditor.focus(); } }; @@ -1050,6 +1052,7 @@ function sftpTextModal(title, content, size=0, limit=5*1024*1024, encoding="utf8 releaseSvgPreviewInteractions(); releaseSvgPreviewLayout(); releaseFloatingEditor(); + svgSourceLocator?.clear?.(); if (editorKey && sftpFloatingEditorRegistry.get(editorKey)?.modal === modal) sftpFloatingEditorRegistry.delete(editorKey); try { aceEditor?.destroy(); } catch {} lightSource = ""; diff --git a/public/app-sftp-svg-source.js b/public/app-sftp-svg-source.js index 358565b..d8254c3 100644 --- a/public/app-sftp-svg-source.js +++ b/public/app-sftp-svg-source.js @@ -1,5 +1,40 @@ function createSftpSvgSourceLocator({getText, getAceEditor, getFallbackEditor, onHighlight}) { - const findSourceIndex = (text, targetId, {preferReference=false, sourceElement=null} = {}) => { + let aceTargetMarkerId = null; + let aceLineMarkerId = null; + let aceGutterRow = -1; + let fallbackHighlightActive = false; + const getAceRange = (start, end) => { + const Range = globalThis.ace?.require?.("ace/range")?.Range; + if (!Range) return null; + return new Range(start.row, start.column, end.row, end.column); + }; + const clearAceHighlight = aceEditor => { + const session = aceEditor?.session; + if (!session) return; + if (aceTargetMarkerId !== null) { + session.removeMarker?.(aceTargetMarkerId); + aceTargetMarkerId = null; + } + if (aceLineMarkerId !== null) { + session.removeMarker?.(aceLineMarkerId); + aceLineMarkerId = null; + } + if (aceGutterRow >= 0) { + session.removeGutterDecoration?.(aceGutterRow, "sftp-svg-source-gutter"); + aceGutterRow = -1; + } + }; + const highlightAceRange = (aceEditor, start, end) => { + const session = aceEditor?.session; + const range = getAceRange(start, end); + if (!session || !range) return; + clearAceHighlight(aceEditor); + aceTargetMarkerId = session.addMarker?.(range, "sftp-svg-source-target", "text", true) ?? null; + aceLineMarkerId = session.addMarker?.(getAceRange(start, {row:start.row, column:Math.max(1, start.column + 1)}), "sftp-svg-source-line", "fullLine", false) ?? null; + session.addGutterDecoration?.(start.row, "sftp-svg-source-gutter"); + aceGutterRow = start.row; + }; + const findSourceRange = (text, targetId, {preferReference=false, sourceElement=null} = {}) => { const wanted = String(targetId || ""); if (!wanted) return -1; const candidates = []; @@ -35,33 +70,56 @@ function createSftpSvgSourceLocator({getText, getAceEditor, getFallbackEditor, o if (attributes[attributeName] === attributeValue) score += 6; } } - candidates.push({index:match.index, score}); + candidates.push({index:match.index, length:raw.length, score}); } if (!/\/\s*>$/.test(raw) && !["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"].includes(name)) stack.push(name); } candidates.sort((left, right) => right.score - left.score || left.index - right.index); - return candidates[0]?.index ?? -1; + return candidates[0] ? {index:candidates[0].index, length:candidates[0].length} : null; }; const focusSourceId = (target, options={}) => { const targetInfo = target && typeof target === "object" ? target : {id:target}; const targetId = String(targetInfo.id || "").trim(); if (!targetId) return; const text = String(getText?.() || ""); - let index = findSourceIndex(text, targetId, {preferReference:targetInfo.nodeName === "use" || options.preferReference, sourceElement:targetInfo.sourceElement}); - if (index < 0) index = text.indexOf(targetId); - if (index < 0) return; + const sourceRange = findSourceRange(text, targetId, {preferReference:targetInfo.nodeName === "use" || options.preferReference, sourceElement:targetInfo.sourceElement}); + const index = sourceRange?.index ?? text.indexOf(targetId); + if (index < 0) return false; + const rangeLength = Math.max(targetId.length, sourceRange?.length || 0); const aceEditor = getAceEditor?.(); const fallbackEditor = getFallbackEditor?.(); if (aceEditor) { - const position = aceEditor.session.getDocument().indexToPosition(index, 0); - aceEditor.selection.moveTo(position.row, position.column); - aceEditor.scrollToLine(position.row, true, true); + const document = aceEditor.session.getDocument(); + const start = document.indexToPosition(index, 0); + const end = document.indexToPosition(index + rangeLength, 0); + const range = getAceRange(start, end); + // Keep the caret at the opening tag while retaining the full range + // selection, so existing source-to-preview navigation can still read + // the tag from the cursor position. + if (range) aceEditor.selection.setRange(range, true); + else aceEditor.selection.moveTo(start.row, start.column); + aceEditor.scrollToLine(start.row, true, true); + aceEditor.renderer?.scrollCursorIntoView?.(start, 0.45); + highlightAceRange(aceEditor, start, end); aceEditor.focus(); } else if (fallbackEditor) { fallbackEditor.focus(); - fallbackEditor.setSelectionRange(index, index + targetId.length); + fallbackEditor.setSelectionRange(index, index + rangeLength); + const lineStart = text.lastIndexOf("\n", Math.max(0, index - 1)) + 1; + const column = Math.max(0, index - lineStart); + const line = text.slice(0, index).split("\n").length - 1; + const fontSize = Number.parseFloat(globalThis.getComputedStyle?.(fallbackEditor)?.fontSize || "14") || 14; + const approximateCharWidth = Math.max(6, fontSize * .62); + fallbackEditor.scrollTop = Math.max(0, line * fontSize * 1.45 - fallbackEditor.clientHeight * .4); + fallbackEditor.scrollLeft = Math.max(0, Math.min( + Math.max(0, fallbackEditor.scrollWidth - fallbackEditor.clientWidth), + column * approximateCharWidth - fallbackEditor.clientWidth * .42 + )); + fallbackEditor.classList.add("sftp-source-locate-active"); + fallbackHighlightActive = true; } onHighlight?.(targetId, {locate:options.locate !== false, force:true}); + return true; }; const idForCursor = () => { const text = String(getText?.() || ""); @@ -102,7 +160,14 @@ function createSftpSvgSourceLocator({getText, getAceEditor, getFallbackEditor, o } return ""; }; - return {focusSourceId, idForCursor}; + const clear = () => { + const aceEditor = getAceEditor?.(); + clearAceHighlight(aceEditor); + const fallbackEditor = getFallbackEditor?.(); + if (fallbackHighlightActive) fallbackEditor?.classList.remove("sftp-source-locate-active"); + fallbackHighlightActive = false; + }; + return {focusSourceId, idForCursor, clear}; } function createSftpSvgEditorScheduler({isActive, renderPreview, syncCursor}) { diff --git a/public/app.css b/public/app.css index a25695e..450a79c 100644 --- a/public/app.css +++ b/public/app.css @@ -2832,6 +2832,11 @@ body.sftp-item-drag-active .tab[data-kind="sftp"], body.sftp-item-drag-active .t .sftp-editor-workspace.showing-svg-preview > .sftp-editor-svg-splitter { grid-column:2; grid-row:1; } .sftp-editor-workspace.showing-svg-preview > :is(.sftp-code-editor,.code-editor,.sftp-light-editor-shell) { grid-column:3; grid-row:1; } .sftp-code-editor { width:100%; height:100%; min-height:0; border:1px solid var(--line); border-radius:5px; overflow:hidden; } +.sftp-code-editor .ace_marker-layer .ace_selection { background:color-mix(in srgb,var(--accent2) 48%,transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--accent) 72%,var(--accent2)); } +.sftp-code-editor .ace_marker-layer .sftp-svg-source-line { position:absolute; z-index:1; background:color-mix(in srgb,var(--accent2) 12%,transparent); border-top:1px solid color-mix(in srgb,var(--accent2) 42%,transparent); border-bottom:1px solid color-mix(in srgb,var(--accent2) 42%,transparent); } +.sftp-code-editor .ace_marker-layer .sftp-svg-source-target { position:absolute; z-index:2; box-sizing:border-box; border:1px solid color-mix(in srgb,var(--accent) 78%,var(--accent2)); border-radius:2px; background:color-mix(in srgb,var(--accent2) 36%,transparent); box-shadow:0 0 0 1px color-mix(in srgb,var(--accent) 42%,transparent),0 0 8px color-mix(in srgb,var(--accent2) 58%,transparent); } +.sftp-code-editor .ace_gutter-cell.sftp-svg-source-gutter { color:var(--text); background:color-mix(in srgb,var(--accent2) 22%,transparent); box-shadow:inset 3px 0 0 var(--accent2); font-weight:700; } +.text-editor.sftp-source-locate-active { outline:2px solid color-mix(in srgb,var(--accent2) 68%,var(--line)); outline-offset:-2px; box-shadow:0 0 0 3px color-mix(in srgb,var(--accent2) 18%,transparent),0 0 14px color-mix(in srgb,var(--accent2) 34%,transparent); } .sftp-svg-editor-preview { position:relative; min-width:0; min-height:0; overflow:hidden; display:grid; grid-template-rows:auto minmax(0,1fr); border:1px solid var(--line); border-radius:5px; background:repeating-conic-gradient(var(--panel2) 0 25%,var(--panel) 0 50%) 50% / 18px 18px; } .sftp-svg-editor-preview-toolbar { min-width:0; min-height:34px; display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:5px 8px; padding:4px 6px; border-bottom:1px solid var(--line); background:color-mix(in srgb,var(--panel2) 82%,transparent); } .sftp-svg-editor-preview-hint { min-width:120px; flex:1 1 150px; overflow:hidden; color:var(--muted); font-size:11px; text-overflow:ellipsis; white-space:nowrap; } diff --git a/scripts/regression-check.js b/scripts/regression-check.js index 168cdc7..620c4e5 100644 --- a/scripts/regression-check.js +++ b/scripts/regression-check.js @@ -262,6 +262,7 @@ async function main() { ok("页面通知支持分类开关、显示时长和独立悬浮任务卡设置", read("src/runtime-settings.ts").includes("DEFAULT_NOTIFICATION_DISPLAY") && read("src/runtime-settings.ts").includes("normalizeNotificationDisplay") && read("public/app-utils.js").includes("function notificationDisplayPreferences") && read("public/app-utils.js").includes("preference?.enabled === false") && readFrontendDomain(root, "settings").includes('id="notificationInfoEnabled"') && readFrontendDomain(root, "settings").includes('id="notificationProgressSuccessDuration"') && readFrontendDomain(root, "settings").includes('id="taskCenterFloatingProgressEnabled"') && read("public/app.css").includes(".notification-preference-row")); ok("桌面通知可独立关闭并明确区分浏览器授权与 Terma 开关", read("src/runtime-settings.ts").includes("desktop_notifications_enabled") && read("src/routes/storage-routes.ts").includes("desktop_notifications_enabled:data.desktop_notifications_enabled") && read("public/app-utils.js").includes("runtimeSettings?.saved?.desktop_notifications_enabled !== false") && readFrontendDomain(root, "settings").includes('id="notificationPermissionStatus"') && readFrontendDomain(root, "settings").includes('id="desktopNotificationToggleBtn"') && readFrontendDomain(root, "settings").includes('id="desktopNotificationStatus"') && read("public/app-settings-security.js").includes("function toggleDesktopNotifications") && read("public/app-settings-security.js").includes("updateDesktopNotificationControls") && read("public/app-settings-security.js").includes("notifications_authorized_disabled")); ok("SFTP SVG 搜索按设备身份去重并保留定位上下文", sftpPreviewFrontend.includes("svgSearchTarget") && sftpPreviewFrontend.includes("exactTargets") && sftpPreviewFrontend.includes("source.closest(\"metadata\")") && sftpPreviewFrontend.includes("const contextWidth = Math.max(720") && sftpPreviewFrontend.includes("fit();")); + ok("SFTP 文本查找与 SVG 源码定位支持单行横向滚动并强化选区高亮", read("public/app-sftp-svg-source.js").includes("findSourceRange") && read("public/app-sftp-svg-source.js").includes("selection.setRange") && read("public/app-sftp-svg-source.js").includes("scrollCursorIntoView") && read("public/app-sftp-svg-source.js").includes("sftp-svg-source-target") && read("public/app-sftp-editor.js").includes("renderer?.scrollCursorIntoView") && read("public/app-sftp-editor.js").includes("fallbackEditor.scrollLeft") && read("public/app.css").includes(".ace_selection") && read("public/app.css").includes(".sftp-svg-source-target") && read("scripts/ui-smoke-electron.js").includes("svgSplitOneLineSourceFocus")); ok( "Linux 缺少 FUSE 时提示原因并保留兼容拖出", sftpFrontend.includes("function sftpNativeDragFallbackInfo()") diff --git a/scripts/ui-smoke-electron.js b/scripts/ui-smoke-electron.js index b459b9a..0941ef5 100644 --- a/scripts/ui-smoke-electron.js +++ b/scripts/ui-smoke-electron.js @@ -9484,6 +9484,23 @@ app.whenReady().then(async () => { } const svgEditorRoot=svgEditorPreview?.querySelector('.sftp-svg-editor-preview-stage')?.shadowRoot?.querySelector('svg'); imagePreviewUi.svgSplitRefresh=Boolean(svgEditorRoot?.querySelector('#'+svgLiveRefreshId)); + const svgEditorSearchToggle=svgEditorWindow?.querySelector('#sftpEditorSearchToggle'); + const svgEditorSearchInput=svgEditorWindow?.querySelector('#sftpEditorSearchInput'); + const svgEditorSearchClose=svgEditorWindow?.querySelector('#sftpEditorSearchClose'); + svgEditorSearchToggle?.click(); + if (svgEditorSearchInput) { + svgEditorSearchInput.value=svgLiveRefreshId; + svgEditorSearchInput.dispatchEvent(new Event('input',{bubbles:true})); + } + await new Promise(resolve=>setTimeout(resolve,220)); + imagePreviewUi.svgLongLineSearch=Boolean(svgEditorSearchInput + && svgEditorWindow?.querySelector('#sftpEditorSearchCount')?.textContent==='1/1' + && svgAceEditor?.getSelectedText?.().includes(svgLiveRefreshId)); + imagePreviewUi.svgLongLineSearchDiagnostics={ + count:svgEditorWindow?.querySelector('#sftpEditorSearchCount')?.textContent||'', + selectedText:svgAceEditor?.getSelectedText?.()||'' + }; + svgEditorSearchClose?.click(); const svgClickTarget=svgEditorRoot?.querySelector('#PD_11100000_14419'); const svgAutoFocusZoomBefore=Number.parseInt(svgEditorPreview?.querySelector('[data-svg-preview-zoom-value]')?.textContent||'',10); const svgEditorClickBeforePosition=svgAceEditor?.getCursorPosition?.(); @@ -9492,6 +9509,11 @@ app.whenReady().then(async () => { const svgEditorClickPosition=svgAceEditor?.getCursorPosition?.(); const svgEditorClickRow=svgEditorClickPosition?.row; const svgEditorClickLine=Number.isFinite(svgEditorClickRow) ? svgAceEditor.session.getLine(svgEditorClickRow) : ''; + const svgEditorClickSelection=svgAceEditor?.getSelectionRange?.(); + const svgEditorClickSelectedText=svgAceEditor?.getSelectedText?.()||''; + const svgEditorClickScrollLeft=Number(svgAceEditor?.renderer?.getScrollLeft?.() ?? svgAceEditor?.renderer?.scroller?.scrollLeft ?? 0); + const svgEditorClickMarkers=svgAceEditor?.session?.getMarkers?.(true)||{}; + const svgEditorClickHasMarker=Object.values(svgEditorClickMarkers).some(marker=>String(marker?.clazz||marker?.className||'').includes('sftp-svg-source-target')); const svgClickTargetAfter=svgEditorRoot?.querySelector('#PD_11100000_14419'); const svgAutoFocusZoomAfter=Number.parseInt(svgEditorPreview?.querySelector('[data-svg-preview-zoom-value]')?.textContent||'',10); const svgAutoFocusViewport=svgEditorPreview?.querySelector('.sftp-svg-editor-preview-viewport'); @@ -9550,6 +9572,21 @@ app.whenReady().then(async () => { && (!svgEditorClickBeforePosition || svgEditorClickBeforePosition.row!==svgEditorClickPosition?.row || svgEditorClickBeforePosition.column!==svgEditorClickPosition?.column)); + imagePreviewUi.svgSplitOneLineSourceFocus=Boolean(svgAceEditor + && svgEditorClickSelection + && svgEditorClickSelection.start?.row===svgEditorClickSelection.end?.row + && svgEditorClickSelectedText.includes('PD_11100000_14419') + && svgEditorClickHasMarker + && svgEditorClickScrollLeft>=0); + imagePreviewUi.svgSplitSourceFocusDiagnostics={ + selectedText:svgEditorClickSelectedText.slice(0,180), + range:svgEditorClickSelection ? { + start:svgEditorClickSelection.start, + end:svgEditorClickSelection.end + } : null, + scrollLeft:svgEditorClickScrollLeft, + hasMarker:svgEditorClickHasMarker + }; const svgStyledUse=svgEditorRoot?.querySelector('#styled-use'); svgStyledUse?.dispatchEvent(new MouseEvent('click',{bubbles:true,composed:true})); await new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve))); @@ -12127,7 +12164,7 @@ app.whenReady().then(async () => { const downloadNoticeUi = sftpUi.downloadNoticeUi || {}; const jobUiFailed = !jobUi.found || !jobUi.singleGlobalEntry || !jobUi.noPaneTaskRegions || !jobUi.failedStatusVisible || !jobUi.totalProgressVisible || !jobUi.totalProgressIndeterminate || !jobUi.totalProgressHidesWhenIdle || !jobUi.floatingVisibleBelowHeader || !jobUi.floatingActions || !jobUi.floatingResumeAction || !jobUi.floatingProgress || !jobUi.floatingOpensTaskCenter || !jobUi.floatingCloseHidesCurrent || !jobUi.floatingNewTaskReopens || !jobUi.floatingMutePersists || !jobUi.floatingSettingRestores || !jobUi.drawerOpened || !jobUi.drawerDefaultCompact || !jobUi.currentOnly || !jobUi.currentActions || !jobUi.failedOnly || !jobUi.failedActions || !jobUi.failedClearAvailable || !jobUi.currentProgress || !jobUi.drawerResizable || !jobUi.drawerResizeAdaptive || !jobUi.drawerResizePersists || !jobUi.drawerResizeReset || !jobUi.deleteDuplicateBlocked || !jobUi.deleteKeepsDrawerOpen || !jobUi.taskLogInitialOpen || !jobUi.taskLogInitialBottom || !jobUi.taskLogRefreshKeepsOpen || !jobUi.taskLogRefreshShowsLatest || !jobUi.taskLogRefreshFollowsBottom || !jobUi.drawerFitsViewport || !jobUi.historyOnly || !jobUi.historyCounts || !jobUi.historyActions || !jobUi.generatedTaskUi?.found || !jobUi.generatedTaskUi?.savedPath || !jobUi.generatedTaskUi?.openFile || !jobUi.generatedTaskUi?.openDirectory || !jobUi.generatedTaskUi?.deleteFile || jobUi.generatedTaskUi?.buttonCount !== 3 || !jobUi.generatedTaskUi?.noDuplicateTaskDelete || !jobUi.outsideClickCloses || !jobUi.escapeCloses || !jobUi.runningStatusVisible || !jobUi.nativeDragTaskStopHidden || !jobUi.itemProgress || !jobUi.staleJobResponseIgnored || !jobUi.toastIconsAligned || !jobUi.toastOrderPreserved || !jobUi.toastStackedDown || !jobUi.toastAvoidsFloatingTask || !jobUi.toastExitAnimated || !jobUi.toastReflowAnimated || !jobUi.toastMovedUp; const textEncodingUiFailed = !textEncodingUi.opened || !textEncodingUi.initialCentered || !textEncodingUi.aceLoaded || textEncodingUi.selected !== 'gbk' || !textEncodingUi.manualLanguage || !textEncodingUi.nonJsonFormattingHidden || !textEncodingUi.nonUtf8SaveAllowed || !textEncodingUi.nonUtf8SaveSubmitted || !textEncodingUi.remoteSaveKeepsOpen || !textEncodingUi.remoteEditorClosesExplicitly || !textEncodingUi.utf8LimitEnforced || !textEncodingUi.utf8BomIncludesPrefix || !textEncodingUi.normalizationOnlyLimitEnforced || !textEncodingUi.lineEndingLabelsLocalized || !textEncodingUi.lightPaged || !textEncodingUi.lightNextPage || !textEncodingUi.jsonFormatting || !textEncodingUi.jsonHiddenAfterLanguageChange || !textEncodingUi.json5FormattingHidden || !textEncodingUi.wordWrap || !textEncodingUi.persistDefault || !textEncodingUi.backup || !textEncodingUi.shellFormat || !textEncodingUi.shellNonLfWarningPrepared || !['lf','crlf','cr'].every(value=>textEncodingUi.lineEndings?.includes(value)) || !['utf8','utf8bom','gb18030','gbk','big5','shift_jis','euc-kr','latin1'].every(value=>textEncodingUi.options?.includes(value)) || !['auto','json','yaml','xml','sh','batchfile','powershell','javascript','java','c_cpp','sql','markdown'].every(value=>textEncodingUi.languageOptions?.includes(value)); - const imagePreviewUiFailed = !imagePreviewUi.svgOpened || !imagePreviewUi.svgSanitized || !imagePreviewUi.svgEmbeddedStyles || !imagePreviewUi.svgOuterWindowStable || !imagePreviewUi.svgFitsCanvas || !imagePreviewUi.svgZoomButtons || !imagePreviewUi.svgCtrlWheel || !imagePreviewUi.svgSearchFocused || !imagePreviewUi.svgSearchLocated || !imagePreviewUi.svgSearchKeepsContext || !imagePreviewUi.svgSearchClearFits || !imagePreviewUi.svgColorModeNotClipped || !imagePreviewUi.svgInvertPreview || !imagePreviewUi.svgColorModePreservesView || !imagePreviewUi.svgPdfInvert || !imagePreviewUi.svgPdfUnicode || !imagePreviewUi.svgPdfLandscape || !imagePreviewUi.svgPreserveAspectRatio || !imagePreviewUi.svgModeControl || !imagePreviewUi.svgRemoteUsesEncodingAwareRead || !imagePreviewUi.svgRemoteEncodingPreserved || !imagePreviewUi.svgSplitEditor || !imagePreviewUi.svgSplitRefresh || !imagePreviewUi.svgSplitAutoFocus || !imagePreviewUi.svgSplitClickToEditor || !imagePreviewUi.svgSplitUseIdClickToEditor || !imagePreviewUi.svgSplitUseReferenceClickToEditor || !imagePreviewUi.svgSplitMarkerVisibleWithoutAutoFocus || !imagePreviewUi.svgEditorControlsWrapFromFirstColumn || !imagePreviewUi.svgSplitManualZoomDisablesAutoFocus || !imagePreviewUi.svgModeRoundTrip || !imagePreviewUi.svgInlineModeKeepsContentWithoutRemoteRead || !imagePreviewUi.svgModePreservesUnsaved || !imagePreviewUi.notificationAboveFullscreen || !imagePreviewUi.rasterOpened || !imagePreviewUi.rasterZoom; + const imagePreviewUiFailed = !imagePreviewUi.svgOpened || !imagePreviewUi.svgSanitized || !imagePreviewUi.svgEmbeddedStyles || !imagePreviewUi.svgOuterWindowStable || !imagePreviewUi.svgFitsCanvas || !imagePreviewUi.svgZoomButtons || !imagePreviewUi.svgCtrlWheel || !imagePreviewUi.svgSearchFocused || !imagePreviewUi.svgSearchLocated || !imagePreviewUi.svgSearchKeepsContext || !imagePreviewUi.svgSearchClearFits || !imagePreviewUi.svgColorModeNotClipped || !imagePreviewUi.svgInvertPreview || !imagePreviewUi.svgColorModePreservesView || !imagePreviewUi.svgPdfInvert || !imagePreviewUi.svgPdfUnicode || !imagePreviewUi.svgPdfLandscape || !imagePreviewUi.svgPreserveAspectRatio || !imagePreviewUi.svgModeControl || !imagePreviewUi.svgRemoteUsesEncodingAwareRead || !imagePreviewUi.svgRemoteEncodingPreserved || !imagePreviewUi.svgSplitEditor || !imagePreviewUi.svgSplitRefresh || !imagePreviewUi.svgLongLineSearch || !imagePreviewUi.svgSplitAutoFocus || !imagePreviewUi.svgSplitClickToEditor || !imagePreviewUi.svgSplitOneLineSourceFocus || !imagePreviewUi.svgSplitUseIdClickToEditor || !imagePreviewUi.svgSplitUseReferenceClickToEditor || !imagePreviewUi.svgSplitMarkerVisibleWithoutAutoFocus || !imagePreviewUi.svgEditorControlsWrapFromFirstColumn || !imagePreviewUi.svgSplitManualZoomDisablesAutoFocus || !imagePreviewUi.svgModeRoundTrip || !imagePreviewUi.svgInlineModeKeepsContentWithoutRemoteRead || !imagePreviewUi.svgModePreservesUnsaved || !imagePreviewUi.notificationAboveFullscreen || !imagePreviewUi.rasterOpened || !imagePreviewUi.rasterZoom; const nativeDragUiFailed = !nativeDragUi.found || !nativeDragUi.webExternalDragBlocked || !nativeDragUi.linuxFallbackNoticeOnce || !nativeDragUi.linuxFallbackUsesCompatibilityMode || !nativeDragUi.streamingPreparesOnPointerDown || !nativeDragUi.streamingThresholdActivatesOnce || !nativeDragUi.streamingCaptureCancelSurvives || !nativeDragUi.pointerUpCancelsPending || !nativeDragUi.streamingSkipsStage || !nativeDragUi.streamingNativeBlocksParallelBrowserDrag || !nativeDragUi.nativeIdleHintStable || !nativeDragUi.nativeOutsideHintStaysStable || !nativeDragUi.nativeMotionTargetsSftp || !nativeDragUi.nativeTransientMissKeepsTarget || !nativeDragUi.nativeFinalTransientMissKeepsTarget || !nativeDragUi.nativeReleasedClearsStaleTarget || !nativeDragUi.nativeResultCopiesOnce || !nativeDragUi.firstDragOnlyStages || !nativeDragUi.firstDragReset || !nativeDragUi.cacheReused || !nativeDragUi.cachedUnarmedStaysInternal || !nativeDragUi.sameWindowDropDoesNotArm || !nativeDragUi.armedDragStartsSynchronously || !nativeDragUi.failureRearmed || !nativeDragUi.successClearsState || !nativeDragUi.finderRenameNoticeShown; const sftpUiFailed = Boolean(sftpUi.error) || !connectionSessionUi.found || !connectionSessionUi.addressIncludesPort || !connectionSessionUi.disconnectedAction || !connectionSessionUi.disconnectedBanner || !connectionSessionUi.connectedAction || !connectionSessionUi.preservedWhileDisconnected || !connectionSessionUi.automaticConnectShared || !connectionSessionUi.manualDisconnectAutoReconnect || !connectionSessionUi.disconnectedFolderOperationReconnects || !connectionSessionUi.dragFeedbackVisible || !connectionSessionUi.dragTargetViewActivated || !connectionSessionUi.targetListDropPrompt || !connectionSessionUi.targetListDropPromptStable || !connectionSessionUi.targetContentLeaveKeepsPreview || !connectionSessionUi.targetHeaderLeaveKeepsPreview || !connectionSessionUi.crossHostListDropCopies || !connectionSessionUi.crossHostPreviewHandoffSurvives || !connectionSessionUi.crossHostDropHasNoUploadToast || !connectionSessionUi.sameHostListDropCopies || !connectionSessionUi.terminalTabPreviewActivated || !connectionSessionUi.invalidTerminalDropRestoresSource || !connectionSessionUi.invalidSftpDropRestoresSource || !connectionSessionUi.acceptedTerminalDropStays || !connectionSessionUi.ownDragUploadSuppressed || !connectionSessionUi.armedPointerCancelClearsRequest || !connectionSessionUi.armedDragAllowsExternalUpload || !connectionSessionUi.staleInternalDragAllowsExternalUpload || !connectionSessionUi.desktopUriListDragAccepted || !connectionSessionUi.releasedDragAllowsExternalUpload || !connectionSessionUi.externalFileDropDetected || !connectionSessionUi.externalFileDropCollected || !connectionSessionUi.externalDropPromptIsSingle || !connectionSessionUi.externalDropPromptAvoidsWorkspaceChrome || !connectionSessionUi.externalDropPromptListCentered || !connectionSessionUi.externalDropSurfaceFillsWorkspace || !connectionSessionUi.externalDropPromptScrollClamped || !connectionSessionUi.externalDropPromptHorizontalClamped || !connectionSessionUi.externalDropPromptClears || nativeDragUiFailed || jobUiFailed || textEncodingUiFailed || imagePreviewUiFailed || !downloadNoticeUi.oncePerMode || !downloadNoticeUi.desktopPath || !downloadNoticeUi.browserDevice || !downloadNoticeUi.batchUsesSharedNotice || !downloadNoticeUi.browserSeparateChoice || !downloadNoticeUi.browserSeparateQueued || !downloadNoticeUi.noDuplicateBatchNotice || !globalSettingsUi.found || !globalSettingsUi.globalScope || !globalSettingsUi.controls || !globalSettingsUi.floatingProgressDefaultOn || !globalSettingsUi.floatingProgressCanRestore || !globalSettingsUi.downloadBehavior || !globalSettingsUi.defaultLimit || !globalSettingsUi.backdropIgnored || !globalSettingsUi.withinViewport || !globalSettingsUi.classicSurface || !globalSettingsUi.themedField || !directorySizeUi.idleButton || !directorySizeUi.requestedOnce || !directorySizeUi.exactBytes || !directorySizeUi.formatted || !directorySizeUi.refreshable || !sftpUi.fileOpenFeedback?.busy || !sftpUi.fileOpenFeedback?.duplicateBlocked || !sftpUi.fileOpenFeedback?.restored || !sftpUi.fileOpenFeedback?.interruptedRetry || !directoryCacheBehavior.sameResponseUntouched || !directoryCacheBehavior.changedResponseRendered || !directoryCacheBehavior.permissionFailureRestored || !sftpUi.searchKeyboardUi?.opened || !sftpUi.searchKeyboardUi?.closed || !sftpUi.searchKeyboardUi?.recursive || !sftpUi.searchKeyboardUi?.feedback || !sftpUi.syncIndicatorFollowsScroll || !sftpUi.diffComparisonUi || !sftpUi.columnLayoutUi?.order || !sftpUi.columnLayoutUi?.persisted || !sftpUi.columnLayoutUi?.resized || !sftpUi.columnLayoutUi?.pointerStable || !sftpUi.columnLayoutUi?.pairOnly || !sftpUi.columnLayoutUi?.adjacentResizeStable || !sftpUi.columnLayoutUi?.dividerUniform || !sftpUi.columnLayoutUi?.localNarrowResizable || !sftpUi.columnLayoutUi?.openButtonStable || !sftpUi.columnLayoutUi?.selectionToolbarStable || !sftpUi.columnLayoutUi?.scrollbarUnified || !sftpUi.columnLayoutUi?.globalCss || !directoryActionsUi.found || directoryActionsUi.stickyPosition !== 'sticky' || !directoryActionsUi.toolbarInHeader || !directoryActionsUi.navigationBeforeFavorites || !directoryActionsUi.reusedWithoutDirectoryReload || !expectedSftpToolActions.every(action=>directoryActionsUi.actionTitles?.includes(action)) || !directoryActionsUi.searchHidden || !directoryActionsUi.pathEditorHidden || !directoryActionsUi.emptyClipboardHidden || !directoryActionsUi.copyQueueVisible || !directoryActionsUi.copyCancelled || !directoryActionsUi.moveQueueVisible || !directoryActionsUi.moveCancelled || !directoryActionsUi.crossHostCopyEnabled || !directoryActionsUi.crossHostMoveDisabled || !directoryActionsUi.crossHostClipboardConflict || !directoryActionsUi.filenameEncodingMenu || !directoryActionsUi.emptyFavoritesCompact || !directoryActionsUi.wideNavigationCompact || !directoryActionsUi.narrowNavigationCompact || !directoryActionsUi.terminalJump || !directoryActionsUi.terminalJumpFirst || !sftpUi.folderOpened || !sftpUi.fileOpened || !sftpUi.unknownAction || sftpUi.stickyPosition !== "sticky" || !sftpUi.breadcrumbScrollable || !sftpUi.singlePathPresentation || sftpUi.breadcrumbLabels?.join('/') !== '根目录/Users/demo/Public' || sftpUi.breadcrumbText.includes('//') || !sftpUi.selectionShown || !sftpUi.selectionActionsShown || !sftpUi.multiNameAddsSelection || !sftpUi.multiNameCancelsSelection || !sftpUi.singleNameReplacesSelection || !sftpUi.specialSelectionExact || sftpUi.selectedRows !== 2 || !sftpUi.dragSelectionSynchronized || !sftpUi.selectionCleared || !sftpUi.fileHasCompression || !sftpUi.permissionOwnerColumn || !sftpUi.permissionOwnerTitle || !sftpUi.symlinkUsesTargetSize || !sftpUi.symlinkExplainsBothSizes || !sftpUi.symlinkMarked || !sftpUi.wideColumnAlignment || !sftpUi.wideActionsFit || !sftpUi.compactSizeVisible || !sftpUi.compactTimeVisible || !sftpUi.compactAccessVisible || !sftpUi.compactMediumHidden || !sftpUi.compactCoreVisible || !sftpUi.compactHorizontalScroll || !sftpUi.permissionModeSync || !sftpUi.recursiveVisible || sftpUi.compactRowHeight > 48 || !sftpUi.moreMenuOpened || !sftpUi.contextMenuOpened || !sftpUi.directoryDownloadMenu || !sftpUi.narrowLayoutClass || !sftpUi.narrowCoreHidden || !sftpUi.narrowMoreVisible || !sftpUi.narrowMetaVisible || !sftpUi.narrowAccessHidden || !sftpUi.narrowHeaderNameVisible || !sftpUi.narrowHeaderSummaryVisible || !sftpUi.narrowCompactActions || !sftpUi.completedMutationDetected || !sftpUi.desktopPagerSingleRow || !sftpUi.pagerFloatsAtWorkspaceBottom || !sftpUi.pagerOpaqueAndElevated || !sftpUi.pagerDockSealsBottom || !sftpUi.pagerPinnedToViewport || !sftpUi.scrollCueVisibleAboveContent || !sftpUi.scrollCueHidesAtEnd || !sftpUi.narrowPagerWraps || sftpUi.pageRows !== 50 || !sftpUi.pagerVisible || !sftpUi.pagerJumpVisible || !sftpUi.pagerText.includes('第 1/2 页') || !sftpUi.previousDisabled || !sftpUi.nextEnabled; const sftpToolbarRecoveryFailed = !directoryActionsUi.recoveredMissingToolbar || !directoryActionsUi.duplicateSftpToolbarsFollowActiveTab; From 8a38fbe0e1ad59ad8d39dba89c99c8c93833b40b Mon Sep 17 00:00:00 2001 From: JunXiaoRuo <47996900+JunXiaoRuo@users.noreply.github.com> Date: Thu, 17 Sep 2026 18:59:32 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E8=A1=A5=E5=85=85=20v1.7.3=20=E5=8F=98?= =?UTF-8?q?=E6=9B=B4=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/release-notes/v1.7.3.md | 4 ++-- docs/update.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/release-notes/v1.7.3.md b/.github/release-notes/v1.7.3.md index 74a7120..cbce857 100644 --- a/.github/release-notes/v1.7.3.md +++ b/.github/release-notes/v1.7.3.md @@ -16,7 +16,7 @@ #### Changes in this release -- No pull requests were merged after v1.7.2; these user-visible changes were committed directly by the project maintainer. +- Fix long single-line navigation and release v1.7.3 ([#37](https://github.com/zmide/Terma/pull/37), [@JunXiaoRuo](https://github.com/JunXiaoRuo)) **Full Changelog**: [v1.7.2...v1.7.3](https://github.com/zmide/Terma/compare/v1.7.2...v1.7.3) @@ -36,6 +36,6 @@ #### 本次变更 -- v1.7.2 之后没有合并 Pull Request;本版用户可见变化均由项目维护者直接提交。 +- 修复超长单行定位并发布 v1.7.3([#37](https://github.com/zmide/Terma/pull/37),[@JunXiaoRuo](https://github.com/JunXiaoRuo)) **完整变更**:[v1.7.2...v1.7.3](https://github.com/zmide/Terma/compare/v1.7.2...v1.7.3) diff --git a/docs/update.md b/docs/update.md index 4feb894..53897d3 100644 --- a/docs/update.md +++ b/docs/update.md @@ -46,7 +46,7 @@ #### Changes in this release -- No pull requests were merged after v1.7.2; these user-visible changes were committed directly by the project maintainer. +- Fix long single-line navigation and release v1.7.3 ([#37](https://github.com/zmide/Terma/pull/37), [@JunXiaoRuo](https://github.com/JunXiaoRuo)) **Full Changelog**: [v1.7.2...v1.7.3](https://github.com/zmide/Terma/compare/v1.7.2...v1.7.3) @@ -66,7 +66,7 @@ #### 本次变更 -- v1.7.2 之后没有合并 Pull Request;本版用户可见变化均由项目维护者直接提交。 +- 修复超长单行定位并发布 v1.7.3([#37](https://github.com/zmide/Terma/pull/37),[@JunXiaoRuo](https://github.com/JunXiaoRuo)) **完整变更**:[v1.7.2...v1.7.3](https://github.com/zmide/Terma/compare/v1.7.2...v1.7.3) From afcbdf4bb6388c2f5c72e754aad7aed1ddc9b7f4 Mon Sep 17 00:00:00 2001 From: JunXiaoRuo <47996900+JunXiaoRuo@users.noreply.github.com> Date: Thu, 17 Sep 2026 19:34:26 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E5=99=A8=E5=AE=9A=E4=BD=8D=E5=9B=9E=E5=BD=92=E4=B8=8E=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E8=B7=B3=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/release-notes/v1.7.3.md | 6 +++--- docs/update.md | 6 +++--- public/app-sftp-editor.js | 4 +--- public/app-sftp-svg-source.js | 27 +++++++++++++++++---------- scripts/release-bilingual-check.js | 8 ++++++++ scripts/release-notes.js | 10 +++++----- scripts/ui-smoke-electron.js | 17 +++++++++++++++-- 7 files changed, 52 insertions(+), 26 deletions(-) diff --git a/.github/release-notes/v1.7.3.md b/.github/release-notes/v1.7.3.md index cbce857..4919ce6 100644 --- a/.github/release-notes/v1.7.3.md +++ b/.github/release-notes/v1.7.3.md @@ -1,6 +1,6 @@ -[English](#english) · [简体中文](#简体中文) +[English](#v1-7-3-english) · [简体中文](#v1-7-3-zh) - + ### English > This maintenance release makes internal editor navigation reliable for very long single-line files and makes selections easier to see, including SVG source locating. @@ -20,7 +20,7 @@ **Full Changelog**: [v1.7.2...v1.7.3](https://github.com/zmide/Terma/compare/v1.7.2...v1.7.3) - + ### 简体中文 > 本维护版本改进超长单行文件的编辑器定位,并让选中内容更容易看清,包括 SVG 源码定位。 diff --git a/docs/update.md b/docs/update.md index 53897d3..5ef2816 100644 --- a/docs/update.md +++ b/docs/update.md @@ -28,9 +28,9 @@ ## v1.7.3 -[English](#english) · [简体中文](#简体中文) +[English](#v1-7-3-english) · [简体中文](#v1-7-3-zh) - + ### English > This maintenance release makes internal editor navigation reliable for very long single-line files and makes selections easier to see, including SVG source locating. @@ -50,7 +50,7 @@ **Full Changelog**: [v1.7.2...v1.7.3](https://github.com/zmide/Terma/compare/v1.7.2...v1.7.3) - + ### 简体中文 > 本维护版本改进超长单行文件的编辑器定位,并让选中内容更容易看清,包括 SVG 源码定位。 diff --git a/public/app-sftp-editor.js b/public/app-sftp-editor.js index 10a371c..12bf951 100644 --- a/public/app-sftp-editor.js +++ b/public/app-sftp-editor.js @@ -1022,9 +1022,7 @@ function sftpTextModal(title, content, size=0, limit=5*1024*1024, encoding="utf8 if (!keepSearchFocus) aceEditor.focus(); } else { fallbackEditor.setSelectionRange(start, end); - const source = editorSearchSource(), lineStart = source.lastIndexOf("\n", Math.max(0, start - 1)) + 1, column = Math.max(0, start - lineStart), line = source.slice(0, start).split("\n").length - 1; - const fontSize = Number.parseFloat(getComputedStyle(fallbackEditor).fontSize || "14") || 14, lineHeight = Number.parseFloat(getComputedStyle(fallbackEditor).lineHeight || "") || fontSize * 1.45, approximateCharWidth = Math.max(6, fontSize * .62); - fallbackEditor.scrollTop = Math.max(0, line * lineHeight - fallbackEditor.clientHeight * .4); fallbackEditor.scrollLeft = Math.max(0, Math.min(Math.max(0, fallbackEditor.scrollWidth - fallbackEditor.clientWidth), column * approximateCharWidth - fallbackEditor.clientWidth * .42)); + scrollSftpFallbackEditorToOffset(fallbackEditor, editorSearchSource(), start); if (!keepSearchFocus) fallbackEditor.focus(); } }; diff --git a/public/app-sftp-svg-source.js b/public/app-sftp-svg-source.js index d8254c3..c980b8a 100644 --- a/public/app-sftp-svg-source.js +++ b/public/app-sftp-svg-source.js @@ -1,3 +1,19 @@ +function scrollSftpFallbackEditorToOffset(editor, text, offset) { + if (!editor) return false; + const source = String(text || ""); + const index = Math.max(0, Math.min(source.length, Number(offset) || 0)); + const lineStart = source.lastIndexOf("\n", Math.max(0, index - 1)) + 1; + const column = Math.max(0, index - lineStart); + const line = source.slice(0, index).split("\n").length - 1; + const style = globalThis.getComputedStyle?.(editor); + const fontSize = Number.parseFloat(style?.fontSize || "14") || 14; + const lineHeight = Number.parseFloat(style?.lineHeight || "") || fontSize * 1.45; + const approximateCharWidth = Math.max(6, fontSize * .62); + editor.scrollTop = Math.max(0, line * lineHeight - editor.clientHeight * .4); + editor.scrollLeft = Math.max(0, Math.min(Math.max(0, editor.scrollWidth - editor.clientWidth), column * approximateCharWidth - editor.clientWidth * .42)); + return true; +} + function createSftpSvgSourceLocator({getText, getAceEditor, getFallbackEditor, onHighlight}) { let aceTargetMarkerId = null; let aceLineMarkerId = null; @@ -105,16 +121,7 @@ function createSftpSvgSourceLocator({getText, getAceEditor, getFallbackEditor, o } else if (fallbackEditor) { fallbackEditor.focus(); fallbackEditor.setSelectionRange(index, index + rangeLength); - const lineStart = text.lastIndexOf("\n", Math.max(0, index - 1)) + 1; - const column = Math.max(0, index - lineStart); - const line = text.slice(0, index).split("\n").length - 1; - const fontSize = Number.parseFloat(globalThis.getComputedStyle?.(fallbackEditor)?.fontSize || "14") || 14; - const approximateCharWidth = Math.max(6, fontSize * .62); - fallbackEditor.scrollTop = Math.max(0, line * fontSize * 1.45 - fallbackEditor.clientHeight * .4); - fallbackEditor.scrollLeft = Math.max(0, Math.min( - Math.max(0, fallbackEditor.scrollWidth - fallbackEditor.clientWidth), - column * approximateCharWidth - fallbackEditor.clientWidth * .42 - )); + scrollSftpFallbackEditorToOffset(fallbackEditor, text, index); fallbackEditor.classList.add("sftp-source-locate-active"); fallbackHighlightActive = true; } diff --git a/scripts/release-bilingual-check.js b/scripts/release-bilingual-check.js index efa1a76..3499a7c 100644 --- a/scripts/release-bilingual-check.js +++ b/scripts/release-bilingual-check.js @@ -41,6 +41,14 @@ if (fs.existsSync(updatePath)) { assertSynchronizedSections(validateBilingualReleaseBody(draftMatch[1], "下一版草稿"), "下一版草稿"); } validateBilingualReleaseBody(bilingualPlaceholder("v9.9.9"), "双语空说明模板"); +validateBilingualReleaseBody( + bilingualPlaceholder("v9.9.9") + .replaceAll("#english", "#v9-9-9-english") + .replaceAll('id="english"', 'id="v9-9-9-english"') + .replaceAll("#简体中文", "#v9-9-9-zh") + .replaceAll('id="简体中文"', 'id="v9-9-9-zh"'), + "版本化快速跳转" +); const releaseNotesDirectory = path.join(root, ".github", "release-notes"); for (const name of fs.readdirSync(releaseNotesDirectory)) { diff --git a/scripts/release-notes.js b/scripts/release-notes.js index 4c08422..a517178 100644 --- a/scripts/release-notes.js +++ b/scripts/release-notes.js @@ -19,13 +19,13 @@ function requiresBilingualRelease(version) { function validateBilingualReleaseBody(body, label="Release Notes") { const normalized = String(body || "").replace(/\r\n?/g, "\n"); - const navigation = normalized.indexOf("[English](#english)"); - const chineseNavigation = normalized.indexOf("[简体中文](#简体中文)"); - const englishAnchor = normalized.indexOf(''); + const navigationMatch = normalized.match(/\[English\]\(#([^)]+)\)\s*·\s*\[简体中文\]\(#([^)]+)\)/); + const navigation = navigationMatch?.index ?? -1; + const englishAnchor = navigationMatch ? normalized.indexOf(``, navigation) : -1; const englishHeading = normalized.indexOf("### English", englishAnchor); - const chineseAnchor = normalized.indexOf(''); + const chineseAnchor = navigationMatch ? normalized.indexOf(``, englishHeading) : -1; const chineseHeading = normalized.indexOf("### 简体中文", chineseAnchor); - if (!(navigation >= 0 && chineseNavigation > navigation && englishAnchor > chineseNavigation && englishHeading > englishAnchor && chineseAnchor > englishHeading && chineseHeading > chineseAnchor)) { + if (!(navigation >= 0 && englishAnchor > navigation && englishHeading > englishAnchor && chineseAnchor > englishHeading && chineseHeading > chineseAnchor)) { throw new Error(`${label} 必须提供快速跳转,并按 English、简体中文顺序排列`); } const english = normalized.slice(englishHeading + "### English".length, chineseAnchor).trim(); diff --git a/scripts/ui-smoke-electron.js b/scripts/ui-smoke-electron.js index 0941ef5..ce41d00 100644 --- a/scripts/ui-smoke-electron.js +++ b/scripts/ui-smoke-electron.js @@ -9511,7 +9511,18 @@ app.whenReady().then(async () => { const svgEditorClickLine=Number.isFinite(svgEditorClickRow) ? svgAceEditor.session.getLine(svgEditorClickRow) : ''; const svgEditorClickSelection=svgAceEditor?.getSelectionRange?.(); const svgEditorClickSelectedText=svgAceEditor?.getSelectedText?.()||''; - const svgEditorClickScrollLeft=Number(svgAceEditor?.renderer?.getScrollLeft?.() ?? svgAceEditor?.renderer?.scroller?.scrollLeft ?? 0); + const svgEditorClickScrollValue=svgAceEditor?.renderer?.getScrollLeft?.() ?? svgAceEditor?.renderer?.scroller?.scrollLeft; + const svgEditorClickScrollLeft=Number(svgEditorClickScrollValue); + const svgEditorClickScrollerRect=svgAceEditor?.renderer?.scroller?.getBoundingClientRect?.(); + const svgEditorClickScreenPosition=svgEditorClickSelection?.start + ? svgAceEditor?.renderer?.textToScreenCoordinates?.(svgEditorClickSelection.start.row,svgEditorClickSelection.start.column) + : null; + const svgEditorClickViewportX=Number(svgEditorClickScreenPosition?.pageX)-Number(window.scrollX||0); + const svgEditorClickHorizontallyVisible=Boolean(svgEditorClickScrollerRect + && Number.isFinite(svgEditorClickScrollLeft) + && Number.isFinite(svgEditorClickViewportX) + && svgEditorClickViewportX>=svgEditorClickScrollerRect.left + && svgEditorClickViewportX<=svgEditorClickScrollerRect.right); const svgEditorClickMarkers=svgAceEditor?.session?.getMarkers?.(true)||{}; const svgEditorClickHasMarker=Object.values(svgEditorClickMarkers).some(marker=>String(marker?.clazz||marker?.className||'').includes('sftp-svg-source-target')); const svgClickTargetAfter=svgEditorRoot?.querySelector('#PD_11100000_14419'); @@ -9577,7 +9588,7 @@ app.whenReady().then(async () => { && svgEditorClickSelection.start?.row===svgEditorClickSelection.end?.row && svgEditorClickSelectedText.includes('PD_11100000_14419') && svgEditorClickHasMarker - && svgEditorClickScrollLeft>=0); + && svgEditorClickHorizontallyVisible); imagePreviewUi.svgSplitSourceFocusDiagnostics={ selectedText:svgEditorClickSelectedText.slice(0,180), range:svgEditorClickSelection ? { @@ -9585,6 +9596,8 @@ app.whenReady().then(async () => { end:svgEditorClickSelection.end } : null, scrollLeft:svgEditorClickScrollLeft, + viewportX:svgEditorClickViewportX, + horizontallyVisible:svgEditorClickHorizontallyVisible, hasMarker:svgEditorClickHasMarker }; const svgStyledUse=svgEditorRoot?.querySelector('#styled-use');