minor refactoring: normalize some function names; cleanup; etc

This commit is contained in:
Andrew Dolgov 2018-12-01 11:18:35 +03:00
parent e720e6b628
commit 195180b64d
8 changed files with 89 additions and 92 deletions

View File

@ -456,7 +456,7 @@ class Article extends Handler_Protected {
# $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\" rel=\"noopener noreferrer\">" . # $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\" rel=\"noopener noreferrer\">" .
# $filename . " (" . $ctype . ")" . "</a>"; # $filename . " (" . $ctype . ")" . "</a>";
$entry = "<div onclick=\"openUrlPopup('".htmlspecialchars($url)."')\" $entry = "<div onclick=\"popupOpenUrl('".htmlspecialchars($url)."')\"
dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>"; dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
array_push($entries_html, $entry); array_push($entries_html, $entry);
@ -536,7 +536,7 @@ class Article extends Handler_Protected {
else else
$filename = ""; $filename = "";
$rv .= "<div onclick='openUrlPopup(\"".htmlspecialchars($entry["url"])."\")' $rv .= "<div onclick='popupOpenUrl(\"".htmlspecialchars($entry["url"])."\")'
dojoType=\"dijit.MenuItem\">".$filename . $title."</div>"; dojoType=\"dijit.MenuItem\">".$filename . $title."</div>";
}; };

View File

@ -1006,7 +1006,7 @@ class Feeds extends Handler_Protected {
print "<div style=\"clear : both\"> print "<div style=\"clear : both\">
<input type=\"checkbox\" name=\"need_auth\" dojoType=\"dijit.form.CheckBox\" id=\"feedDlg_loginCheck\" <input type=\"checkbox\" name=\"need_auth\" dojoType=\"dijit.form.CheckBox\" id=\"feedDlg_loginCheck\"
onclick='checkboxToggleElement(this, \"feedDlg_loginContainer\")'> onclick='displayIfChecked(this, \"feedDlg_loginContainer\")'>
<label for=\"feedDlg_loginCheck\">". <label for=\"feedDlg_loginCheck\">".
__('This feed requires authentication.')."</div>"; __('This feed requires authentication.')."</div>";

View File

@ -640,7 +640,7 @@ class Pref_Feeds extends Handler_Protected {
$auth_checked = $auth_enabled ? 'checked' : ''; $auth_checked = $auth_enabled ? 'checked' : '';
print "<div style=\"clear : both\"> print "<div style=\"clear : both\">
<input type=\"checkbox\" $auth_checked name=\"need_auth\" dojoType=\"dijit.form.CheckBox\" id=\"feedEditDlg_loginCheck\" <input type=\"checkbox\" $auth_checked name=\"need_auth\" dojoType=\"dijit.form.CheckBox\" id=\"feedEditDlg_loginCheck\"
onclick='checkboxToggleElement(this, \"feedEditDlg_loginContainer\")'> onclick='displayIfChecked(this, \"feedEditDlg_loginContainer\")'>
<label for=\"feedEditDlg_loginCheck\">". <label for=\"feedEditDlg_loginCheck\">".
__('This feed requires authentication.')."</div>"; __('This feed requires authentication.')."</div>";
@ -1656,7 +1656,7 @@ class Pref_Feeds extends Handler_Protected {
print "<div style=\"clear : both\"> print "<div style=\"clear : both\">
<input type=\"checkbox\" name=\"need_auth\" dojoType=\"dijit.form.CheckBox\" id=\"feedDlg_loginCheck\" <input type=\"checkbox\" name=\"need_auth\" dojoType=\"dijit.form.CheckBox\" id=\"feedDlg_loginCheck\"
onclick='checkboxToggleElement(this, \"feedDlg_loginContainer\")'> onclick='displayIfChecked(this, \"feedDlg_loginContainer\")'>
<label for=\"feedDlg_loginCheck\">". <label for=\"feedDlg_loginCheck\">".
__('Feeds require authentication.')."</div>"; __('Feeds require authentication.')."</div>";

View File

@ -153,7 +153,7 @@ class Pref_Filters extends Handler_Protected {
$id = $line['id']; $id = $line['id'];
$tmp .= "<td width='5%' align='center'><img style='cursor : pointer' title='".__("Preview article")."' $tmp .= "<td width='5%' align='center'><img style='cursor : pointer' title='".__("Preview article")."'
src='images/information.png' onclick='openArticlePopup($id)'></td><td>"; src='images/information.png' onclick='popupOpenArticle($id)'></td><td>";
/*foreach ($filter['rules'] as $rule) { /*foreach ($filter['rules'] as $rule) {
$reg_exp = str_replace('/', '\/', $rule["reg_exp"]); $reg_exp = str_replace('/', '\/', $rule["reg_exp"]);

View File

@ -165,10 +165,10 @@ function viewfeed(params) {
function feedlist_init() { function feedlist_init() {
console.log("in feedlist init"); console.log("in feedlist init");
loading_set_progress(50); setLoadingProgress(50);
document.onkeydown = hotkey_handler; document.onkeydown = hotkey_handler;
setInterval(hotkey_prefix_timeout, 5*1000); setInterval(hotkeyPrefixTimeout, 5*1000);
setInterval(catchupBatchedArticles, 3*1000); setInterval(catchupBatchedArticles, 3*1000);
if (!getActiveFeedId()) { if (!getActiveFeedId()) {

View File

@ -259,18 +259,6 @@ function getCookie(name) {
return unescape(dc.substring(begin + prefix.length, end)); return unescape(dc.substring(begin + prefix.length, end));
} }
function gotoPreferences() {
document.location.href = "prefs.php";
}
function gotoLogout() {
document.location.href = "backend.php?op=logout";
}
function gotoMain() {
document.location.href = "index.php";
}
function toggleSelectRowById(sender, id) { function toggleSelectRowById(sender, id) {
const row = $(id); const row = $(id);
return toggleSelectRow(sender, row); return toggleSelectRow(sender, row);
@ -314,11 +302,12 @@ function toggleSelectRow(sender, row) {
updateSelectedPrompt(); updateSelectedPrompt();
} }
function checkboxToggleElement(elem, id) { // noinspection JSUnusedGlobalSymbols
if (elem.checked) { function displayIfChecked(checkbox, elemId) {
Effect.Appear(id, {duration : 0.5}); if (checkbox.checked) {
Effect.Appear(elemId, {duration : 0.5});
} else { } else {
Effect.Fade(id, {duration : 0.5}); Effect.Fade(elemId, {duration : 0.5});
} }
} }
@ -326,6 +315,7 @@ function getURLParam(param){
return String(window.location.href).parseQuery()[param]; return String(window.location.href).parseQuery()[param];
} }
// noinspection JSUnusedGlobalSymbols
function closeInfoBox() { function closeInfoBox() {
const dialog = dijit.byId("infoBox"); const dialog = dijit.byId("infoBox");
@ -334,9 +324,7 @@ function closeInfoBox() {
return false; return false;
} }
function displayDlg(title, id, param, callback) { function displayDlg(title, id, param, callback) {
notify_progress("Loading, please wait...", true); notify_progress("Loading, please wait...", true);
const query = { op: "dlg", method: id, param: param }; const query = { op: "dlg", method: id, param: param };
@ -404,6 +392,7 @@ function fatalError(code, msg, ext_info) {
} }
} }
/* global ERRORS */
if (ERRORS && ERRORS[code] && !msg) { if (ERRORS && ERRORS[code] && !msg) {
msg = ERRORS[code]; msg = ERRORS[code];
} }
@ -430,6 +419,7 @@ function fatalError(code, msg, ext_info) {
} }
// noinspection JSUnusedGlobalSymbols
function filterDlgCheckAction(sender) { function filterDlgCheckAction(sender) {
const action = sender.value; const action = sender.value;
@ -466,7 +456,7 @@ function explainError(code) {
return displayDlg(__("Error explained"), "explainError", code); return displayDlg(__("Error explained"), "explainError", code);
} }
function loading_set_progress(p) { function setLoadingProgress(p) {
loading_progress += p; loading_progress += p;
if (dijit.byId("loading_bar")) if (dijit.byId("loading_bar"))
@ -481,8 +471,7 @@ function strip_tags(s) {
return s.replace(/<\/?[^>]+(>|$)/g, ""); return s.replace(/<\/?[^>]+(>|$)/g, "");
} }
function hotkey_prefix_timeout() { function hotkeyPrefixTimeout() {
const date = new Date(); const date = new Date();
const ts = Math.round(date.getTime() / 1000); const ts = Math.round(date.getTime() / 1000);
@ -494,6 +483,7 @@ function hotkey_prefix_timeout() {
} }
} }
// noinspection JSUnusedGlobalSymbols
function uploadIconHandler(rc) { function uploadIconHandler(rc) {
switch (rc) { switch (rc) {
case 0: case 0:
@ -513,6 +503,7 @@ function uploadIconHandler(rc) {
} }
} }
// noinspection JSUnusedGlobalSymbols
function removeFeedIcon(id) { function removeFeedIcon(id) {
if (confirm(__("Remove stored feed icon?"))) { if (confirm(__("Remove stored feed icon?"))) {
@ -533,6 +524,7 @@ function removeFeedIcon(id) {
return false; return false;
} }
// noinspection JSUnusedGlobalSymbols
function uploadFeedIcon() { function uploadFeedIcon() {
const file = $("icon_file"); const file = $("icon_file");
@ -547,17 +539,11 @@ function uploadFeedIcon() {
} }
function addLabel(select, callback) { function addLabel(select, callback) {
const caption = prompt(__("Please enter label caption:"), ""); const caption = prompt(__("Please enter label caption:"), "");
if (caption != undefined) { if (caption != undefined && caption.trim().length > 0) {
if (caption == "") { const query = { op: "pref-labels", method: "add", caption: caption.trim() };
alert(__("Can't create label: missing caption."));
return false;
}
const query = { op: "pref-labels", method: "add", caption: caption };
if (select) if (select)
Object.extend(query, {output: "select"}); Object.extend(query, {output: "select"});
@ -686,9 +672,6 @@ function quickAddFeed() {
function createNewRuleElement(parentNode, replaceNode) { function createNewRuleElement(parentNode, replaceNode) {
const form = document.forms["filter_new_rule_form"]; const form = document.forms["filter_new_rule_form"];
//form.reg_exp.value = form.reg_exp.value.replace(/(<([^>]+)>)/ig,"");
const query = { op: "pref-filters", method: "printrulename", rule: dojo.formToJson(form) }; const query = { op: "pref-filters", method: "printrulename", rule: dojo.formToJson(form) };
xhrPost("backend.php", query, (transport) => { xhrPost("backend.php", query, (transport) => {
@ -818,7 +801,7 @@ function editFilterTest(query) {
if (dijit.byId("filterTestDlg")) if (dijit.byId("filterTestDlg"))
dijit.byId("filterTestDlg").destroyRecursive(); dijit.byId("filterTestDlg").destroyRecursive();
var test_dlg = new dijit.Dialog({ const test_dlg = new dijit.Dialog({
id: "filterTestDlg", id: "filterTestDlg",
title: "Test Filter", title: "Test Filter",
style: "width: 600px", style: "width: 600px",
@ -902,16 +885,16 @@ function editFilterTest(query) {
} }
function quickAddFilter() { function quickAddFilter() {
let query = ""; let query;
if (!inPreferences()) { if (!inPreferences()) {
query = "backend.php?op=pref-filters&method=newfilter&feed=" + query = { op: "pref-filters", method: "newfilter",
param_escape(getActiveFeedId()) + "&is_cat=" + feed: getActiveFeedId(), is_cat: activeFeedIsCat() };
param_escape(activeFeedIsCat());
} else { } else {
query = "backend.php?op=pref-filters&method=newfilter"; query = { op: "pref-filters", method: "newfilter" };
} }
console.log(query); console.log('quickAddFilter', query);
if (dijit.byId("feedEditDlg")) if (dijit.byId("feedEditDlg"))
dijit.byId("feedEditDlg").destroyRecursive(); dijit.byId("feedEditDlg").destroyRecursive();
@ -980,12 +963,12 @@ function quickAddFilter() {
}); });
} }
}, },
href: query}); href: "backend.php?" + dojo.objectToQuery(query)});
if (!inPreferences()) { if (!inPreferences()) {
const selectedText = getSelectionText(); const selectedText = getSelectionText();
var lh = dojo.connect(dialog, "onLoad", function(){ const lh = dojo.connect(dialog, "onLoad", function(){
dojo.disconnect(lh); dojo.disconnect(lh);
if (selectedText != "") { if (selectedText != "") {
@ -1078,25 +1061,27 @@ function backend_sanity_check_callback(transport) {
console.log('reading init-params...'); console.log('reading init-params...');
for (const k in params) { for (const k in params) {
switch (k) { if (params.hasOwnProperty(k)) {
case "label_base_index": switch (k) {
_label_base_index = parseInt(params[k]) case "label_base_index":
break; _label_base_index = parseInt(params[k])
case "hotkeys": break;
// filter mnemonic definitions (used for help panel) from hotkeys map case "hotkeys":
// i.e. *(191)|Ctrl-/ -> *(191) // filter mnemonic definitions (used for help panel) from hotkeys map
// i.e. *(191)|Ctrl-/ -> *(191)
const tmp = []; const tmp = [];
for (const sequence in params[k][1]) { for (const sequence in params[k][1]) {
const filtered = sequence.replace(/\|.*$/, ""); const filtered = sequence.replace(/\|.*$/, "");
tmp[filtered] = params[k][1][sequence]; tmp[filtered] = params[k][1][sequence];
} }
params[k][1] = tmp; params[k][1] = tmp;
break; break;
}
console.log("IP:", k, "=>", params[k]);
} }
console.log("IP:", k, "=>", params[k]);
} }
init_params = params; init_params = params;
@ -1108,6 +1093,7 @@ function backend_sanity_check_callback(transport) {
init_second_stage(); init_second_stage();
} }
// noinspection JSUnusedGlobalSymbols
function genUrlChangeKey(feed, is_cat) { function genUrlChangeKey(feed, is_cat) {
if (confirm(__("Generate new syndication address for this feed?"))) { if (confirm(__("Generate new syndication address for this feed?"))) {
@ -1212,10 +1198,9 @@ function editFeed(feed) {
if (feed <= 0) if (feed <= 0)
return alert(__("You can't edit this kind of feed.")); return alert(__("You can't edit this kind of feed."));
const query = "backend.php?op=pref-feeds&method=editfeed&id=" + const query = { op: "pref-feeds", method: "editfeed", id: feed };
param_escape(feed);
console.log(query); console.log("editFeed", query);
if (dijit.byId("filterEditDlg")) if (dijit.byId("filterEditDlg"))
dijit.byId("filterEditDlg").destroyRecursive(); dijit.byId("filterEditDlg").destroyRecursive();
@ -1231,20 +1216,20 @@ function editFeed(feed) {
if (this.validate()) { if (this.validate()) {
notify_progress("Saving data...", true); notify_progress("Saving data...", true);
xhrPost("backend.php", dialog.attr('value'), (transport) => { xhrPost("backend.php", dialog.attr('value'), () => {
dialog.hide(); dialog.hide();
notify(''); notify('');
updateFeedList(); updateFeedList();
}); });
} }
}, },
href: query}); href: "backend.php?" + dojo.objectToQuery(query)});
dialog.show(); dialog.show();
} }
function feedBrowser() { function feedBrowser() {
const query = "backend.php?op=feeds&method=feedBrowser"; const query = { op: "feeds", method: "feedBrowser" };
if (dijit.byId("feedAddDlg")) if (dijit.byId("feedAddDlg"))
dijit.byId("feedAddDlg").hide(); dijit.byId("feedAddDlg").hide();
@ -1252,6 +1237,7 @@ function feedBrowser() {
if (dijit.byId("feedBrowserDlg")) if (dijit.byId("feedBrowserDlg"))
dijit.byId("feedBrowserDlg").destroyRecursive(); dijit.byId("feedBrowserDlg").destroyRecursive();
// noinspection JSUnusedGlobalSymbols
const dialog = new dijit.Dialog({ const dialog = new dijit.Dialog({
id: "feedBrowserDlg", id: "feedBrowserDlg",
title: __("More Feeds"), title: __("More Feeds"),
@ -1340,10 +1326,7 @@ function feedBrowser() {
const selected = this.getSelectedFeedIds(); const selected = this.getSelectedFeedIds();
if (selected.length > 0) { if (selected.length > 0) {
if (confirm(__("Remove selected feeds from the archive? Feeds with stored articles will not be removed."))) {
const pr = __("Remove selected feeds from the archive? Feeds with stored articles will not be removed.");
if (confirm(pr)) {
Element.show('feed_browser_spinner'); Element.show('feed_browser_spinner');
const query = { op: "rpc", method: "remarchive", ids: selected.toString() }; const query = { op: "rpc", method: "remarchive", ids: selected.toString() };
@ -1359,14 +1342,15 @@ function feedBrowser() {
this.subscribe(); this.subscribe();
} }
}, },
href: query href: "backend.php?" + dojo.objectToQuery(query)
}); });
dialog.show(); dialog.show();
} }
// noinspection JSUnusedGlobalSymbols
function showFeedsWithErrors() { function showFeedsWithErrors() {
const query = "backend.php?op=pref-feeds&method=feedsWithErrors"; const query = { op: "pref-feeds", method: "feedsWithErrors" };
if (dijit.byId("errorFeedsDlg")) if (dijit.byId("errorFeedsDlg"))
dijit.byId("errorFeedsDlg").destroyRecursive(); dijit.byId("errorFeedsDlg").destroyRecursive();
@ -1404,7 +1388,8 @@ function showFeedsWithErrors() {
// //
} }
}, },
href: query}); href: "backend.php?" + dojo.objectToQuery(query)
});
dialog.show(); dialog.show();
} }
@ -1430,16 +1415,17 @@ function helpDialog(topic) {
dialog.show(); dialog.show();
} }
// noinspection JSUnusedGlobalSymbols
function label_to_feed_id(label) { function label_to_feed_id(label) {
return _label_base_index - 1 - Math.abs(label); return _label_base_index - 1 - Math.abs(label);
} }
// noinspection JSUnusedGlobalSymbols
function feed_to_label_id(feed) { function feed_to_label_id(feed) {
return _label_base_index - 1 + Math.abs(feed); return _label_base_index - 1 + Math.abs(feed);
} }
// http://stackoverflow.com/questions/6251937/how-to-get-selecteduser-highlighted-text-in-contenteditable-element-and-replac // http://stackoverflow.com/questions/6251937/how-to-get-selecteduser-highlighted-text-in-contenteditable-element-and-replac
function getSelectionText() { function getSelectionText() {
let text = ""; let text = "";
@ -1461,13 +1447,16 @@ function getSelectionText() {
return text.stripTags(); return text.stripTags();
} }
function openUrlPopup(url) { // noinspection JSUnusedGlobalSymbols
function popupOpenUrl(url) {
const w = window.open(""); const w = window.open("");
w.opener = null; w.opener = null;
w.location = url; w.location = url;
} }
function openArticlePopup(id) {
// noinspection JSUnusedGlobalSymbols
function popupOpenArticle(id) {
const w = window.open("", const w = window.open("",
"ttrss_article_popup", "ttrss_article_popup",
"height=900,width=900,resizable=yes,status=no,location=no,menubar=no,directories=no,scrollbars=yes,toolbar=no"); "height=900,width=900,resizable=yes,status=no,location=no,menubar=no,directories=no,scrollbars=yes,toolbar=no");
@ -1476,7 +1465,7 @@ function openArticlePopup(id) {
w.location = "backend.php?op=article&method=view&mode=raw&html=1&zoom=1&id=" + id + "&csrf_token=" + getInitParam("csrf_token"); w.location = "backend.php?op=article&method=view&mode=raw&html=1&zoom=1&id=" + id + "&csrf_token=" + getInitParam("csrf_token");
} }
function keyevent_to_action(e) { function keyeventToAction(e) {
const hotkeys_map = getInitParam("hotkeys"); const hotkeys_map = getInitParam("hotkeys");
const keycode = e.which; const keycode = e.which;
@ -1526,7 +1515,7 @@ function keyevent_to_action(e) {
} }
} }
console.log('keyevent_to_action', hotkey_full, '=>', action_name); console.log('keyeventToAction', hotkey_full, '=>', action_name);
return action_name; return action_name;
} }

View File

@ -647,7 +647,7 @@ function selectTab(id, noupdate) {
function init_second_stage() { function init_second_stage() {
document.onkeydown = pref_hotkey_handler; document.onkeydown = pref_hotkey_handler;
loading_set_progress(50); setLoadingProgress(50);
notify(""); notify("");
let tab = getURLParam('tab'); let tab = getURLParam('tab');
@ -665,7 +665,7 @@ function init_second_stage() {
window.setTimeout(function() { editFeed(param) }, 100); window.setTimeout(function() { editFeed(param) }, 100);
} }
setInterval(hotkey_prefix_timeout, 5*1000); setInterval(hotkeyPrefixTimeout, 5*1000);
} }
function init() { function init() {
@ -716,7 +716,7 @@ function init() {
try { try {
parser.parse(); parser.parse();
loading_set_progress(50); setLoadingProgress(50);
const clientTzOffset = new Date().getTimezoneOffset() * 60; const clientTzOffset = new Date().getTimezoneOffset() * 60;
const params = { op: "rpc", method: "sanityCheck", clientTzOffset: clientTzOffset }; const params = { op: "rpc", method: "sanityCheck", clientTzOffset: clientTzOffset };
@ -750,7 +750,7 @@ function validatePrefsReset() {
function pref_hotkey_handler(e) { function pref_hotkey_handler(e) {
if (e.target.nodeName == "INPUT" || e.target.nodeName == "TEXTAREA") return; if (e.target.nodeName == "INPUT" || e.target.nodeName == "TEXTAREA") return;
const action_name = keyevent_to_action(e); const action_name = keyeventToAction(e);
if (action_name) { if (action_name) {
switch (action_name) { switch (action_name) {
@ -1215,3 +1215,6 @@ function updateSelectedPrompt() {
// no-op shim for toggleSelectedRow() // no-op shim for toggleSelectedRow()
} }
function gotoMain() {
document.location.href = "index.php";
}

View File

@ -94,7 +94,7 @@ function updateFeedList() {
try { try {
feedlist_init(); feedlist_init();
loading_set_progress(25); setLoadingProgress(25);
} catch (e) { } catch (e) {
exception_error(e); exception_error(e);
} }
@ -235,7 +235,7 @@ function init() {
if (!genericSanityCheck()) if (!genericSanityCheck())
return false; return false;
loading_set_progress(30); setLoadingProgress(30);
init_hotkey_actions(); init_hotkey_actions();
const a = document.createElement('audio'); const a = document.createElement('audio');
@ -562,7 +562,7 @@ function init_second_stage() {
setActiveFeedId(hash_feed_id, hash_feed_is_cat); setActiveFeedId(hash_feed_id, hash_feed_is_cat);
} }
loading_set_progress(50); setLoadingProgress(50);
// can't use cache_clear() here because viewfeed might not have initialized yet // can't use cache_clear() here because viewfeed might not have initialized yet
if ('sessionStorage' in window && window['sessionStorage'] !== null) if ('sessionStorage' in window && window['sessionStorage'] !== null)
@ -593,7 +593,7 @@ function quickMenuGo(opid) {
gotoPreferences(); gotoPreferences();
break; break;
case "qmcLogout": case "qmcLogout":
gotoLogout(); document.location.href = "backend.php?op=logout";
break; break;
case "qmcTagCloud": case "qmcTagCloud":
displayDlg(__("Tag cloud"), "printTagCloud"); displayDlg(__("Tag cloud"), "printTagCloud");
@ -738,7 +738,7 @@ function viewModeChanged() {
function hotkey_handler(e) { function hotkey_handler(e) {
if (e.target.nodeName == "INPUT" || e.target.nodeName == "TEXTAREA") return; if (e.target.nodeName == "INPUT" || e.target.nodeName == "TEXTAREA") return;
const action_name = keyevent_to_action(e); const action_name = keyeventToAction(e);
if (action_name) { if (action_name) {
const action_func = hotkey_actions[action_name]; const action_func = hotkey_actions[action_name];
@ -907,8 +907,13 @@ function hash_get(key) {
const kv = window.location.hash.substring(1).toQueryParams(); const kv = window.location.hash.substring(1).toQueryParams();
return kv[key]; return kv[key];
} }
function hash_set(key, value) { function hash_set(key, value) {
const kv = window.location.hash.substring(1).toQueryParams(); const kv = window.location.hash.substring(1).toQueryParams();
kv[key] = value; kv[key] = value;
window.location.hash = $H(kv).toQueryString(); window.location.hash = $H(kv).toQueryString();
} }
function gotoPreferences() {
document.location.href = "prefs.php";
}