Преглед изворни кода

fix(gui): update `uncamel()` to handle strings like 'IDs' (fixes #10128) (#10131)

> ⚠️ resubmission targeting `main` instead of `v2`

### Purpose

Updates `uncamel()` function in
[uncamelFilter.js](https://github.com/syncthing/syncthing/blob/v2/gui/default/syncthing/core/uncamelFilter.js)
to fix camelCase conversion edge cases, see #10128

This adds an array called `reservedStrings` which will be printed as-is,
e.g. `IDs`, `LAN` etc. I pre-populated this with what I believe makes
sense, but of course this is easily updated.

### Testing

I compiled all the config variables I could find in
`syncthing/lib/config/*configuration.go` and tested this new function
against them. Everything seemed to pass.

### Screenshot


![Image](https://github.com/user-attachments/assets/af8c9821-58b3-4a6a-8462-bead8a6d845a)
Luke Hamburg пре 9 месеци
родитељ
комит
98555a9a80
1 измењених фајлова са 32 додато и 20 уклоњено
  1. 32 20
      gui/default/syncthing/core/uncamelFilter.js

+ 32 - 20
gui/default/syncthing/core/uncamelFilter.js

@@ -1,27 +1,39 @@
 angular.module('syncthing.core')
     .filter('uncamel', function () {
+        const reservedStrings = [
+            'IDs', 'ID', // substrings must come AFTER longer keywords containing them
+            'URL', 'UR',
+            'API', 'QUIC', 'TCP', 'UDP', 'NAT', 'LAN', 'WAN',
+            'KiB', 'MiB', 'GiB', 'TiB'
+        ];
         return function (input) {
-            input = input.replace(/(.)([A-Z][a-z]+)/g, '$1 $2').replace(/([a-z0-9])([A-Z])/g, '$1 $2');
-            var parts = input.split(' ');
-            var lastPart = parts.splice(-1)[0];
+            if (!input || typeof input !== 'string') return '';
+            const placeholders = {};
+            let counter = 0;
+            reservedStrings.forEach(word => {
+                const placeholder = `__RSV${counter}__`;
+                const re = new RegExp(word, 'g');
+                input = input.replace(re, placeholder);
+                placeholders[placeholder] = word;
+                counter++;
+            });
+            input = input.replace(/([a-z0-9])([A-Z])/g, '$1 $2');
+            Object.entries(placeholders).forEach(([ph, word]) => {
+                input = input.replace(new RegExp(ph, 'g'), ` ${word} `);
+            });
+            let parts = input.split(' ');
+            const lastPart = parts.pop();
             switch (lastPart) {
-                case "S":
-                    parts.push('(seconds)');
-                    break;
-                case "M":
-                    parts.push('(minutes)');
-                    break;
-                case "H":
-                    parts.push('(hours)');
-                    break;
-                case "Ms":
-                    parts.push('(milliseconds)');
-                    break;
-                default:
-                    parts.push(lastPart);
-                    break;
+                case 'S': parts.push('(seconds)'); break;
+                case 'M': parts.push('(minutes)'); break;
+                case 'H': parts.push('(hours)'); break;
+                case 'Ms': parts.push('(milliseconds)'); break;
+                default: parts.push(lastPart); break;
             }
-            input = parts.join(' ');
-            return input.charAt(0).toUpperCase() + input.slice(1);
+            parts = parts.map(part => {
+                const match = reservedStrings.find(w => w.toUpperCase() === part.toUpperCase());
+                return match || part.charAt(0).toUpperCase() + part.slice(1);
+            });
+            return parts.join(' ').replace(/\s+/g, ' ').trim();
         };
     });