diff --git a/core/05-functions.php b/core/05-functions.php
index 07f9ecd..94a1049 100644
--- a/core/05-functions.php
+++ b/core/05-functions.php
@@ -130,6 +130,31 @@ function glob_recursive($pattern, $flags = 0)
 	return $files;
 }
 
+/**
+ * Normalize file name & path.
+ * Used to convert filenames returned by glob_recursive() to a format used in pageindex.
+ *
+ * @package core
+ * @author	Alx84
+ * @param	string	$filename	A filename with storage prefix as returned by glob_recursive()
+ * @return	string	Normalized filename
+ */
+function normalize_filename($filename)
+{
+	global $env;
+	// glob_recursive() returns values like "./storage_prefix/folder/filename.md"
+	// in the pageindex we save them as "folder/filename.md"
+	$result = mb_substr( // Store the filename, whilst trimming the storage prefix
+		$filename,
+		mb_strlen(preg_replace("/^\.\//iu", "", $env->storage_prefix)) // glob_recursive trim the ./ from returned filenames , so we need to as well
+	);
+	// Remove the `./` from the beginning if it's still hanging around
+	if(mb_substr($result, 0, 2) == "./")
+		$result = mb_substr($result, 2);
+
+	return $result;
+}
+
 /**
  * Resolves a relative path against a given base directory.
  * @since 0.20.0
diff --git a/core/20-pageindex-loader.php b/core/20-pageindex-loader.php
index f3a0e7b..b94b47e 100644
--- a/core/20-pageindex-loader.php
+++ b/core/20-pageindex-loader.php
@@ -3,68 +3,73 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
+/**
+* Rebuilds the page index based on what files are found
+* @param	bool	$output	Whether to send progress information to the user's browser.
+*/
+function pageindex_rebuild(bool $output = true) : void {
+
+	global $env, $pageindex;
+
+	if($output && !is_cli()) {
+		header("content-type: text/event-stream");
+		ob_end_flush();
+	}
 
-/*
- * Sort out the pageindex. Create it if it doesn't exist, and load + parse it
- * if it does.
- */
-if(!file_exists($paths->pageindex))
-{
 	$glob_str = $env->storage_prefix . "*.md";
 	$existingpages = glob_recursive($glob_str);
 	$existingpages_count = count($existingpages);
+
 	// Debug statements. Uncomment when debugging the pageindex regenerator.
 	// var_dump($env->storage_prefix);
 	// var_dump($glob_str);
 	// var_dump($existingpages);
+
+	// save our existing pageindex, if it is available at this point
+	// we will use it to salvage some data out of it, like tags and authors
+	if (is_a($pageindex, 'stdClass')) $old_pageindex = $pageindex;
+	else $old_pageindex = new stdClass();
+
+
+	// compose a new pageindex into a global variable
 	$pageindex = new stdClass();
 	// We use a for loop here because foreach doesn't loop over new values inserted
 	// while we were looping
 	for($i = 0; $i < $existingpages_count; $i++)
 	{
 		$pagefilename = $existingpages[$i];
-		
-		// Create a new entry
+
+		// Create a new entry for each md file we found
 		$newentry = new stdClass();
-		$newentry->filename = mb_substr( // Store the filename, whilst trimming the storage prefix
-			$pagefilename,
-			mb_strlen(preg_replace("/^\.\//iu", "", $env->storage_prefix)) // glob_recursive trim the ./ from returned filenames , so we need to as well
-		);
-		// Remove the `./` from the beginning if it's still hanging around
-		if(mb_substr($newentry->filename, 0, 2) == "./")
-			$newentry->filename = mb_substr($newentry->filename, 2);
+
+		// glob_recursive() returns values like "./storage_prefix/folder/filename.md"
+		// in the pageindex we save them as "folder/filename.md"
+		$newentry->filename = normalize_filename($pagefilename);
+
 		$newentry->size = filesize($pagefilename); // Store the page size
 		$newentry->lastmodified = filemtime($pagefilename); // Store the date last modified
-		// Todo find a way to keep the last editor independent of the page index
-		$newentry->lasteditor = "unknown"; // Set the editor to "unknown"
-		
+
 		// Extract the name of the (sub)page without the ".md"
 		$pagekey = filepath_to_pagename($newentry->filename);
 		error_log("pagename '$newentry->filename' → filepath '$pagekey'");
-		
+
 		if(file_exists($env->storage_prefix . $pagekey) && // If it exists...
 			!is_dir($env->storage_prefix . $pagekey)) // ...and isn't a directory
 		{
 			// This page (potentially) has an associated file!
 			// Let's investigate.
-			
+
 			// Blindly add the file to the pageindex for now.
 			// Future We might want to do a security check on the file later on.
 			// File a bug if you think we should do this.
 			$newentry->uploadedfile = true; // Yes this page does have an uploaded file associated with it
 			$newentry->uploadedfilepath = $pagekey; // It's stored here
-			
+
 			// Work out what kind of file it really is
 			$mimechecker = finfo_open(FILEINFO_MIME_TYPE);
 			$newentry->uploadedfilemime = finfo_file($mimechecker, $env->storage_prefix . $pagekey);
 		}
-		
-		// Debug statements. Uncomment when debugging the pageindex regenerator.
-		// echo("pagekey: ");
-		// var_dump($pagekey);
-		// echo("newentry: ");
-		// var_dump($newentry);
-		
+
 		// Subpage parent checker
 		if(strpos($pagekey, "/") !== false)
 		{
@@ -83,7 +88,15 @@ if(!file_exists($paths->pageindex))
 				$existingpages[] = $subpage_parent_filename;
 			}
 		}
-		
+
+        // Attempt to salvage tags and lasteditor from the previous pageindex
+        if (@$old_pageindex->$pagekey->tags)
+		  $newentry->tags = $old_pageindex->$pagekey->tags;
+		$newentry->lasteditor = "unknown";
+		if (@$old_pageindex->$pagekey->lasteditor)
+		  $newentry->lasteditor = $old_pageindex->$pagekey->lasteditor;
+
+
 		// If the initial revision doesn't exist on disk, create it (if it does, then we handle that later)
 		if(function_exists("history_add_revision") && !file_exists("{$pagefilename}.r0")) { // Can't use module_exists - too early
 			copy($pagefilename, "{$pagefilename}.r0");
@@ -91,20 +104,31 @@ if(!file_exists($paths->pageindex))
 				"type" => "edit",
 				"rid" => 0,
 				"timestamp" => $newentry->lastmodified,
-				"filename" => "{$pagefilename}.r0",
+				"filename" => normalize_filename("{$pagefilename}.r0"),
 				"newsize" => $newentry->size,
 				"sizediff" => $newentry->size,
-				"editor" => "unknown"
+				"editor" => $newentry->lasteditor
 			] ];
 		}
 
 		// Store the new entry in the new page index
 		$pageindex->$pagekey = $newentry;
+
+		if($output) {
+				$message = "[" . ($i + 1) . " / $existingpages_count] Added $pagefilename to the pageindex.";
+				if(!is_cli()) $message = "data: $message\n\n";
+				else $message = "$message\r";
+				echo($message);
+				flush();
+			}
 	}
-	
+
 	if(function_exists("history_add_revision")) {
-		$history_revs = glob_recursive($env->storage_prefix . "*.r*");
-		// It's very important that we read the history revisions in the right order and that we don't skip any
+
+		// collect from the filesystem what revision files we have
+		$history_revs = glob_recursive($env->storage_prefix . "*.md.r*");
+
+		// sort them in the ascending order of their revision numbers - it's very important for further processing
 		usort($history_revs, function($a, $b) {
 			preg_match("/[0-9]+$/", $a, $revid_a);
 			$revid_a = intval($revid_a[0]);
@@ -112,29 +136,40 @@ if(!file_exists($paths->pageindex))
 			$revid_b = intval($revid_b[0]);
 			return $revid_a - $revid_b;
 		});
-		// We can guarantee that the direcotry separator is present on the end - it's added explicitly earlier
-		$strlen_storageprefix = strlen($env->storage_prefix);
+
+
 		foreach($history_revs as $filename) {
 			preg_match("/[0-9]+$/", $filename, $revid);
 			error_log("raw revid | ".var_export($revid, true));
 			if(count($revid) === 0) continue;
 			$revid = intval($revid[0]);
-			
+
 			$pagename = filepath_to_pagename($filename);
-			$filepath_stripped = substr($filename, $strlen_storageprefix);
-			
+			$filepath_stripped = normalize_filename($filename);
+
 			if(!isset($pageindex->$pagename->history))
 				$pageindex->$pagename->history = [];
-			
+
 			if(isset($pageindex->$pagename->history[$revid]))
 				continue;
-			
+
 			error_log("pagename: $pagename, revid: $revid, pageindex entry: ".var_export($pageindex->$pagename, true));
 			$newsize = filesize($filename);
 			$prevsize = 0;
 			if($revid > 0 && isset($pageindex->$pagename->history[$revid - 1])) {
 				$prevsize = filesize(end($pageindex->$pagename->history)->filename);
 			}
+
+			// Let's attempt to salvage the editor for this revision from the old pageindex
+			// For that we walk through history of edits from old pageindex to find what editor was set for this specific file
+			$revision_editor = "unknown";
+			if ($old_pageindex->$pagename->history) {
+				foreach ($old_pageindex->$pagename->history as $revision)
+					if ($revision->filename == $filepath_stripped && isset($revision->editor))
+						$revision_editor = $revision->editor;
+			}
+
+			// save the revision into history
 			$pageindex->$pagename->history[$revid] = (object) [
 				"type" => "edit",
 				"rid" => $revid,
@@ -142,13 +177,30 @@ if(!file_exists($paths->pageindex))
 				"filename" => $filepath_stripped,
 				"newsize" => $newsize,
 				"sizediff" => $newsize - $prevsize,
-				"editor" => "unknown"
+				"editor" => $revision_editor
 			];
 		}
 	}
-	
+
 	save_pageindex();
 	unset($existingpages);
+
+
+	if($output && !is_cli()) {
+		echo("data: Done! \n\n");
+		flush();
+	}
+
+
+}
+
+/*
+ * Sort out the pageindex. Create it if it doesn't exist, and load + parse it
+ * if it does.
+ */
+if(!file_exists($paths->pageindex))
+{
+	pageindex_rebuild(false);
 }
 else
 {
diff --git a/modules/feature-guiconfig.php b/modules/feature-guiconfig.php
index 648f06a..daf15d3 100644
--- a/modules/feature-guiconfig.php
+++ b/modules/feature-guiconfig.php
@@ -47,10 +47,10 @@ register_module([
 			$content .= "<p>You're currently running Pepperminty Wiki $version+" . substr($commit, 0, 7) . ".</p>\n";
 			$content .= "<h2>Actions</h2>";
 			
+			// rebuild search index button
 			$content .= "<button class='action-invindex-rebuild' title='Rebuilds the index that is consulted when searching the wiki. Hit this button if some pages are not showing up.'>Rebuild Search Index</button>\n";
 			$content .= "<progress class='action-invindex-rebuild-progress' min='0' max='100' value='0' style='display: none;'></progress><br />\n";
 			$content .= "<output class='action-invindex-rebuild-latestmessage'></output><br />\n";
-			
 			$invindex_rebuild_script = <<<SCRIPT
 window.addEventListener("load", function(event) {
 	document.querySelector(".action-invindex-rebuild").addEventListener("click", function(event) {
@@ -76,8 +76,38 @@ window.addEventListener("load", function(event) {
 	});
 });
 SCRIPT;
-
 			page_renderer::add_js_snippet($invindex_rebuild_script);
+
+			// rebuild page index button
+			$content .= "<button class='action-pageindex-rebuild' title='Rebuilds the page index that contains information (tags, author, dates, filename) about all wiki pages. Hit this button if MD files were changed externally.'>Rebuild Page Index</button>\n";
+			$content .= "<progress class='action-pageindex-rebuild-progress' min='0' max='100' value='0' style='display: none;'></progress><br />\n";
+			$content .= "<output class='action-pageindex-rebuild-latestmessage'></output><br />\n";
+			$pageindex_rebuild_script = <<<SCRIPT
+window.addEventListener("load", function(event) {
+	document.querySelector(".action-pageindex-rebuild").addEventListener("click", function(event) {
+		var rebuildActionEvents = new EventSource("?action=pageindex-rebuild");
+		var latestMessageElement = document.querySelector(".action-pageindex-rebuild-latestmessage");
+		var progressElement = document.querySelector(".action-pageindex-rebuild-progress");
+		rebuildActionEvents.addEventListener("message", function(event) {
+			console.log(event);
+			let message = event.data;
+			latestMessageElement.value = event.data;
+			let parts = message.match(/^\[\s*(\d+)\s+\/\s+(\d+)\s*\]/);
+			if(parts != null) {
+				progressElement.style.display = "";
+				progressElement.min = 0;
+				progressElement.max = parseInt(parts[2]);
+				progressElement.value = parseInt(parts[1]);
+			}
+			if(message.startsWith("Done!"))
+				rebuildActionEvents.close();
+		});
+		// Close the connection on error & don't try again
+		rebuildActionEvents.addEventListener("error", (_event) => rebuildActionEvents.close());
+	});
+});
+SCRIPT;
+			page_renderer::add_js_snippet($pageindex_rebuild_script);
 			
 			$content .= "<h2>Settings</h2>";
 			$content .= "<p>Mouse over the name of each setting to see a description of what it does.</p>\n";
@@ -207,6 +237,41 @@ SCRIPT;
 			$content .= "</textarea>\n";
 			exit(page_renderer::render_main("Master Settings Updated - $settings->sitename", $content));
 		});
+
+		/**
+		 * @api {get} ?action=pageindex-rebuild[&format=json] Rebuilds the page index
+		 * @apiName UserList
+		 * @apiGroup Utility
+		 * @apiPermission Anonymous
+		 */
+
+
+		/*                                 _               _                                        _               _   _       _
+         *                                (_)             | |                                      | |             (_) | |     | |
+         *  _ __     __ _    __ _    ___   _   _ __     __| |   ___  __  __  ______   _ __    ___  | |__    _   _   _  | |   __| |
+         * | '_ \   / _` |  / _` |  / _ \ | | | '_ \   / _` |  / _ \ \ \/ / |______| | '__|  / _ \ | '_ \  | | | | | | | |  / _` |
+         * | |_) | | (_| | | (_| | |  __/ | | | | | | | (_| | |  __/  >  <           | |    |  __/ | |_) | | |_| | | | | | | (_| |
+         * | .__/   \__,_|  \__, |  \___| |_| |_| |_|  \__,_|  \___| /_/\_\          |_|     \___| |_.__/   \__,_| |_| |_|  \__,_|
+         * | |               __/ |
+         * |_|              |___/
+         *
+         */
+		add_action("pageindex-rebuild", function() {
+			global $env, $settings;
+			if($env->is_admin ||
+				(
+					!empty($_POST["secret"]) &&
+					$_POST["secret"] === $settings->secret
+				)
+			)
+				pageindex_rebuild();
+			else
+			{
+				http_response_code(401);
+				exit(page_renderer::render_main("Error - Page index regenerator - $settings->sitename", "<p>Error: You aren't allowed to regenerate the page index. Try logging in as an admin, or setting the <code>secret</code> POST parameter to $settings->sitename's secret - which can be found in $settings->sitename's <code>peppermint.json</code> file.</p>"));
+			}
+		});
+
 		
 		add_help_section("800-raw-page-content", "Viewing Raw Page Content", "<p>Although you can use the edit page to view a page's source, you can also ask $settings->sitename to send you the raw page source and nothing else. This feature is intended for those who want to automate their interaction with $settings->sitename.</p>
 		<p>To use this feature, navigate to the page for which you want to see the source, and then alter the <code>action</code> parameter in the url's query string to be <code>raw</code>. If the <code>action</code> parameter doesn't exist, add it. Note that when used on an file's page this action will return the source of the description and not the file itself.</p>");
diff --git a/themes/default/theme.css b/themes/default/theme.css
index 5ef9cdd..e66c733 100644
--- a/themes/default/theme.css
+++ b/themes/default/theme.css
@@ -174,6 +174,7 @@ blockquote { padding-left: 1em; border-left: 0.2em solid var(--accent-a3); borde
 
 pre { white-space: pre-wrap; padding: 0.3em 0.5em; background: var(--bg-page-inset); border-radius: 0.25em; box-shadow: inset 0 0 0.5em var(--shadow); }
 code { font-size: 1.1em; }
+code:not(:has(> pre)) { background: var(--bg-page-inset); }
 
 a { cursor: pointer; }
 a:focus { outline-width: 0.1em; }