diff --git a/build-plugin.sh b/build-plugin.sh
index b7d6773..2a6d061 100755
--- a/build-plugin.sh
+++ b/build-plugin.sh
@@ -5,7 +5,7 @@ set -eu
export COPYFILE_DISABLE=1
PLUGIN="u-navigator"
-VERSION="2026.09.12.r001"
+VERSION="2026.09.12.r002"
PKG_DIR="packages"
WORK_DIR=".plugin-build"
PKG_NAME="${PLUGIN}-${VERSION}.tgz"
diff --git a/packages/u-navigator-2026.09.12.r002.tgz b/packages/u-navigator-2026.09.12.r002.tgz
new file mode 100644
index 0000000..daeb30d
Binary files /dev/null and b/packages/u-navigator-2026.09.12.r002.tgz differ
diff --git a/server/public/app.js b/server/public/app.js
index 62e895c..a2e49f1 100644
--- a/server/public/app.js
+++ b/server/public/app.js
@@ -32,6 +32,7 @@ const state = {
settingsPanelVisible: false,
typeAhead: {
key: '',
+ sequence: '',
windowId: null,
at: 0
},
@@ -2360,15 +2361,27 @@ function handleTypeAheadKey(event) {
if (!win || !win.data.entries?.length) return;
const now = Date.now();
- const repeated = state.typeAhead.windowId === win.id && state.typeAhead.key === key && now - state.typeAhead.at < 1000;
+ const sameWindow = state.typeAhead.windowId === win.id;
+ const withinWindow = now - state.typeAhead.at < 1000;
+ const sequence = sameWindow && withinWindow ? (state.typeAhead.sequence || '') : '';
+ // Gleicher Buchstabe kurz nach einander = nächstes Vorkommen dieses Buchstabens
+ // (z. B. "AA" springt zum zweiten A-Eintrag), statt die Folge zu verdoppeln.
+ const repeated = sameWindow && withinWindow && state.typeAhead.key === key && sequence.length <= 1;
+ let next = repeated ? key : sequence + key;
+ if (!repeated && next.length > 16) next = key;
+
const entries = win.data.entries;
const selectedIndex = entries.findIndex((entry) => entry.path === win.data.selectedPath);
- const startIndex = repeated ? selectedIndex + 1 : Math.max(0, selectedIndex + 1);
- const match = findTypeAheadMatch(entries, key, startIndex);
- if (!match) return;
+ const startIndex = Math.max(0, selectedIndex + 1);
+ const match = findTypeAheadMatch(entries, next, startIndex, repeated);
+ if (!match) {
+ // Unvollständiger Pfad ohne Treffer: Auswahl bleibt, Folge wird verworfen
+ if (sequence) state.typeAhead = { key, sequence: '', windowId: win.id, at: now };
+ return;
+ }
event.preventDefault();
- state.typeAhead = { key, windowId: win.id, at: now };
+ state.typeAhead = { key, sequence: next, windowId: win.id, at: now };
win.data.selectedPath = match.path;
win.data.selectedPaths = [match.path];
win.data.selectionAnchorPath = match.path;
@@ -2377,15 +2390,23 @@ function handleTypeAheadKey(event) {
updateSelectedRows();
updatePropertiesWindows();
scrollEntryIntoView(win, match.path);
- recordDebug('typeahead.match', { key, path: match.path });
+ recordDebug('typeahead.match', { sequence: next, path: match.path });
}
-function findTypeAheadMatch(entries, key, startIndex) {
- for (let offset = 0; offset < entries.length; offset += 1) {
- const index = (startIndex + offset) % entries.length;
- const entry = entries[index];
- if (entry.name?.toLocaleLowerCase().startsWith(key)) {
- return entry;
+function findTypeAheadMatch(entries, sequence, startIndex, preferNextOccurrence = false) {
+ // Erst: Präfix-Treffer ab der aktuellen Position (mit Wraparound).
+ // Zweit: Teiltreffer (z. B. "amy" findet "Amy Spring"), damit Tippfolgen auch bei
+ // Präfix-Konkurrenz zum richtigen Eintrag springen.
+ for (let pass = 0; pass < 2; pass += 1) {
+ for (let offset = 0; offset < entries.length; offset += 1) {
+ const index = (startIndex + offset) % entries.length;
+ const entry = entries[index];
+ const name = entry.name?.toLocaleLowerCase();
+ if (!name) continue;
+ const isMatch = pass === 0
+ ? name.startsWith(sequence)
+ : name.includes(sequence) && !(preferNextOccurrence && sequence.length <= 1);
+ if (isMatch) return entry;
}
}
return null;
diff --git a/u-navigator.plg b/u-navigator.plg
index dbf2c2d..6b79a19 100644
--- a/u-navigator.plg
+++ b/u-navigator.plg
@@ -1,7 +1,7 @@
-
+
@@ -24,7 +24,7 @@
https://git.casaderoll.de/michael/Unraid-Navigator/raw/branch/main/packages/&package;
-9cee8533bb7f227236fb9ff4963e2d73
+97ee8d18a7eee6b3b1da1fa7451d5553