Repository: syncthing/syncthing (language: go)

Issue:
gui: Links to documentation should point to matching version
Original discussion on the forum: https://forum.syncthing.net/t/version-specific-documentation-proposal/17858

The documentation links in some places of the Web GUI currently point at the single URL `https://docs.syncthing.net/...`.  That is a moving target however, documenting stuff that might not yet be found in the currently running version or missing features that have been removed / reworked in a subsequent release and already was removed documentation.  The envisioned solution consists of these base components:

1. [x] Create an archive of the built HTML result for every version tagged on [the docs source repository](https://github.com/syncthing/docs/).
2. [x] Serve them (statically) under some version-specific URL, e.g. `https://docs.syncthing.net/v1.18.6/...` or `https://docs.syncthing.net/...?version=v1.18.6`.
3. [x] Alter the links contained in the Web GUI to include (part of) the version string for the running instance.
4. [ ] Add fall-back logic on the server side to redirect any unavailable version URL to the generic, up-to-date "main" version as it is now.
5. [x] Keep a JSON file on the server listing all available deployed documentation version builds.
6. [ ] Embed the version tag in built HTML docs to easily see what one is looking at.

An optional future refinement would be to add a version selector on the docs site to quickly switch between versions, e.g. when comparing API evolution.  But that is out of scope for this request.


Discussion / hints:

Fix patch:
diff --git a/gui/default/index.html b/gui/default/index.html
index 4c1a2fb6d34..85868c8bab0 100644
--- a/gui/default/index.html
+++ b/gui/default/index.html
@@ -89,7 +89,7 @@ <h3 class="panel-title">
           </li>
           <li class="dropdown" language-select></li>
           <li>
-            <a class="navbar-link" href="https://docs.syncthing.net/intro/gui.html" target="_blank">
+            <a class="navbar-link" href="{{docsURL('intro/gui')}}" target="_blank">
               <span class="fas fa-question-circle"></span>
               <span class="hidden-xs" translate>Help</span>
             </a>
@@ -108,7 +108,7 @@ <h3 class="panel-title">
               <li><a href="" ng-click="restart()"><span class="fas fa-fw fa-refresh"></span>&nbsp;<span translate>Restart</span></a></li>
               <li class="divider" aria-hidden="true"></li>
               <li class="visible-xs">
-                <a href="https://docs.syncthing.net/intro/gui.html" target="_blank">
+                <a href="{{docsURL('intro/gui')}}" target="_blank">
                   <span class="fas fa-fw fa-question-circle"></span>&nbsp;<span translate>Help</span>
                 </a>
               </li>
@@ -432,7 +432,7 @@ <h4 class="panel-title">
                             <i class="small">
                               <span translate style="white-space: normal;">Altered by ignoring deletes.</span>
                               <br>
-                              <a href="https://docs.syncthing.net/advanced/folder-ignoredelete" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
+                              <a href="{{docsURL('advanced/folder-ignoredelete')}}" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
                             </i>
                           </div>
                         </td>
diff --git a/gui/default/syncthing/core/notifications.html b/gui/default/syncthing/core/notifications.html
index 0af0bef7de9..b7b7d4de6cf 100644
--- a/gui/default/syncthing/core/notifications.html
+++ b/gui/default/syncthing/core/notifications.html
@@ -28,7 +28,7 @@ <h3 class="panel-title"><span class="fas fa-bolt"></span>&nbsp;<span translate>A
         <span translate>You can read more about the two release channels at the link below.</span>
       </p>
       <p translate>You can change your choice at any time in the Settings dialog.</p>
-      <p><a href="https://docs.syncthing.net/users/releases.html"><span class="fas fa-info-circle"></span>&nbsp;<span translate>Learn more</span></a></p>
+      <p><a href="{{docsURL('users/releases')}}"><span class="fas fa-info-circle"></span>&nbsp;<span translate>Learn more</span></a></p>
     </div>
     <div class="panel-footer">
       <button type="button" class="btn btn-sm btn-default pull-right" ng-click="showSettings(); dismissNotification('channelNotification')">
@@ -49,7 +49,7 @@ <h3 class="panel-title"><span class="fas fa-bolt"></span>&ensp;<span translate>W
     </div>
     <div class="panel-body">
       <p translate>Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.</p>
-      <p><a href="https://docs.syncthing.net/users/syncing.html#scanning"><span class="fas fa-info-circle"></span>&nbsp;<span translate>Learn more</span></a></p>
+      <p><a href="{{docsURL('users/syncing#scanning')}}"><span class="fas fa-info-circle"></span>&nbsp;<span translate>Learn more</span></a></p>
       <p>
         <span translate>Do you want to enable watching for changes for all your folders?</span><br />
         <span translate>Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.</span>
@@ -77,7 +77,7 @@ <h3 class="panel-title"><span class="fas fa-bolt"></span>&ensp;<span translate>A
     </div>
     <div class="panel-body">
       <p translate>Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.</p>
-      <p><a href="https://docs.syncthing.net/users/crashrep.html"><span class="fas fa-info-circle"></span>&nbsp;<span translate>Learn more</span></a></p>
+      <p><a href="{{docsURL('users/crashrep')}}"><span class="fas fa-info-circle"></span>&nbsp;<span translate>Learn more</span></a></p>
     </div>
     <div class="panel-footer clearfix">
       <div class="pull-right">
@@ -101,7 +101,7 @@ <h3 class="panel-title"><span class="fas fa-bolt"></span>&ensp;<span translate>A
     <div class="panel-body">
       <p translate>Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.</p>
       <p translate>However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.</p>
-      <p><a href="https://docs.syncthing.net/users/crashrep.html"><span class="fas fa-info-circle"></span>&nbsp;<span translate>Learn more</span></a></p>
+      <p><a href="{{docsURL('users/crashrep')}}"><span class="fas fa-info-circle"></span>&nbsp;<span translate>Learn more</span></a></p>
     </div>
     <div class="panel-footer clearfix">
       <div class="pull-right">
@@ -140,4 +140,4 @@ <h3 class="panel-title"><span class="fas fa-bolt"></span>&nbsp;<span translate>G
       <div class="clearfix"></div>
     </div>
   </div>
-</notification>
\ No newline at end of file
+</notification>
diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js
index 51fd73b9e52..cfd12bdefb8 100755
--- a/gui/default/syncthing/core/syncthingController.js
+++ b/gui/default/syncthing/core/syncthingController.js
@@ -2887,6 +2887,34 @@ angular.module('syncthing.core')
             return $scope.version.version + ', ' + os + ' (' + arch + ')';
         };
 
+        $scope.versionBase = function () {
+            if (!$scope.version.version) {
+                return '';
+            }
+            var version = $scope.version.version;
+            var pos = version.indexOf('-');
+            if (pos > 0) {
+                version = version.slice(0, pos);
+            }
+            return version;
+        };
+
+        $scope.docsURL = function (path) {
+            var url = 'https://docs.syncthing.net';
+            if (path) {
+                var hash = path.indexOf('#');
+                if (hash != -1) {
+                    url += '/' + path.slice(0, hash);
+                    url += '?version=' + $scope.versionBase();
+                    url += path.slice(hash);
+                } else {
+                    url += '/' + path;
+                    url += '?version=' + $scope.versionBase();
+                }
+            }
+            return url;
+        };
+
         $scope.inputTypeFor = function (key, value) {
             if (key.substr(0, 1) === '_') {
                 return 'skip';
diff --git a/gui/default/syncthing/folder/editFolderModalView.html b/gui/default/syncthing/folder/editFolderModalView.html
index 2c0c3559750..cce101696d2 100644
--- a/gui/default/syncthing/folder/editFolderModalView.html
+++ b/gui/default/syncthing/folder/editFolderModalView.html
@@ -77,7 +77,7 @@
 
         <div id="folder-versioning" class="tab-pane">
           <div class="form-group">
-            <label translate>File Versioning</label>&emsp;<a href="https://docs.syncthing.net/users/versioning.html" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
+            <label translate>File Versioning</label>&emsp;<a href="{{docsURL('users/versioning')}}" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
             <select class="form-control" ng-model="currentFolder._guiVersioning.selector">
               <option value="none" translate>No File Versioning</option>
               <option value="trashcan" translate>Trash Can File Versioning</option>
@@ -164,7 +164,7 @@
               </p>
             </div>
             <hr />
-            <p class="small"><span translate>Quick guide to supported patterns</span> (<a href="https://docs.syncthing.net/users/ignoring.html" target="_blank" translate>full documentation</a>):</p>
+            <p class="small"><span translate>Quick guide to supported patterns</span> (<a href="{{docsURL('users/ignoring')}}" target="_blank" translate>full documentation</a>):</p>
             <dl class="dl-horizontal dl-narrow small">
               <dt><code>(?d)</code></dt>
               <dd><b><span translate>Prefix indicating that the file can be deleted if preventing directory removal</span></b></dd>
@@ -190,7 +190,7 @@
           <div class="row form-group" ng-class="{'has-error': folderEditor.rescanIntervalS.$invalid && folderEditor.rescanIntervalS.$dirty}">
             <div class="col-md-12">
               <label translate>Scanning</label>
-              &nbsp;<a href="https://docs.syncthing.net/users/syncing.html#scanning" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a></br>
+              &nbsp;<a href="{{docsURL('users/syncing#scanning')}}" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a></br>
 
               <div class="row">
                 <div class="col-md-6">
@@ -213,7 +213,7 @@
           <div class="row">
             <div class="col-md-6 form-group">
               <label translate>Folder Type</label>
-              &nbsp;<a href="https://docs.syncthing.net/users/foldertypes.html" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
+              &nbsp;<a href="{{docsURL('users/foldertypes')}}" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
               <select class="form-control" ng-change="setDefaultsForFolderType()" ng-model="currentFolder.type" ng-disabled="editingFolderExisting() && currentFolder.type == 'receiveencrypted'">
                 <option value="sendreceive" translate>Send &amp; Receive</option>
                 <option value="sendonly" translate>Send Only</option>
diff --git a/gui/default/syncthing/settings/settingsModalView.html b/gui/default/syncthing/settings/settingsModalView.html
index d83cbc94a8a..60da9b9ac71 100644
--- a/gui/default/syncthing/settings/settingsModalView.html
+++ b/gui/default/syncthing/settings/settingsModalView.html
@@ -85,7 +85,7 @@
             </div>
             <div class="col-md-6">
               <div class="form-group">
-                <label translate>Automatic upgrades</label>&emsp;<a href="https://docs.syncthing.net/users/releases.html" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
+                <label translate>Automatic upgrades</label>&emsp;<a href="{{docsURL('users/releases')}}" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
                 <select class="form-control" ng-model="tmpOptions.upgrades" ng-if="upgradeInfo">
                   <option ng-if="!version.isCandidate" value="none" translate>No upgrades</option>
                   <option value="stable" translate>Stable releases only</option>
@@ -115,7 +115,7 @@
 
         <div id="settings-gui" class="tab-pane">
           <div class="form-group" ng-class="{'has-error': settingsEditor.Address.$invalid && settingsEditor.Address.$dirty}">
-            <label translate for="Address">GUI Listen Address</label>&emsp;<a href="https://docs.syncthing.net/users/guilisten.html" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
+            <label translate for="Address">GUI Listen Address</label>&emsp;<a href="{{docsURL('users/guilisten')}}" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
             <p class="text-warning" ng-show="system.guiAddressOverridden">
               <span class="fas fa-exclamation-triangle"></span>
               <span translate>The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.</span>
@@ -187,7 +187,7 @@
 
         <div id="settings-connections" class="tab-pane">
           <div class="form-group">
-            <label translate for="ListenAddressesStr">Sync Protocol Listen Addresses</label>&emsp;<a href="https://docs.syncthing.net/users/config.html#listen-addresses" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
+            <label translate for="ListenAddressesStr">Sync Protocol Listen Addresses</label>&emsp;<a href="{{docsURL('users/config#listen-addresses')}}" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
             <input id="ListenAddressesStr" class="form-control" type="text" ng-model="tmpOptions._listenAddressesStr" />
           </div>
           <div class="row">


Test patch: