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');