Add restore locally changed content button

This commit is contained in:
Starbeamrainbowlabs 2017-11-21 20:07:50 +00:00
parent f9ad937a21
commit bcf562e7ca
4 changed files with 110 additions and 33 deletions

View File

@ -15,6 +15,7 @@ This file holds the changelog for Pepperminty Wiki. This is the master list of t
- The `history` action now supports `format=json` and `format=csv` - The `history` action now supports `format=json` and `format=csv`
- Added tags next to the names of pages in the search results - Added tags next to the names of pages in the search results
- Added new `random_page_exclude` setting that allows you to exclude pages from the random action with a (PHP) regular expression - Added new `random_page_exclude` setting that allows you to exclude pages from the random action with a (PHP) regular expression
- Added "restore locally saved content" button to page editor
- [module api] Added new `get_page_parent($pagename)` method. - [module api] Added new `get_page_parent($pagename)` method.
- [module api] Added new remote file system to download additional required files. Use it with `register_remote_file` - [module api] Added new remote file system to download additional required files. Use it with `register_remote_file`

View File

@ -4020,9 +4020,14 @@ window.addEventListener("load", function(event) {
} }
]); ]);
/**
* Holds a collection to methods to manipulate various types of search index.
*/
class search class search
{ {
// Words that we should exclude from the inverted index /**
* Words that we should exclude from the inverted index
*/
public static $stop_words = [ public static $stop_words = [
"a", "about", "above", "above", "across", "after", "afterwards", "again", "a", "about", "above", "above", "across", "after", "afterwards", "again",
"against", "all", "almost", "alone", "along", "already", "also", "against", "all", "almost", "alone", "along", "already", "also",
@ -4068,6 +4073,12 @@ class search
"your", "yours", "yourself", "yourselves" "your", "yours", "yourself", "yourselves"
]; ];
/**
* Converts a source string into an index of search terms that can be
* merged into an inverted index.
* @param string $source The source string to index.
* @return array An index represents the specified string.
*/
public static function index($source) public static function index($source)
{ {
$source = html_entity_decode($source, ENT_QUOTES); $source = html_entity_decode($source, ENT_QUOTES);
@ -4098,6 +4109,11 @@ class search
return $index; return $index;
} }
/**
* Converts a source string into a series of raw tokens.
* @param string $source The source string to process.
* @return array An array of raw tokens extracted from the specified source string.
*/
public static function tokenize($source) public static function tokenize($source)
{ {
$source = strtolower($source); $source = strtolower($source);
@ -4105,11 +4121,21 @@ class search
return preg_split("/((^\p{P}+)|(\p{P}*\s+\p{P}*)|(\p{P}+$))|\|/u", $source, -1, PREG_SPLIT_NO_EMPTY); return preg_split("/((^\p{P}+)|(\p{P}*\s+\p{P}*)|(\p{P}+$))|\|/u", $source, -1, PREG_SPLIT_NO_EMPTY);
} }
/**
* Removes (most) markdown markup from the specified string.
* Stripped strings are not suitable for indexing!
* @param string $source The source string to process.
* @return string The stripped string.
*/
public static function strip_markup($source) public static function strip_markup($source)
{ {
return str_replace([ "[", "]", "\"", "*", "_", " - ", "`" ], "", $source); return str_replace([ "[", "]", "\"", "*", "_", " - ", "`" ], "", $source);
} }
/**
* Rebuilds the master inverted index and clears the page id index.
* @param boolean $output Whether to send progress information to the user's browser.
*/
public static function rebuild_invindex($output = true) public static function rebuild_invindex($output = true)
{ {
global $pageindex, $env, $paths, $settings; global $pageindex, $env, $paths, $settings;
@ -4158,25 +4184,23 @@ class search
self::save_invindex($paths->searchindex, $invindex); self::save_invindex($paths->searchindex, $invindex);
} }
/* /**
* @summary Sorts an index alphabetically. Will also sort an inverted index. * Sorts an index alphabetically. Will also sort an inverted index.
* This allows us to do a binary search instead of a regular * This allows us to do a binary search instead of a regular
* sequential search. * sequential search.
* @param array $index The index to sort.
*/ */
public static function sort_index(&$index) public static function sort_index(&$index)
{ {
ksort($index, SORT_NATURAL); ksort($index, SORT_NATURAL);
} }
/* /**
* @summary Compares two *regular* indexes to find the differences between them. * Compares two *regular* indexes to find the differences between them.
* * @param array $oldindex The old index.
* @param {array} $indexa - The old index. * @param array $newindex The new index.
* @param {array} $indexb - The new index. * @param array $changed An array to be filled with the nterms of all the changed entries.
* @param {array} $changed - An array to be filled with the nterms of all * @param array $removed An array to be filled with the nterms of all the removed entries.
* the changed entries.
* @param {array} $removed - An array to be filled with the nterms of all
* the removed entries.
*/ */
public static function compare_indexes($oldindex, $newindex, &$changed, &$removed) public static function compare_indexes($oldindex, $newindex, &$changed, &$removed)
{ {
@ -4193,15 +4217,19 @@ class search
} }
} }
/* /**
* @summary Reads in and parses an inverted index. * Reads in and parses an inverted index.
* @param string $invindex_filename The path tp the inverted index to parse.
* @todo Remove this function and make everything streamable
*/ */
// Todo remove this function and make everything streamable
public static function load_invindex($invindex_filename) { public static function load_invindex($invindex_filename) {
$invindex = json_decode(file_get_contents($invindex_filename), true); $invindex = json_decode(file_get_contents($invindex_filename), true);
return $invindex; return $invindex;
} }
/**
* Reads in and parses an inverted index, measuring the time it takes to do so.
* @param string $invindex_filename The path to the file inverted index to parse.
*/
public static function measure_invindex_load_time($invindex_filename) { public static function measure_invindex_load_time($invindex_filename) {
global $env; global $env;
@ -4210,8 +4238,12 @@ class search
$env->perfdata->searchindex_decode_time = round((microtime(true) - $searchindex_decode_start)*1000, 3); $env->perfdata->searchindex_decode_time = round((microtime(true) - $searchindex_decode_start)*1000, 3);
} }
/* /**
* @summary Merge an index into an inverted index. * Merge an index into an inverted index.
* @param array $invindex The inverted index to merge into.
* @param int $pageid The id of the page to assign to the index that's being merged.
* @param array $index The regular index to merge.
* @param array $removals An array of index entries to remove from the inverted index. Useful for applying changes to an inverted index instead of deleting and remerging an entire page's index.
*/ */
public static function merge_into_invindex(&$invindex, $pageid, &$index, &$removals = []) public static function merge_into_invindex(&$invindex, $pageid, &$index, &$removals = [])
{ {
@ -4262,11 +4294,22 @@ class search
} }
} }
/**
* Saves the given inverted index back to disk.
* @param string $filename The path to the file to save the inverted index to.
* @param array $invindex The inverted index to save.
*/
public static function save_invindex($filename, &$invindex) public static function save_invindex($filename, &$invindex)
{ {
file_put_contents($filename, json_encode($invindex)); file_put_contents($filename, json_encode($invindex));
} }
/**
* Searches the given inverted index for the specified search terms.
* @param string $query The search query.
* @param array $invindex The inverted index to search.
* @return array An array of matching pages.
*/
public static function query_invindex($query, &$invindex) public static function query_invindex($query, &$invindex)
{ {
global $settings, $pageindex; global $settings, $pageindex;
@ -4368,7 +4411,7 @@ class search
// Sort the new list of clump distances // Sort the new list of clump distances
sort($clumpDistances); sort($clumpDistances);
// Calcualate a measureof how clumped the offsets are // Calcualate a measure of how clumped the offsets are
$tightClumpLimit = floor((count($clumpDistances) - 1) / 0.25); $tightClumpLimit = floor((count($clumpDistances) - 1) / 0.25);
$tightClumpsMeasure = $clumpDistances[$tightClumpLimit] - $clumpDistances[0]; $tightClumpsMeasure = $clumpDistances[$tightClumpLimit] - $clumpDistances[0];
$clumpsRange = $clumpDistances[count($clumpDistances) - 1] - $clumpDistances[0]; $clumpsRange = $clumpDistances[count($clumpDistances) - 1] - $clumpDistances[0];
@ -4395,6 +4438,13 @@ class search
return $matching_pages; return $matching_pages;
} }
/**
* Extracts a context string (in HTML) given a search query that could be displayed
* in a list of search results.
* @param string $query The search queary to generate the context for.
* @param string $source The page source to extract the context from.
* @return string The generated context string.
*/
public static function extract_context($query, $source) public static function extract_context($query, $source)
{ {
global $settings; global $settings;
@ -4479,6 +4529,12 @@ class search
return implode(" ... ", $contexts); return implode(" ... ", $contexts);
} }
/**
* Highlights the keywords of a context string.
* @param string $query The query to use when highlighting.
* @param string $context The context string to highlight.
* @return string The highlighted (HTML) string.
*/
public static function highlight_context($query, $context) public static function highlight_context($query, $context)
{ {
$qterms = self::tokenize($query); $qterms = self::tokenize($query);
@ -6165,6 +6221,7 @@ register_module([
$content .= "<form method='post' name='edit-form' action='index.php?action=preview-edit&page=" . rawurlencode($env->page) . "' class='editform'> $content .= "<form method='post' name='edit-form' action='index.php?action=preview-edit&page=" . rawurlencode($env->page) . "' class='editform'>
<input type='hidden' name='prev-content-hash' value='" . ((isset($old_pagetext)) ? sha1($old_pagetext) : sha1($pagetext)) . "' /> <input type='hidden' name='prev-content-hash' value='" . ((isset($old_pagetext)) ? sha1($old_pagetext) : sha1($pagetext)) . "' />
<button class='smartsave-restore'>Restore Locally Saved Content</button>
<textarea name='content' autofocus tabindex='1'>$pagetext</textarea> <textarea name='content' autofocus tabindex='1'>$pagetext</textarea>
<pre class='fit-text-mirror'></pre> <pre class='fit-text-mirror'></pre>
<input type='text' name='tags' value='" . htmlentities($page_tags, ENT_HTML5 | ENT_QUOTES) . "' placeholder='Enter some tags for the page here. Separate them with commas.' title='Enter some tags for the page here. Separate them with commas.' tabindex='2' /> <input type='text' name='tags' value='" . htmlentities($page_tags, ENT_HTML5 | ENT_QUOTES) . "' placeholder='Enter some tags for the page here. Separate them with commas.' title='Enter some tags for the page here. Separate them with commas.' tabindex='2' />
@ -6208,16 +6265,25 @@ window.addEventListener("load", function(event) {
/// ~~~ Smart saving ~~~ /// /// ~~~ Smart saving ~~~ ///
// TODO: Add a button to press that restores the content that you were working on before. // TODO: Add a button to press that restores the content that you were working on before.
page_renderer::AddJSSnippet('document.addEventListener("load", function(event) { page_renderer::AddJSSnippet('window.addEventListener("load", function(event) {
"use strict";
// Smart saving // Smart saving
function getSmartSaveKey() { return document.querySelector("main h1").innerHTML.replace("Creating ", "").replace("Editing ", "").trim(); } let getSmartSaveKey = function() { return document.querySelector("main h1").innerHTML.replace("Creating ", "").replace("Editing ", "").trim(); }
// Saving // Saving
document.querySelector("textarea[name=content]").addEventListener("keyup", function(event) { window.localStorage.setItem(getSmartSaveKey(), event.target.value) }); document.querySelector("textarea[name=content]").addEventListener("keyup", function(event) { window.localStorage.setItem(getSmartSaveKey(), event.target.value) });
// Loading // Loading
window.addEventListener("load", function(event) { var editor = document.querySelector("textarea[name=content]");
var editor = document.querySelector("textarea[name=content]"); let smartsave_restore = function() {
if(editor.value.length > 0) return; // Don\'t restore if there\'s data in the editor already
editor.value = localStorage.getItem(getSmartSaveKey()); editor.value = localStorage.getItem(getSmartSaveKey());
}
if(editor.value.length === 0) // Don\'t restore if there\'s data in the editor already
smartsave_restore();
document.querySelector(".smartsave-restore").addEventListener("click", function(event) {
event.stopPropagation();
event.preventDefault();
smartsave_restore();
}); });
});'); });');

View File

@ -104,7 +104,7 @@
"author": "Starbeamrainbowlabs", "author": "Starbeamrainbowlabs",
"description": "Adds proper search functionality to Pepperminty Wiki using an inverted index to provide a full text search engine. If pages don't show up, then you might have hit a stop word. If not, try requesting the `invindex-rebuild` action to rebuild the inverted index from scratch.", "description": "Adds proper search functionality to Pepperminty Wiki using an inverted index to provide a full text search engine. If pages don't show up, then you might have hit a stop word. If not, try requesting the `invindex-rebuild` action to rebuild the inverted index from scratch.",
"id": "feature-search", "id": "feature-search",
"lastupdate": 1508071155, "lastupdate": 1511208633,
"optional": false "optional": false
}, },
{ {
@ -167,7 +167,7 @@
"author": "Starbeamrainbowlabs", "author": "Starbeamrainbowlabs",
"description": "Allows you to edit pages by adding the edit and save actions. You should probably include this one.", "description": "Allows you to edit pages by adding the edit and save actions. You should probably include this one.",
"id": "page-edit", "id": "page-edit",
"lastupdate": 1510613807, "lastupdate": 1511294696,
"optional": false "optional": false
}, },
{ {

View File

@ -138,6 +138,7 @@ register_module([
$content .= "<form method='post' name='edit-form' action='index.php?action=preview-edit&page=" . rawurlencode($env->page) . "' class='editform'> $content .= "<form method='post' name='edit-form' action='index.php?action=preview-edit&page=" . rawurlencode($env->page) . "' class='editform'>
<input type='hidden' name='prev-content-hash' value='" . ((isset($old_pagetext)) ? sha1($old_pagetext) : sha1($pagetext)) . "' /> <input type='hidden' name='prev-content-hash' value='" . ((isset($old_pagetext)) ? sha1($old_pagetext) : sha1($pagetext)) . "' />
<button class='smartsave-restore'>Restore Locally Saved Content</button>
<textarea name='content' autofocus tabindex='1'>$pagetext</textarea> <textarea name='content' autofocus tabindex='1'>$pagetext</textarea>
<pre class='fit-text-mirror'></pre> <pre class='fit-text-mirror'></pre>
<input type='text' name='tags' value='" . htmlentities($page_tags, ENT_HTML5 | ENT_QUOTES) . "' placeholder='Enter some tags for the page here. Separate them with commas.' title='Enter some tags for the page here. Separate them with commas.' tabindex='2' /> <input type='text' name='tags' value='" . htmlentities($page_tags, ENT_HTML5 | ENT_QUOTES) . "' placeholder='Enter some tags for the page here. Separate them with commas.' title='Enter some tags for the page here. Separate them with commas.' tabindex='2' />
@ -181,16 +182,25 @@ window.addEventListener("load", function(event) {
/// ~~~ Smart saving ~~~ /// /// ~~~ Smart saving ~~~ ///
// TODO: Add a button to press that restores the content that you were working on before. // TODO: Add a button to press that restores the content that you were working on before.
page_renderer::AddJSSnippet('document.addEventListener("load", function(event) { page_renderer::AddJSSnippet('window.addEventListener("load", function(event) {
"use strict";
// Smart saving // Smart saving
function getSmartSaveKey() { return document.querySelector("main h1").innerHTML.replace("Creating ", "").replace("Editing ", "").trim(); } let getSmartSaveKey = function() { return document.querySelector("main h1").innerHTML.replace("Creating ", "").replace("Editing ", "").trim(); }
// Saving // Saving
document.querySelector("textarea[name=content]").addEventListener("keyup", function(event) { window.localStorage.setItem(getSmartSaveKey(), event.target.value) }); document.querySelector("textarea[name=content]").addEventListener("keyup", function(event) { window.localStorage.setItem(getSmartSaveKey(), event.target.value) });
// Loading // Loading
window.addEventListener("load", function(event) { var editor = document.querySelector("textarea[name=content]");
var editor = document.querySelector("textarea[name=content]"); let smartsave_restore = function() {
if(editor.value.length > 0) return; // Don\'t restore if there\'s data in the editor already
editor.value = localStorage.getItem(getSmartSaveKey()); editor.value = localStorage.getItem(getSmartSaveKey());
}
if(editor.value.length === 0) // Don\'t restore if there\'s data in the editor already
smartsave_restore();
document.querySelector(".smartsave-restore").addEventListener("click", function(event) {
event.stopPropagation();
event.preventDefault();
smartsave_restore();
}); });
});'); });');