Merge branch 'js-objects' into weblate-integration

master
Andrew Dolgov 6 years ago
commit 4cb3a601a0

@ -727,7 +727,7 @@ class Article extends Handler_Protected {
if (!$zoom_mode) {
$rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
<a title=\"".__('Edit tags for this article')."\"
href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
href=\"#\" onclick=\"Article.editTags($id)\">(+)</a>";
$rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
@ -877,7 +877,7 @@ class Article extends Handler_Protected {
$tags_str = "";
for ($i = 0; $i < $maxtags; $i++) {
$tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
$tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"Feeds.open({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
}
$tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
@ -907,8 +907,8 @@ class Article extends Handler_Protected {
static function format_article_note($id, $note, $allow_edit = true) {
$str = "<div class='articleNote' onclick=\"editArticleNote($id)\">
<div class='noteEdit' onclick=\"editArticleNote($id)\">".
$str = "<div class='articleNote' onclick=\"Plugins.Note.edit($id)\">
<div class='noteEdit' onclick=\"Plugins.Note.edit($id)\">".
($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
return $str;

@ -49,10 +49,10 @@ class Dlg extends Handler_Protected {
print "<div align='center'>";
print "<button dojoType=\"dijit.form.Button\" onclick=\"return opmlRegenKey()\">".
print "<button dojoType=\"dijit.form.Button\" onclick=\"return Helpers.OPML.changeKey()\">".
__('Generate new URL')."</button> ";
print "<button dojoType=\"dijit.form.Button\" onclick=\"return closeInfoBox()\">".
print "<button dojoType=\"dijit.form.Button\" onclick=\"return CommonDialogs.closeInfoBox()\">".
__('Close this window')."</button>";
print "</div>";
@ -85,7 +85,7 @@ class Dlg extends Handler_Protected {
print "<div align='center'>";
print "<button onclick=\"return closeInfoBox()\">".
print "<button onclick=\"return CommonDialogs.closeInfoBox()\">".
__('Close this window')."</button>";
print "</div>";
@ -139,7 +139,7 @@ class Dlg extends Handler_Protected {
$key_escaped = str_replace("'", "\\'", $key);
echo "<a href=\"javascript:viewfeed({feed:'$key_escaped'}) \" style=\"font-size: " .
echo "<a href=\"#\" onclick=\"Feeds.open({feed:'$key_escaped'}) \" style=\"font-size: " .
$size . "px\" title=\"$value articles tagged with " .
$key . '">' . $key . '</a> ';
}
@ -150,7 +150,7 @@ class Dlg extends Handler_Protected {
print "<div align='center'>";
print "<button dojoType=\"dijit.form.Button\"
onclick=\"return closeInfoBox()\">".
onclick=\"return CommonDialogs.closeInfoBox()\">".
__('Close this window')."</button>";
print "</div>";
@ -166,7 +166,9 @@ class Dlg extends Handler_Protected {
$url_path = htmlspecialchars($this->params[2]) . "&key=" . $key;
print "<h2>".__("You can view this feed as RSS using the following URL:")."</h2>";
$feed_title = Feeds::getFeedTitle($feed_id, $is_cat);
print "<div>".T_sprintf("%s can be accessed via the following secret URL:", $feed_title)."</div>";
print "<div class=\"tagCloudContainer\">";
print "<a id='gen_feed_url' href='$url_path' target='_blank'>$url_path</a>";
@ -174,10 +176,10 @@ class Dlg extends Handler_Protected {
print "<div align='center'>";
print "<button dojoType=\"dijit.form.Button\" onclick=\"return genUrlChangeKey('$feed_id', '$is_cat')\">".
print "<button dojoType=\"dijit.form.Button\" onclick=\"return CommonDialogs.genUrlChangeKey('$feed_id', '$is_cat')\">".
__('Generate new URL')."</button> ";
print "<button dojoType=\"dijit.form.Button\" onclick=\"return closeInfoBox()\">".
print "<button dojoType=\"dijit.form.Button\" onclick=\"return CommonDialogs.closeInfoBox()\">".
__('Close this window')."</button>";
print "</div>";
@ -190,10 +192,10 @@ class Dlg extends Handler_Protected {
print_warning(__("You are using default tt-rss password. Please change it in the Preferences (Personal data / Authentication)."));
print "<div align='center'>";
print "<button dojoType=\"dijit.form.Button\" onclick=\"gotoPreferences()\">".
print "<button dojoType=\"dijit.form.Button\" onclick=\"document.location.href = 'prefs.php'\">".
__('Open Preferences')."</button> ";
print "<button dojoType=\"dijit.form.Button\"
onclick=\"return closeInfoBox()\">".
onclick=\"return CommonDialogs.closeInfoBox()\">".
__('Close this window')."</button>";
print "</div>";
}

@ -15,21 +15,21 @@ class Feeds extends Handler_Protected {
$feed_id, $is_cat, $search,
$error, $feed_last_updated) {
$catchup_sel_link = "catchupSelection()";
$catchup_sel_link = "Headlines.catchupSelection()";
$archive_sel_link = "archiveSelection()";
$delete_sel_link = "deleteSelection()";
$archive_sel_link = "Headlines.archiveSelection()";
$delete_sel_link = "Headlines.deleteSelection()";
$sel_all_link = "selectArticles('all')";
$sel_unread_link = "selectArticles('unread')";
$sel_none_link = "selectArticles('none')";
$sel_inv_link = "selectArticles('invert')";
$sel_all_link = "Headlines.select('all')";
$sel_unread_link = "Headlines.select('unread')";
$sel_none_link = "Headlines.select('none')";
$sel_inv_link = "Headlines.select('invert')";
$tog_unread_link = "selectionToggleUnread()";
$tog_marked_link = "selectionToggleMarked()";
$tog_published_link = "selectionTogglePublished()";
$tog_unread_link = "Headlines.selectionToggleUnread()";
$tog_marked_link = "Headlines.selectionToggleMarked()";
$tog_published_link = "Headlines.selectionTogglePublished()";
$set_score_link = "setSelectionScore()";
$set_score_link = "Article.selectionSetScore()";
if ($is_cat) $cat_q = "&is_cat=$is_cat";
@ -50,8 +50,8 @@ class Feeds extends Handler_Protected {
$reply .= "<span class='r'>
<a href=\"#\"
title=\"".__("View as RSS feed")."\"
onclick=\"displayDlg('".__("View as RSS")."','generatedFeed', '$feed_id:$is_cat:$rss_link')\">
title=\"".__("Show as feed")."\"
onclick=\"App.displayDlg('".__("Show as feed")."','generatedFeed', '$feed_id:$is_cat:$rss_link')\">
<img class=\"noborder\" src=\"images/pub_set.png\"></a>";
@ -95,7 +95,7 @@ class Feeds extends Handler_Protected {
$reply .= "</span> "; */
$reply .= "<select dojoType=\"dijit.form.Select\"
onchange=\"headlineActionsChange(this)\">";
onchange=\"Headlines.onActionChanged(this)\">";
$reply .= "<option value=\"0\" disabled='1'>".__('Select...')."</option>";
@ -124,12 +124,12 @@ class Feeds extends Handler_Protected {
}
if (PluginHost::getInstance()->get_plugin("mail")) {
$reply .= "<option value=\"emailArticle(false)\">".__('Forward by email').
$reply .= "<option value=\"Plugins.Mail.send()\">".__('Forward by email').
"</option>";
}
if (PluginHost::getInstance()->get_plugin("mailto")) {
$reply .= "<option value=\"mailtoArticle(false)\">".__('Forward by email').
$reply .= "<option value=\"Plugins.Mailto.send()\">".__('Forward by email').
"</option>";
}
@ -137,7 +137,8 @@ class Feeds extends Handler_Protected {
//$reply .= "<option value=\"catchupPage()\">".__('Mark as read')."</option>";
$reply .= "<option value=\"displayDlg('".__("View as RSS")."','generatedFeed', '$feed_id:$is_cat:$rss_link')\">".__('View as RSS')."</option>";
$reply .= "<option value=\"App.displayDlg('".__("Show as feed")."','generatedFeed', '$feed_id:$is_cat:$rss_link')\">".
__('Show as feed')."</option>";
$reply .= "</select>";
@ -299,7 +300,7 @@ class Feeds extends Handler_Protected {
$label_cache = $line["label_cache"];
$labels = false;
$mouseover_attrs = "onmouseover='postMouseIn(event, $id)' onmouseout='postMouseOut($id)'";
$mouseover_attrs = "onmouseover='Article.mouseIn($id)' onmouseout='Article.mouseOut($id)'";
if ($label_cache) {
$label_cache = json_decode($label_cache, true);
@ -331,11 +332,11 @@ class Feeds extends Handler_Protected {
$marked_pic_src = $line["marked"] ? "mark_set.png" : "mark_unset.png";
$class .= $line["marked"] ? " marked" : "";
$marked_pic = "<img src=\"images/$marked_pic_src\" class=\"marked-pic marked-$id\" onclick='toggleMark($id)'>";
$marked_pic = "<img src=\"images/$marked_pic_src\" class=\"marked-pic marked-$id\" onclick='Headlines.toggleMark($id)'>";
$published_pic_src = $line["published"] ? "pub_set.png" : "pub_unset.png";
$class .= $line["published"] ? " published" : "";
$published_pic = "<img src=\"images/$published_pic_src\" class=\"pub-pic pub-$id\" onclick='togglePub($id)'>";
$published_pic = "<img src=\"images/$published_pic_src\" class=\"pub-pic pub-$id\" onclick='Headlines.togglePub($id)'>";
$updated_fmt = make_local_datetime($line["updated"], false, false, false, true);
$date_entered_fmt = T_sprintf("Imported at %s",
@ -345,7 +346,7 @@ class Feeds extends Handler_Protected {
$score_pic = "images/" . get_score_pic($score);
$score_pic = "<img class='score-pic' score='$score' onclick='changeScore($id, this)' src=\"$score_pic\"
$score_pic = "<img class='score-pic' score='$score' onclick='Article.setScore($id, this)' src=\"$score_pic\"
title=\"$score\">";
if ($score > 500) {
@ -388,11 +389,11 @@ class Feeds extends Handler_Protected {
$vgroup_last_feed = $feed_id;
$vf_catchup_link = "<a class='catchup' onclick='catchupFeedInGroup($feed_id);' href='#'>".__('mark feed as read')."</a>";
$vf_catchup_link = "<a class='catchup' onclick='Feeds.catchupFeedInGroup($feed_id);' href='#'>".__('mark feed as read')."</a>";
$reply['content'] .= "<div data-feed-id='$feed_id' class='feed-titl'>".
$reply['content'] .= "<div data-feed-id='$feed_id' class='feed-title'>".
"<div style='float : right'>$feed_icon_img</div>".
"<a class='title' href=\"#\" onclick=\"viewfeed({feed:$feed_id})\">".
"<a class='title' href=\"#\" onclick=\"Feeds.open({feed:$feed_id})\">".
$line["feed_title"]."</a>
$vf_catchup_link</div>";
@ -405,7 +406,7 @@ class Feeds extends Handler_Protected {
$reply['content'] .= "<div class='left'>";
$reply['content'] .= "<input dojoType=\"dijit.form.CheckBox\"
type=\"checkbox\" onclick=\"toggleSelectRow2(this)\"
type=\"checkbox\" onclick=\"Headlines.onRowChecked(this)\"
class='rchk'>";
$reply['content'] .= "$marked_pic";
@ -413,7 +414,7 @@ class Feeds extends Handler_Protected {
$reply['content'] .= "</div>";
$reply['content'] .= "<div onclick='return hlClicked(event, $id)'
$reply['content'] .= "<div onclick='return Headlines.click(event, $id)'
class=\"title\"><span class='hl-content $hlc_suffix'>";
$reply['content'] .= "<a class=\"title $hlc_suffix\"
href=\"" . htmlspecialchars($line["link"]) . "\"
@ -434,7 +435,7 @@ class Feeds extends Handler_Protected {
if (@$line["feed_title"]) {
$rgba = @$rgba_cache[$feed_id];
$reply['content'] .= "<span class=\"feed\"><a style=\"background : rgba($rgba, 0.3)\" href=\"#\" onclick=\"viewfeed({feed:$feed_id})\">".
$reply['content'] .= "<span class=\"feed\"><a style=\"background : rgba($rgba, 0.3)\" href=\"#\" onclick=\"Feeds.open({feed:$feed_id})\">".
truncate_string($line["feed_title"],30)."</a></span>";
}
}
@ -451,7 +452,7 @@ class Feeds extends Handler_Protected {
if ($line["feed_title"] && !$vfeed_group_enabled) {
$reply['content'] .= "<span onclick=\"viewfeed({feed:$feed_id})\"
$reply['content'] .= "<span onclick=\"Feeds.open({feed:$feed_id})\"
style=\"cursor : pointer\"
title=\"".htmlspecialchars($line['feed_title'])."\">
$feed_icon_img</span>";
@ -481,14 +482,14 @@ class Feeds extends Handler_Protected {
$vgroup_last_feed = $feed_id;
$vf_catchup_link = "<a class='catchup' onclick='catchupFeedInGroup($feed_id);' href='#'>".__('mark feed as read')."</a>";
$vf_catchup_link = "<a class='catchup' onclick='Feeds.catchupFeedInGroup($feed_id);' href='#'>".__('mark feed as read')."</a>";
$feed_icon_src = Feeds::getFeedIcon($feed_id);
$feed_icon_img = "<img class=\"tinyFeedIcon\" src=\"$feed_icon_src\">";
$reply['content'] .= "<div data-feed-id='$feed_id' class='feed-title'>".
"<div style=\"float : right\">$feed_icon_img</div>".
"<a href=\"#\" class='title' onclick=\"viewfeed({feed:$feed_id})\">".
"<a href=\"#\" class='title' onclick=\"Feeds.open({feed:$feed_id})\">".
$line["feed_title"]."</a> $vf_catchup_link</div>";
}
@ -504,7 +505,7 @@ class Feeds extends Handler_Protected {
$tmp_content .= "<div style=\"vertical-align : middle\">";
$tmp_content .= "<input dojoType=\"dijit.form.CheckBox\"
type=\"checkbox\" onclick=\"toggleSelectRow2(this, false, true)\"
type=\"checkbox\" onclick=\"Headlines.onRowChecked(this)\"
class='rchk'>";
$tmp_content .= "$marked_pic";
@ -523,7 +524,7 @@ class Feeds extends Handler_Protected {
// data-article-id included for context menu
$tmp_content .= "<span
onclick=\"return cdmClicked(event, $id);\"
onclick=\"return Headlines.click(event, $id);\"
data-article-id=\"$id\"
class=\"titleWrap hlMenuAttach $hlc_suffix\">
<a class=\"title $hlc_suffix\"
@ -535,9 +536,13 @@ class Feeds extends Handler_Protected {
$tmp_content .= $labels_str;
$tmp_content .= "<span class='collapse'>
<img src=\"images/collapse.png\" onclick=\"return cdmCollapseActive(event)\"
title=\"".__("Collapse article")."\"/></span>";
if (!get_pref("CDM_EXPANDED")) {
$tmp_content .= "<span class='collapse'>
<img src=\"images/collapse.png\" onclick=\"return Article.cdmUnsetActive(event)\"
title=\"" . __("Collapse article") . "\"/></span>";
$tmp_content .= "<span class='excerpt'>" . $line["content_preview"] . "</span>";
}
$tmp_content .= "</span>";
@ -547,7 +552,7 @@ class Feeds extends Handler_Protected {
$tmp_content .= "<div class=\"feed\">
<a href=\"#\" style=\"background-color: rgba($rgba,0.3)\"
onclick=\"viewfeed({feed:$feed_id})\">".
onclick=\"Feeds.open({feed:$feed_id})\">".
truncate_string($line["feed_title"],30)."</a>
</div>";
}
@ -561,13 +566,13 @@ class Feeds extends Handler_Protected {
if (!get_pref("VFEED_GROUP_BY_FEED") && $line["feed_title"]) {
$tmp_content .= "<span style=\"cursor : pointer\"
title=\"".htmlspecialchars($line["feed_title"])."\"
onclick=\"viewfeed({feed:$feed_id})\">$feed_icon_img</span>";
onclick=\"Feeds.open({feed:$feed_id})\">$feed_icon_img</span>";
}
$tmp_content .= "</div>"; //score wrapper2
$tmp_content .= "</div>"; //header
$tmp_content .= "<div class=\"content\" onclick=\"return cdmClicked(event, $id, true);\">";
$tmp_content .= "<div class=\"content\" onclick=\"return Headlines.click(event, $id, true);\">";
$tmp_content .= "<div id=\"POSTNOTE-$id\">";
if ($line['note']) {
@ -628,7 +633,7 @@ class Feeds extends Handler_Protected {
$tmp_content .= "<img src='images/tag.png' alt='Tags' title='Tags'>
<span id=\"ATSTR-$id\">$tags_str</span>
<a title=\"".__('Edit tags for this article')."\"
href=\"#\" onclick=\"editArticleTags($id)\">(+)</a>";
href=\"#\" onclick=\"Article.editTags($id)\">(+)</a>";
$num_comments = (int) $line["num_comments"];
$entry_comments = "";
@ -722,7 +727,7 @@ class Feeds extends Handler_Protected {
if ($num_errors > 0) {
$reply['content'] .= "<br/>";
$reply['content'] .= "<a class=\"insensitive\" href=\"#\" onclick=\"showFeedsWithErrors()\">" .
$reply['content'] .= "<a class=\"insensitive\" href=\"#\" onclick=\"CommonDialogs.showFeedsWithErrors()\">" .
__('Some feeds have update errors (click for details)') . "</a>";
}
$reply['content'] .= "</span></p></div>";
@ -913,7 +918,7 @@ class Feeds extends Handler_Protected {
if ($num_errors > 0) {
$reply['headlines']['content'] .= "<br/>";
$reply['headlines']['content'] .= "<a class=\"insensitive\" href=\"#\" onclick=\"showFeedsWithErrors()\">".
$reply['headlines']['content'] .= "<a class=\"insensitive\" href=\"#\" onclick=\"CommonDialogs.showFeedsWithErrors()\">".
__('Some feeds have update errors (click for details)')."</a>";
}
$reply['headlines']['content'] .= "</span></p>";
@ -1014,7 +1019,7 @@ class Feeds extends Handler_Protected {
<button dojoType=\"dijit.form.Button\" class=\"btn-primary\" type=\"submit\" onclick=\"return dijit.byId('feedAddDlg').execute()\">".__('Subscribe')."</button>";
if (!(defined('_DISABLE_FEED_BROWSER') && _DISABLE_FEED_BROWSER)) {
print "<button dojoType=\"dijit.form.Button\" onclick=\"return feedBrowser()\">".__('More feeds')."</button>";
print "<button dojoType=\"dijit.form.Button\" onclick=\"return CommonDialogs.feedBrowser()\">".__('More feeds')."</button>";
}
print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('feedAddDlg').hide()\">".__('Cancel')."</button>

@ -8,11 +8,8 @@ class Opml extends Handler_Protected {
}
function export() {
$output_name = $_REQUEST["filename"];
if (!$output_name) $output_name = "TinyTinyRSS.opml";
$show_settings = $_REQUEST["settings"];
$output_name = "tt-rss_".date("Y-m-d").".opml";
$show_settings = $_REQUEST["include_settings"];
$owner_uid = $_SESSION["uid"];
$rc = $this->opml_export($output_name, $owner_uid, false, ($show_settings == 1));

@ -492,7 +492,7 @@ class Pref_Feeds extends Handler_Protected {
@unlink($icon_file);
print "<script type=\"text/javascript\">";
print "parent.uploadIconHandler($rc);";
print "parent.CommonDialogs.uploadIconHandler($rc);";
print "</script>";
return;
}
@ -745,9 +745,9 @@ class Pref_Feeds extends Handler_Protected {
<input type=\"hidden\" name=\"op\" value=\"pref-feeds\">
<input type=\"hidden\" name=\"feed_id\" value=\"$feed_id\">
<input type=\"hidden\" name=\"method\" value=\"uploadicon\">
<button class=\"\" dojoType=\"dijit.form.Button\" onclick=\"return uploadFeedIcon();\"
<button class=\"\" dojoType=\"dijit.form.Button\" onclick=\"return CommonDialogs.uploadFeedIcon();\"
type=\"submit\">".__('Replace')."</button>
<button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick=\"return removeFeedIcon($feed_id);\"
<button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick=\"return CommonDialogs.removeFeedIcon($feed_id);\"
type=\"submit\">".__('Remove')."</button>
</form>";
@ -765,7 +765,7 @@ class Pref_Feeds extends Handler_Protected {
print "<div class='dlgButtons'>
<div style=\"float : left\">
<button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick='return unsubscribeFeed($feed_id, \"$title\")'>".
<button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick='return CommonDialogs.unsubscribeFeed($feed_id, \"$title\")'>".
__('Unsubscribe')."</button>";
print "</div>";
@ -1149,14 +1149,14 @@ class Pref_Feeds extends Handler_Protected {
if ($num_errors > 0) {
$error_button = "<button dojoType=\"dijit.form.Button\"
onclick=\"showFeedsWithErrors()\" id=\"errorButton\">" .
onclick=\"CommonDialogs.showFeedsWithErrors()\" id=\"errorButton\">" .
__("Feeds with errors") . "</button>";
}
$inactive_button = "<button dojoType=\"dijit.form.Button\"
id=\"pref_feeds_inactive_btn\"
style=\"display : none\"
onclick=\"showInactiveFeeds()\">" .
onclick=\"dijit.byId('feedTree').showInactiveFeeds()\">" .
__("Inactive feeds") . "</button>";
$feed_search = clean($_REQUEST["search"]);
@ -1174,7 +1174,7 @@ class Pref_Feeds extends Handler_Protected {
print "<div style='float : right; padding-right : 4px;'>
<input dojoType=\"dijit.form.TextBox\" id=\"feed_search\" size=\"20\" type=\"search\"
value=\"$feed_search\">
<button dojoType=\"dijit.form.Button\" onclick=\"updateFeedList()\">".
<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('feedTree').reload()\">".
__('Search')."</button>
</div>";
@ -1190,15 +1190,15 @@ class Pref_Feeds extends Handler_Protected {
print "<div dojoType=\"dijit.form.DropDownButton\">".
"<span>" . __('Feeds')."</span>";
print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
print "<div onclick=\"quickAddFeed()\"
print "<div onclick=\"CommonDialogs.quickAddFeed()\"
dojoType=\"dijit.MenuItem\">".__('Subscribe to feed')."</div>";
print "<div onclick=\"editSelectedFeed()\"
print "<div onclick=\"dijit.byId('feedTree').editSelectedFeed()\"
dojoType=\"dijit.MenuItem\">".__('Edit selected feeds')."</div>";
print "<div onclick=\"resetFeedOrder()\"
print "<div onclick=\"dijit.byId('feedTree').resetFeedOrder()\"
dojoType=\"dijit.MenuItem\">".__('Reset sort order')."</div>";
print "<div onclick=\"batchSubscribe()\"
print "<div onclick=\"dijit.byId('feedTree').batchSubscribe()\"
dojoType=\"dijit.MenuItem\">".__('Batch subscribe')."</div>";
print "<div dojoType=\"dijit.MenuItem\" onclick=\"removeSelectedFeeds()\">"
print "<div dojoType=\"dijit.MenuItem\" onclick=\"dijit.byId('feedTree').removeSelectedFeeds()\">"
.__('Unsubscribe')."</div> ";
print "</div></div>";
@ -1206,11 +1206,11 @@ class Pref_Feeds extends Handler_Protected {
print "<div dojoType=\"dijit.form.DropDownButton\">".
"<span>" . __('Categories')."</span>";
print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
print "<div onclick=\"createCategory()\"
print "<div onclick=\"dijit.byId('feedTree').createCategory()\"
dojoType=\"dijit.MenuItem\">".__('Add category')."</div>";
print "<div onclick=\"resetCatOrder()\"
print "<div onclick=\"dijit.byId('feedTree').resetCatOrder()\"
dojoType=\"dijit.MenuItem\">".__('Reset sort order')."</div>";
print "<div onclick=\"removeSelectedCategories()\"
print "<div onclick=\"dijit.byId('feedTree').removeSelectedCategories()\"
dojoType=\"dijit.MenuItem\">".__('Remove selected')."</div>";
print "</div></div>";
@ -1247,15 +1247,15 @@ class Pref_Feeds extends Handler_Protected {
var bare_id = id.substr(id.indexOf(':')+1);
if (id.match('FEED:')) {
editFeed(bare_id);
CommonDialogs.editFeed(bare_id);
} else if (id.match('CAT:')) {
editCat(bare_id, item);
dijit.byId('feedTree').editCategory(bare_id, item);
}
</script>
<script type=\"dojo/method\" event=\"onLoad\" args=\"item\">
Element.hide(\"feedlistLoading\");
checkInactiveFeeds();
dijit.byId('feedTree').checkInactiveFeeds();
</script>
</div>";
@ -1270,11 +1270,11 @@ class Pref_Feeds extends Handler_Protected {
print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('OPML')."\">";
print "<p>" . __("Using OPML you can export and import your feeds, filters, labels and Tiny Tiny RSS settings.") .
__("Only main settings profile can be migrated using OPML.") . "</p>";
print_notice(__("Using OPML you can export and import your feeds, filters, labels and Tiny Tiny RSS settings.") .
__("Only main settings profile can be migrated using OPML."));
print "<iframe id=\"upload_iframe\"
name=\"upload_iframe\" onload=\"opmlImportComplete(this)\"
name=\"upload_iframe\" onload=\"Helpers.OPML.onImportComplete(this)\"
style=\"width: 400px; height: 100px; display: none;\"></iframe>";
print "<form name=\"opml_form\" style='display : block' target=\"upload_iframe\"
@ -1285,28 +1285,33 @@ class Pref_Feeds extends Handler_Protected {
</label>
<input type=\"hidden\" name=\"op\" value=\"dlg\">
<input type=\"hidden\" name=\"method\" value=\"importOpml\">
<button dojoType=\"dijit.form.Button\" onclick=\"return opmlImport();\" type=\"submit\">" .
__('Import my OPML') . "</button>";
<button dojoType=\"dijit.form.Button\" onclick=\"return Helpers.OPML.import();\" type=\"submit\">" .
__('Import OPML') . "</button>";
print "<hr>";
print "</form>";
print "<p/>";
$opml_export_filename = "TinyTinyRSS_".date("Y-m-d").".opml";
print "<form dojoType=\"dijit.form.Form\" id=\"opmlExportForm\">";
print "<p>" . __('Filename:') .
" <input class=\"input input-text\" type=\"text\" id=\"filename\" value=\"$opml_export_filename\" />&nbsp;" .
__('Include settings') . "<input type=\"checkbox\" id=\"settings\" checked=\"1\"/>";
print "<button dojoType=\"dijit.form.Button\"
onclick=\"Helpers.OPML.export()\" >" .
__('Export OPML') . "</button>";
print "</p><button dojoType=\"dijit.form.Button\"
onclick=\"gotoExportOpml(document.opml_form.filename.value, document.opml_form.settings.checked)\" >" .
__('Export OPML') . "</button></p></form>";
print "<label>";
print_checkbox("include_settings", true, "1", "");
print "&nbsp;" . __("Include settings");
print "</label>";
print "</form>";
print "<hr>";
print "<p>" . __('Your OPML can be published publicly and can be subscribed by anyone who knows the URL below.') . "</p>";
print_notice(__('Your OPML can be published publicly and can be subscribed by anyone who knows the URL below.'));
print_warning("Published OPML does not include your Tiny Tiny RSS settings, feeds that require authentication or feeds hidden from Popular feeds.");
print "<button dojoType=\"dijit.form.Button\" onclick=\"return displayDlg('".__("Public OPML URL")."','pubOPMLUrl')\">".
print "<button dojoType=\"dijit.form.Button\" onclick=\"return App.displayDlg('".__("Public OPML URL")."','pubOPMLUrl')\">".
__('Display published OPML URL')."</button> ";
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION,
@ -1323,10 +1328,10 @@ class Pref_Feeds extends Handler_Protected {
print "<p>";
print "<button dojoType=\"dijit.form.Button\" onclick=\"return displayDlg('".__("View as RSS")."','generatedFeed', '$rss_url')\">".
print "<button dojoType=\"dijit.form.Button\" onclick=\"return App.displayDlg('".__("Show as feed")."','generatedFeed', '$rss_url')\">".
__('Display URL')."</button> ";
print "<button class=\"warning\" dojoType=\"dijit.form.Button\" onclick=\"return clearFeedAccessKeys()\">".
print "<button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick=\"return Helpers.clearFeedAccessKeys()\">".
__('Clear all generated URLs')."</button> ";
print "</p>";
@ -1412,9 +1417,9 @@ class Pref_Feeds extends Handler_Protected {
print "<div dojoType=\"dijit.form.DropDownButton\">".
"<span>" . __('Select')."</span>";
print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
print "<div onclick=\"selectTableRows('prefInactiveFeedList', 'all')\"
print "<div onclick=\"Tables.select('prefInactiveFeedList', true)\"
dojoType=\"dijit.MenuItem\">".__('All')."</div>";
print "<div onclick=\"selectTableRows('prefInactiveFeedList', 'none')\"
print "<div onclick=\"Tables.select('prefInactiveFeedList', false)\"
dojoType=\"dijit.MenuItem\">".__('None')."</div>";
print "</div></div>";
print "</div>"; #toolbar
@ -1428,20 +1433,17 @@ class Pref_Feeds extends Handler_Protected {
while ($line = $sth->fetch()) {
$feed_id = $line["id"];
$this_row_id = "id=\"FUPDD-$feed_id\"";
# class needed for selectTableRows()
print "<tr class=\"placeholder\" $this_row_id>";
print "<tr class=\"placeholder\" data-row-id='$feed_id'>";
# id needed for selectTableRows()
print "<td width='5%' align='center'><input
onclick='toggleSelectRow2(this);' dojoType=\"dijit.form.CheckBox\"
type=\"checkbox\" id=\"FUPDC-$feed_id\"></td>";
onclick='Tables.onRowChecked(this);' dojoType=\"dijit.form.CheckBox\"
type=\"checkbox\"></td>";
print "<td>";
print "<a class=\"visibleLink\" href=\"#\" ".
"title=\"".__("Click to edit feed")."\" ".
"onclick=\"editFeed(".$line["id"].")\">".
"onclick=\"CommonDialogs.editFeed(".$line["id"].")\">".
htmlspecialchars($line["title"])."</a>";
print "</td><td class=\"insensitive\" align='right'>";
@ -1477,9 +1479,9 @@ class Pref_Feeds extends Handler_Protected {
print "<div dojoType=\"dijit.form.DropDownButton\">".
"<span>" . __('Select')."</span>";
print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
print "<div onclick=\"selectTableRows('prefErrorFeedList', 'all')\"
print "<div onclick=\"Tables.select('prefErrorFeedList', true)\"
dojoType=\"dijit.MenuItem\">".__('All')."</div>";
print "<div onclick=\"selectTableRows('prefErrorFeedList', 'none')\"
print "<div onclick=\"Tables.select('prefErrorFeedList', false)\"
dojoType=\"dijit.MenuItem\">".__('None')."</div>";
print "</div></div>";
print "</div>"; #toolbar
@ -1493,20 +1495,17 @@ class Pref_Feeds extends Handler_Protected {
while ($line = $sth->fetch()) {
$feed_id = $line["id"];
$this_row_id = "id=\"FERDD-$feed_id\"";
# class needed for selectTableRows()
print "<tr class=\"placeholder\" $this_row_id>";
print "<tr class=\"placeholder\" data-row-id='$feed_id'>";
# id needed for selectTableRows()
print "<td width='5%' align='center'><input
onclick='toggleSelectRow2(this);' dojoType=\"dijit.form.CheckBox\"
type=\"checkbox\" id=\"FERDC-$feed_id\"></td>";
onclick='Tables.onRowChecked(this);' dojoType=\"dijit.form.CheckBox\"
type=\"checkbox\"></td>";
print "<td>";
print "<a class=\"visibleLink\" href=\"#\" ".
"title=\"".__("Click to edit feed")."\" ".
"onclick=\"editFeed(".$line["id"].")\">".
"onclick=\"CommonDialogs.editFeed(".$line["id"].")\">".
htmlspecialchars($line["title"])."</a>: ";
print "<span class=\"insensitive\">";

@ -429,7 +429,7 @@ class Pref_Filters extends Handler_Protected {
$data = htmlspecialchars(json_encode($line));
print "<li><input dojoType='dijit.form.CheckBox' type='checkbox' onclick='toggleSelectListRow2(this)'>".
print "<li><input dojoType='dijit.form.CheckBox' type='checkbox' onclick='Lists.onRowChecked(this)'>".
"<span onclick=\"dijit.byId('filterEditDlg').editRule(this)\">".$this->getRuleName($line)."</span>".
"<input type='hidden' name='rule[]' value=\"$data\"/></li>";
}
@ -473,7 +473,7 @@ class Pref_Filters extends Handler_Protected {
$data = htmlspecialchars(json_encode($line));
print "<li><input dojoType='dijit.form.CheckBox' type='checkbox' onclick='toggleSelectListRow2(this)'>".
print "<li><input dojoType='dijit.form.CheckBox' type='checkbox' onclick='Lists.onRowChecked(this)'>".
"<span onclick=\"dijit.byId('filterEditDlg').editAction(this)\">".$this->getActionName($line)."</span>".
"<input type='hidden' name='action[]' value=\"$data\"/></li>";
}
@ -797,20 +797,20 @@ class Pref_Filters extends Handler_Protected {
dojoType=\"dijit.MenuItem\">".__('None')."</div>";
print "</div></div>";
print "<button dojoType=\"dijit.form.Button\" onclick=\"return quickAddFilter()\">".
print "<button dojoType=\"dijit.form.Button\" onclick=\"return Filters.quickAddFilter()\">".
__('Create filter')."</button> ";
print "<button dojoType=\"dijit.form.Button\" onclick=\"return joinSelectedFilters()\">".
print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterTree').joinSelectedFilters()\">".
__('Combine')."</button> ";
print "<button dojoType=\"dijit.form.Button\" onclick=\"return editSelectedFilter()\">".
print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterTree').editSelectedFilter()\">".
__('Edit')."</button> ";
print "<button dojoType=\"dijit.form.Button\" onclick=\"return resetFilterOrder()\">".
print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterTree').resetFilterOrder()\">".
__('Reset sort order')."</button> ";
print "<button dojoType=\"dijit.form.Button\" onclick=\"return removeSelectedFilters()\">".
print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterTree').removeSelectedFilters()\">".
__('Remove')."</button> ";
print "</div>"; # toolbar
@ -840,7 +840,7 @@ class Pref_Filters extends Handler_Protected {
var bare_id = id.substr(id.indexOf(':')+1);
if (id.match('FILTER:')) {
editFilter(bare_id);
dijit.byId('filterTree').editFilter(bare_id);
}
</script>
@ -1044,7 +1044,7 @@ class Pref_Filters extends Handler_Protected {
print "<div class=\"dlgSecCont\">";
print "<select name=\"action_id\" dojoType=\"dijit.form.Select\"
onchange=\"filterDlgCheckAction(this)\">";
onchange=\"Filters.filterDlgCheckAction(this)\">";
$res = $this->pdo->query("SELECT id,description FROM ttrss_filter_actions
ORDER BY name");

@ -275,13 +275,13 @@ class Pref_Labels extends Handler_Protected {
dojoType=\"dijit.MenuItem\">".__('None')."</div>";
print "</div></div>";
print"<button dojoType=\"dijit.form.Button\" onclick=\"return addLabel()\">".
print"<button dojoType=\"dijit.form.Button\" onclick=\"CommonDialogs.addLabel()\">".
__('Create label')."</button dojoType=\"dijit.form.Button\"> ";
print "<button dojoType=\"dijit.form.Button\" onclick=\"removeSelectedLabels()\">".
print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('labelTree').removeSelected()\">".
__('Remove')."</button dojoType=\"dijit.form.Button\"> ";
print "<button dojoType=\"dijit.form.Button\" onclick=\"labelColorReset()\">".
print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('labelTree').resetColors()\">".
__('Clear colors')."</button dojoType=\"dijit.form.Button\">";
@ -310,7 +310,7 @@ class Pref_Labels extends Handler_Protected {
var bare_id = id.substr(id.indexOf(':')+1);
if (id.match('LABEL:')) {
editLabel(bare_id);
dijit.byId('labelTree').editLabel(bare_id);
}
</script>
</div>";
@ -323,4 +323,4 @@ class Pref_Labels extends Handler_Protected {
print "</div>"; #container
}
}
}

@ -183,7 +183,7 @@ class Pref_Prefs extends Handler_Protected {
print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\">
evt.preventDefault();
if (this.validate()) {
notify_progress('Saving data...', true);
Notify.progress('Saving data...', true);
new Ajax.Request('backend.php', {
parameters: dojo.objectToQuery(this.getValues()),
@ -249,12 +249,12 @@ class Pref_Prefs extends Handler_Protected {
print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\">
evt.preventDefault();
if (this.validate()) {
notify_progress('Changing password...', true);
Notify.progress('Changing password...', true);
new Ajax.Request('backend.php', {
parameters: dojo.objectToQuery(this.getValues()),
onComplete: function(transport) {
notify('');
Notify.close();
if (transport.responseText.indexOf('ERROR: ') == 0) {
$('pwd_change_infobox').innerHTML =
@ -316,14 +316,14 @@ class Pref_Prefs extends Handler_Protected {
print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\">
evt.preventDefault();
if (this.validate()) {
notify_progress('Disabling OTP', true);
Notify.progress('Disabling OTP', true);
new Ajax.Request('backend.php', {
parameters: dojo.objectToQuery(this.getValues()),
onComplete: function(transport) {
notify('');
Notify.close();
if (transport.responseText.indexOf('ERROR: ') == 0) {
notify_error(transport.responseText.replace('ERROR: ', ''));
Notify.error(transport.responseText.replace('ERROR: ', ''));
} else {
window.location.reload();
}
@ -367,14 +367,14 @@ class Pref_Prefs extends Handler_Protected {
print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\">
evt.preventDefault();
if (this.validate()) {
notify_progress('Saving data...', true);
Notify.progress('Saving data...', true);
new Ajax.Request('backend.php', {
parameters: dojo.objectToQuery(this.getValues()),
onComplete: function(transport) {
notify('');
Notify.close();
if (transport.responseText.indexOf('ERROR:') == 0) {
notify_error(transport.responseText.replace('ERROR:', ''));
Notify.error(transport.responseText.replace('ERROR:', ''));
} else {
window.location.reload();
}
@ -436,12 +436,12 @@ class Pref_Prefs extends Handler_Protected {
onComplete: function(transport) {
var msg = transport.responseText;
if (quit) {
gotoMain();
document.location.href = 'index.php';
} else {
if (msg == 'PREFS_NEED_RELOAD') {
window.location.reload();
} else {
notify_info(msg);
Notify.info(msg);
}
}
} });
@ -542,7 +542,7 @@ class Pref_Prefs extends Handler_Protected {
} else if ($pref_name == "USER_STYLESHEET") {
print "<button dojoType=\"dijit.form.Button\"
onclick=\"customizeCSS()\">" . __('Customize') . "</button>";
onclick=\"Helpers.customizeCSS()\">" . __('Customize') . "</button>";
} else if ($pref_name == "USER_CSS_THEME") {
@ -608,11 +608,11 @@ class Pref_Prefs extends Handler_Protected {
print "<br/>";
print " <button dojoType=\"dijit.form.Button\" disabled=\"$has_serial\"
onclick=\"insertSSLserial('$cert_serial')\">" .
onclick=\"dijit.byId('SSL_CERT_SERIAL').attr('value', '$cert_serial')\">" .
__('Register') . "</button>";
print " <button dojoType=\"dijit.form.Button\"
onclick=\"insertSSLserial('')\">" .
onclick=\"dijit.byId('SSL_CERT_SERIAL').attr('value', '')\">" .
__('Clear') . "</button>";
} else if ($pref_name == 'DIGEST_PREFERRED_TIME') {
@ -659,10 +659,10 @@ class Pref_Prefs extends Handler_Protected {
</div>
</div>";
print "<button dojoType=\"dijit.form.Button\" onclick=\"return editProfiles()\">".
print "<button dojoType=\"dijit.form.Button\" onclick=\"return Helpers.editProfiles()\">".
__('Manage profiles')."</button> ";
print "<button dojoType=\"dijit.form.Button\" class=\"btn-danger\" onclick=\"return validatePrefsReset()\">".
print "<button dojoType=\"dijit.form.Button\" class=\"btn-danger\" onclick=\"return Helpers.confirmReset()\">".
__('Reset to defaults')."</button>";
print "&nbsp;";
@ -689,12 +689,12 @@ class Pref_Prefs extends Handler_Protected {
print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\">
evt.preventDefault();
if (this.validate()) {
notify_progress('Saving data...', true);
Notify.progress('Saving data...', true);
new Ajax.Request('backend.php', {
parameters: dojo.objectToQuery(this.getValues()),
onComplete: function(transport) {
notify('');
Notify.close();
if (confirm(__('Selected plugins have been enabled. Reload?'))) {
window.location.reload();
}
@ -756,7 +756,7 @@ class Pref_Prefs extends Handler_Protected {
if (count($tmppluginhost->get_all($plugin)) > 0) {
if (in_array($name, $system_enabled)) {
print "<td><a href='#' onclick=\"clearPluginData('$name')\"
print "<td><a href='#' onclick=\"Helpers.clearPluginData('$name')\"
class='visibleLink'>".__("Clear data")."</a></td>";
}
}
@ -799,7 +799,7 @@ class Pref_Prefs extends Handler_Protected {
$plugin_icon = $checked ? "plugin.png" : "plugin_disabled.png";
print "<td align='center'><input id='FPCHK-$name' name='plugins[]' value='$name' onclick='toggleSelectRow2(this);'
print "<td align='center'><input id='FPCHK-$name' name='plugins[]' value='$name' onclick='Tables.onRowChecked(this);'
dojoType=\"dijit.form.CheckBox\" $checked $disabled
type=\"checkbox\"></td>";
@ -816,7 +816,7 @@ class Pref_Prefs extends Handler_Protected {
if (count($tmppluginhost->get_all($plugin)) > 0) {
if (in_array($name, $system_enabled) || in_array($name, $user_enabled)) {
print "<td><a href='#' onclick=\"clearPluginData('$name')\" class='visibleLink'>".__("Clear data")."</a></td>";
print "<td><a href='#' onclick=\"Helpers.clearPluginData('$name')\" class='visibleLink'>".__("Clear data")."</a></td>";
}
}
@ -992,9 +992,9 @@ class Pref_Prefs extends Handler_Protected {
print "<div dojoType=\"dijit.form.DropDownButton\">".
"<span>" . __('Select')."</span>";
print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
print "<div onclick=\"selectTableRows('prefFeedProfileList', 'all')\"
print "<div onclick=\"Tables.select('prefFeedProfileList', true)\"
dojoType=\"dijit.MenuItem\">".__('All')."</div>";
print "<div onclick=\"selectTableRows('prefFeedProfileList', 'none')\"
print "<div onclick=\"Tables.select('prefFeedProfileList', false)\"
dojoType=\"dijit.MenuItem\">".__('None')."</div>";
print "</div></div>";
@ -1019,11 +1019,10 @@ class Pref_Prefs extends Handler_Protected {
print "<table width=\"100%\" class=\"prefFeedProfileList\"
cellspacing=\"0\" id=\"prefFeedProfileList\">";
print "<tr class=\"placeholder\" id=\"FCATR-0\">"; #odd
print "<tr class=\"placeholder\">"; # data-row-id='0' <-- no point, shouldn't be removed
print "<td width='5%' align='center'><input
id='FCATC-0'
onclick='toggleSelectRow2(this);'
onclick='Tables.onRowChecked(this);'
dojoType=\"dijit.form.CheckBox\"
type=\"checkbox\"></td>";
@ -1043,15 +1042,13 @@ class Pref_Prefs extends Handler_Protected {
while ($line = $sth->fetch()) {
$profile_id = $line["id"];
$this_row_id = "id=\"FCATR-$profile_id\"";
print "<tr class=\"placeholder\" $this_row_id>";
print "<tr class=\"placeholder\" data-row-id='$profile_id'>";
$edit_title = htmlspecialchars($line["title"]);
print "<td width='5%' align='center'><input
onclick='toggleSelectRow2(this);'
id='FCATC-$profile_id'
onclick='Tables.onRowChecked(this);'
dojoType=\"dijit.form.CheckBox\"
type=\"checkbox\"></td>";

@ -26,7 +26,7 @@ class Pref_System extends Handler_Protected {
function index() {
print "<div dojoType=\"dijit.layout.AccordionContainer\" region=\"center\">";
print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Error Log')."\">";
print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Event Log')."\">";
if (LOG_DESTINATION == "sql") {
@ -37,10 +37,10 @@ class Pref_System extends Handler_Protected {
LIMIT 100");
print "<button dojoType=\"dijit.form.Button\"
onclick=\"updateSystemList()\">".__('Refresh')."</button> ";
onclick=\"Helpers.updateEventLog()\">".__('Refresh')."</button> ";
print "&nbsp;<button dojoType=\"dijit.form.Button\"
onclick=\"clearSqlLog()\">".__('Clear log')."</button> ";
class=\"btn-danger\" onclick=\"Helpers.clearEventLog()\">".__('Clear')."</button> ";
print "<p><table width=\"100%\" cellspacing=\"10\" class=\"prefErrorLog\">";

@ -218,12 +218,13 @@ class Pref_Users extends Handler_Protected {
}
function add() {
$login = trim(clean($_REQUEST["login"]));
$tmp_user_pwd = make_password(8);
$salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
$pwd_hash = encrypt_password($tmp_user_pwd, $salt, true);
if (!$login) return; // no blank usernames
$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE
login = ?");
$sth->execute([$login]);
@ -243,18 +244,18 @@ class Pref_Users extends Handler_Protected {
$new_uid = $row['id'];
print format_notice(T_sprintf("Added user <b>%s</b> with password <b>%s</b>",
$login, $tmp_user_pwd));
print T_sprintf("Added user %s with password %s",
$login, $tmp_user_pwd);
initialize_user($new_uid);
} else {
print format_warning(T_sprintf("Could not create user <b>%s</b>", $login));
print T_sprintf("Could not create user %s", $login);
}
} else {
print format_warning(T_sprintf("User <b>%s</b> already exists.", $login));
print T_sprintf("User %s already exists.", $login);
}
}
@ -282,9 +283,9 @@ class Pref_Users extends Handler_Protected {
$sth->execute([$pwd_hash, $new_salt, $uid]);
if ($show_password) {
print T_sprintf("Changed password of user <b>%s</b> to <b>%s</b>", $login, $tmp_user_pwd);
print T_sprintf("Changed password of user %s to %s", $login, $tmp_user_pwd);
} else {
print_notice(T_sprintf("Sending new password of user <b>%s</b> to <b>%s</b>", $login, $email));
print_notice(T_sprintf("Sending new password of user %s to %s", $login, $email));
}
if ($email) {
@ -341,7 +342,7 @@ class Pref_Users extends Handler_Protected {
print "<div style='float : right; padding-right : 4px;'>
<input dojoType=\"dijit.form.TextBox\" id=\"user_search\" size=\"20\" type=\"search\"
value=\"$user_search\">
<button dojoType=\"dijit.form.Button\" onclick=\"updateUsersList()\">".
<button dojoType=\"dijit.form.Button\" oncl1ick=\"Users.reload()\">".
__('Search')."</button>
</div>";
@ -354,20 +355,20 @@ class Pref_Users extends Handler_Protected {
print "<div dojoType=\"dijit.form.DropDownButton\">".
"<span>" . __('Select')."</span>";
print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
print "<div onclick=\"selectTableRows('prefUserList', 'all')\"
print "<div onclick=\"Tables.select('prefUserList', true)\"
dojoType=\"dijit.MenuItem\">".__('All')."</div>";
print "<div onclick=\"selectTableRows('prefUserList', 'none')\"
print "<div onclick=\"Tables.select('prefUserList', false)\"
dojoType=\"dijit.MenuItem\">".__('None')."</div>";
print "</div></div>";
print "<button dojoType=\"dijit.form.Button\" onclick=\"addUser()\">".__('Create user')."</button>";
print "<button dojoType=\"dijit.form.Button\" onclick=\"Users.add()\">".__('Create user')."</button>";
print "
<button dojoType=\"dijit.form.Button\" onclick=\"editSelectedUser()\">".
<button dojoType=\"dijit.form.Button\" onclick=\"Users.editSelected()\">".
__('Edit')."</button dojoType=\"dijit.form.Button\">
<button dojoType=\"dijit.form.Button\" onclick=\"removeSelectedUsers()\">".
<button dojoType=\"dijit.form.Button\" onclick=\"Users.removeSelected()\">".
__('Remove')."</button dojoType=\"dijit.form.Button\">
<button dojoType=\"dijit.form.Button\" onclick=\"resetSelectedUserPass()\">".
<button dojoType=\"dijit.form.Button\" onclick=\"Users.resetSelected()\">".
__('Reset password')."</button dojoType=\"dijit.form.Button\">";
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION,
@ -400,11 +401,11 @@ class Pref_Users extends Handler_Protected {
print "<tr class=\"title\">
<td align='center' width=\"5%\">&nbsp;</td>
<td width='20%'><a href=\"#\" onclick=\"updateUsersList('login')\">".__('Login')."</a></td>
<td width='20%'><a href=\"#\" onclick=\"updateUsersList('access_level')\">".__('Access Level')."</a></td>
<td width='10%'><a href=\"#\" onclick=\"updateUsersList('num_feeds')\">".__('Subscribed feeds')."</a></td>
<td width='20%'><a href=\"#\" onclick=\"updateUsersList('created')\">".__('Registered')."</a></td>
<td width='20%'><a href=\"#\" onclick=\"updateUsersList('last_login')\">".__('Last login')."</a></td></tr>";
<td width='20%'><a href=\"#\" onclick=\"Users.reload('login')\">".__('Login')."</a></td>
<td width='20%'><a href=\"#\" onclick=\"Users.reload('access_level')\">".__('Access Level')."</a></td>
<td width='10%'><a href=\"#\" onclick=\"Users.reload('num_feeds')\">".__('Subscribed feeds')."</a></td>
<td width='20%'><a href=\"#\" onclick=\"Users.reload('created')\">".__('Registered')."</a></td>
<td width='20%'><a href=\"#\" onclick=\"Users.reload('last_login')\">".__('Last login')."</a></td></tr>";
$lnum = 0;
@ -412,27 +413,21 @@ class Pref_Users extends Handler_Protected {
$uid = $line["id"];
print "<tr id=\"UMRR-$uid\">";
print "<tr data-row-id=\"$uid\" onclick='Users.edit($uid)'>";
$line["login"] = htmlspecialchars($line["login"]);
$line["created"] = make_local_datetime($line["created"], false);
$line["last_login"] = make_local_datetime($line["last_login"], false);
print "<td align='center'><input onclick='toggleSelectRow2(this);'
dojoType=\"dijit.form.CheckBox\" type=\"checkbox\"
id=\"UMCHK-$uid\"></td>";
$onclick = "onclick='editUser($uid, event)' title='".__('Click to edit')."'";
print "<td $onclick><img src='images/user.png' class='marked-pic' alt=''> " . $line["login"] . "</td>";
print "<td align='center'><input onclick='Tables.onRowChecked(this); event.stopPropagation();'
dojoType=\"dijit.form.CheckBox\" type=\"checkbox\"></td>";
if (!$line["email"]) $line["email"] = "&nbsp;";
print "<td title='".__('Click to edit')."'><img src='images/user.png' class='marked-pic' alt=''> " . $line["login"] . "</td>";
print "<td $onclick>" . $access_level_names[$line["access_level"]] . "</td>";
print "<td $onclick>" . $line["num_feeds"] . "</td>";
print "<td $onclick>" . $line["created"] . "</td>";
print "<td $onclick>" . $line["last_login"] . "</td>";
print "<td>" . $access_level_names[$line["access_level"]] . "</td>";
print "<td>" . $line["num_feeds"] . "</td>";
print "<td>" . $line["created"] . "</td>";
print "<td>" . $line["last_login"] . "</td>";
print "</tr>";

@ -103,7 +103,7 @@
margin-top : 4px;
margin-bottom : 4px;
.collapse {
.collapse, .excerpt {
display : none;
}
@ -290,7 +290,7 @@ div#floatingTitle {
color : @default-text;
}
.collapse {
.collapse, .excerpt {
display : none;
}
@ -373,6 +373,15 @@ div#floatingTitle.Unread a.title {
overflow : hidden;
max-width : 500px;
}
.excerpt {
white-space : nowrap;
font-size : 11px;
color : #999;
font-weight : normal;
cursor : pointer;
}
}
.cdm.expandable.Unread {
@ -410,12 +419,18 @@ div.cdm.expandable.Unread div.header a.title {
color : black;
}
div.cdm.expandable.active div.header a.title {
color: @color-link;
font-size: 16px;
font-weight: 600;
text-rendering: optimizelegibility;
font-family: @fonts-ui-bold;
div.cdm.expandable.active {
.excerpt {
display: none;
}
div.header a.title {
color: @color-link;
font-size: 16px;
font-weight: 600;
text-rendering: optimizelegibility;
font-family: @fonts-ui-bold;
}
}
div.cdm.expandable:not(.active) {
@ -424,4 +439,4 @@ div.cdm.expandable:not(.active) {
.content, .collapse {
display : none;
}
}
}

File diff suppressed because one or more lines are too long

@ -910,6 +910,25 @@ body.ttrss_main {
-webkit-overflow-scrolling : touch;
-webkit-transform: translateZ(0);
-webkit-backface-visibility: hidden;
div.feed-title {
border: 0px solid @color-link;
border-bottom-width: 1px;
padding: 5px 3px 5px 5px;
}
div.feed-title a.title {
color: @default-text;
font-weight: bold;
}
div.feed-title a {
color: @default-text;
}
div.feed-title a:hover {
color: @color-link;
}
}
#headlines-toolbar_splitter, #toolbar_splitter {

@ -53,12 +53,10 @@
$site_url = htmlspecialchars($line["site_url"]);
$subscribers = $line["subscribers"];
$check_box = "<input onclick='toggleSelectListRow2(this)'
$check_box = "<input onclick='Lists.onRowChecked(this)'
dojoType=\"dijit.form.CheckBox\"
type=\"checkbox\" \">";
$class = ($feedctr % 2) ? "even" : "odd";
$site_url = "<a target=\"_blank\" rel=\"noopener noreferrer\"
href=\"$site_url\">
<span class=\"fb_feedTitle\">".
@ -75,11 +73,9 @@
$feed_url = htmlspecialchars($line["feed_url"]);
$site_url = htmlspecialchars($line["site_url"]);
$check_box = "<input onclick='toggleSelectListRow2(this)' dojoType=\"dijit.form.CheckBox\"
$check_box = "<input onclick='Lists.onRowChecked(this)' dojoType=\"dijit.form.CheckBox\"
type=\"checkbox\">";
$class = ($feedctr % 2) ? "even" : "odd";
if ($line['articles_archived'] > 0) {
$archived = sprintf(_ngettext("%d archived article", "%d archived articles", (int) $line['articles_archived']), $line['articles_archived']);
$archived = "&nbsp;<span class='subscribers'>($archived)</span>";

@ -1244,7 +1244,7 @@
"g t" => "goto_tagcloud",
"g *p" => "goto_prefs",
// "other" => array(
"(9)|Tab" => "select_article_cursor", // tab
"r" => "select_article_cursor",
"c l" => "create_label",
"c f" => "create_filter",
"c s" => "collapse_sidebar",
@ -1310,9 +1310,6 @@
$data['last_article_id'] = Article::getLastArticleId();
$data['cdm_expanded'] = get_pref('CDM_EXPANDED');
$data['dep_ts'] = calculate_dep_timestamp();
$data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
$data["labels"] = Labels::get_all_labels($_SESSION["uid"]);
if (CHECK_FOR_UPDATES && !$disable_update_check && $_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) {
@ -2379,52 +2376,6 @@
return in_array($interface, class_implements($class));
}
function get_minified_js($files) {
$rv = '';
foreach ($files as $js) {
if (!isset($_GET['debug'])) {
$cached_file = CACHE_DIR . "/js/".basename($js);
if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js")) {
list($header, $contents) = explode("\n", file_get_contents($cached_file), 2);
if ($header && $contents) {
list($htag, $hversion) = explode(":", $header);
if ($htag == "tt-rss" && $hversion == VERSION) {
$rv .= $contents;
continue;
}
}
}
$minified = JShrink\Minifier::minify(file_get_contents("js/$js"));
file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified);
$rv .= $minified;
} else {
$rv .= file_get_contents("js/$js"); // no cache in debug mode
}
}
return $rv;
}
function calculate_dep_timestamp() {
$files = array_merge(glob("js/*.js"), glob("css/*.css"));
$max_ts = -1;
foreach ($files as $file) {
if (filemtime($file) > $max_ts) $max_ts = filemtime($file);
}
return $max_ts;
}
function T_js_decl($s1, $s2) {
if ($s1 && $s2) {
$s1 = preg_replace("/\n/", "", $s1);
@ -2615,3 +2566,15 @@
function arr_qmarks($arr) {
return str_repeat('?,', count($arr) - 1) . '?';
}
function get_scripts_timestamp() {
$files = glob("js/*.js");
$ts = 0;
foreach ($files as $file) {
$file_ts = filemtime($file);
if ($file_ts > $ts) $ts = $file_ts;
}
return $ts;
}

@ -10,7 +10,7 @@
foreach (array("lib/prototype.js",
"lib/dojo/dojo.js",
"lib/dojo/tt-rss-layer.js",
"js/functions.js",
"js/common.js",
"errors.php?mode=js") as $jsfile) {
echo javascript_tag($jsfile);
@ -19,67 +19,49 @@
<script type="text/javascript">
require({cache:{}});
Event.observe(window, 'load', function() {
init();
});
</script>
</head>
<body class="claro ttrss_main ttrss_login">
<script type="text/javascript">
function init() {
require(['dojo/parser','dijit/form/Button','dijit/form/CheckBox','dijit/form/Form',
'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'],function(parser){
parser.parse();
//show tooltip node only after this widget is instaniated.
dojo.query('div[dojoType="dijit.Tooltip"]').style({
display:''
});
fetchProfiles();
dijit.byId("bw_limit").attr("checked", getCookie("ttrss_bwlimit") == 'true');
document.forms.loginForm.login.focus();
});
}
require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox','dijit/form/Form',
'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'],function(parser, ready){
ready(function() {
parser.parse();
//show tooltip node only after this widget is instaniated.
dojo.query('div[dojoType="dijit.Tooltip"]').style({
display:''
});
fetchProfiles();
dijit.byId("bw_limit").attr("checked", Cookie.get("ttrss_bwlimit") == 'true');
document.forms.loginForm.login.focus();
});
});
function fetchProfiles() {
try {
var query = "op=getProfiles&login=" + param_escape(document.forms["loginForm"].login.value);
if (query) {
new Ajax.Request("public.php", {
parameters: query,
onComplete: function(transport) {
if (transport.responseText.match("select")) {
$('profile_box').innerHTML = transport.responseText;
//dojo.parser.parse('profile_box');
}
} });
}
} catch (e) {
exception_error("fetchProfiles", e);
}
const query = "op=getProfiles&login=" + encodeURIComponent(document.forms["loginForm"].login.value);
new Ajax.Request("public.php", {
parameters: query,
onComplete: function(transport) {
if (transport.responseText.match("select")) {
$('profile_box').innerHTML = transport.responseText;
//dojo.parser.parse('profile_box');
}
} });
}
function gotoRegForm() {
window.location.href = "register.php";
return false;
}
function bwLimitChange(elem) {
try {
var limit_set = elem.checked;
setCookie("ttrss_bwlimit", limit_set,
<?php print SESSION_COOKIE_LIFETIME ?>);
} catch (e) {
exception_error("bwLimitChange", e);
}
Cookie.set("ttrss_bwlimit", elem.checked,
<?php print SESSION_COOKIE_LIFETIME ?>);
}
</script>
@ -142,7 +124,7 @@ function bwLimitChange(elem) {
</div>
<div dojoType="dijit.Tooltip" connectId="bw_limit_label" position="below" style="display:none">
<?php echo __("Does not display images in articles, reduces automatic refreshes."); ?>
<?php echo __("Does not display images in articles, reduces automatic refreshes."); ?>
</div>
<?php if (SESSION_COOKIE_LIFETIME > 0) { ?>

@ -91,7 +91,7 @@
<script>
dojoConfig = {
async: true,
cacheBust: new Date(),
cacheBust: "<?php echo get_scripts_timestamp(); ?>",
packages: [
{ name: "fox", location: "../../js" },
]
@ -103,6 +103,8 @@
"lib/scriptaculous/scriptaculous.js?load=effects,controls",
"lib/dojo/dojo.js",
"lib/dojo/tt-rss-layer.js",
"js/tt-rss.js",
"js/common.js",
"errors.php?mode=js") as $jsfile) {
echo javascript_tag($jsfile);
@ -110,13 +112,9 @@
} ?>
<script type="text/javascript">
'use strict';
require({cache:{}});
<?php
print get_minified_js(["tt-rss.js",
"functions.js", "feedlist.js", "viewfeed.js", "PluginHost.js"]);
?>
</script>
<script type="text/javascript">
<?php
foreach (PluginHost::getInstance()->get_plugins() as $n => $p) {
@ -136,12 +134,6 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="referrer" content="no-referrer"/>
<script type="text/javascript">
Event.observe(window, 'load', function() {
init();
});
</script>
</head>
<body class="claro ttrss_main">
@ -187,7 +179,7 @@
<form id="main_toolbar_form" action="" onsubmit='return false'>
<select name="view_mode" title="<?php echo __('Show articles') ?>"
onchange="viewModeChanged()"
onchange="App.onViewModeChanged()"
dojoType="dijit.form.Select">
<option selected="selected" value="adaptive"><?php echo __('Adaptive') ?></option>
<option value="all_articles"><?php echo __('All Articles') ?></option>
@ -199,7 +191,7 @@
</select>
<select title="<?php echo __('Sort articles') ?>"
onchange="viewModeChanged()"
onchange="App.onViewModeChanged()"
dojoType="dijit.form.Select" name="order_by">
<option selected="selected" value="default"><?php echo __('Default') ?></option>
<option value="feed_dates"><?php echo __('Newest first') ?></option>
@ -207,16 +199,16 @@
<option value="title"><?php echo __('Title') ?></option>
</select>
<div dojoType="dijit.form.ComboButton" onclick="catchupCurrentFeed()">
<div dojoType="dijit.form.ComboButton" onclick="Feeds.catchupCurrent()">
<span><?php echo __('Mark as read') ?></span>
<div dojoType="dijit.DropDownMenu">
<div dojoType="dijit.MenuItem" onclick="catchupCurrentFeed('1day')">
<div dojoType="dijit.MenuItem" onclick="Feeds.catchupCurrent('1day')">
<?php echo __('Older than one day') ?>
</div>
<div dojoType="dijit.MenuItem" onclick="catchupCurrentFeed('1week')">
<div dojoType="dijit.MenuItem" onclick="Feeds.catchupCurrent('1week')">
<?php echo __('Older than one week') ?>
</div>
<div dojoType="dijit.MenuItem" onclick="catchupCurrentFeed('2week')">
<div dojoType="dijit.MenuItem" onclick="Feeds.catchupCurrent('2week')">
<?php echo __('Older than two weeks') ?>
</div>
</div>
@ -240,18 +232,18 @@
<div dojoType="dijit.form.DropDownButton">
<span><?php echo __('Actions...') ?></span>
<div dojoType="dijit.Menu" style="display: none">
<div dojoType="dijit.MenuItem" onclick="quickMenuGo('qmcPrefs')"><?php echo __('Preferences...') ?></div>
<div dojoType="dijit.MenuItem" onclick="quickMenuGo('qmcSearch')"><?php echo __('Search...') ?></div>
<div dojoType="dijit.MenuItem" onclick="App.onActionSelected('qmcPrefs')"><?php echo __('Preferences...') ?></div>
<div dojoType="dijit.MenuItem" onclick="App.onActionSelected('qmcSearch')"><?php echo __('Search...') ?></div>
<div dojoType="dijit.MenuItem" disabled="1"><?php echo __('Feed actions:') ?></div>
<div dojoType="dijit.MenuItem" onclick="quickMenuGo('qmcAddFeed')"><?php echo __('Subscribe to feed...') ?></div>
<div dojoType="dijit.MenuItem" onclick="quickMenuGo('qmcEditFeed')"><?php echo __('Edit this feed...') ?></div>
<div dojoType="dijit.MenuItem" onclick="quickMenuGo('qmcRemoveFeed')"><?php echo __('Unsubscribe') ?></div>
<div dojoType="dijit.MenuItem" onclick="App.onActionSelected('qmcAddFeed')"><?php echo __('Subscribe to feed...') ?></div>
<div dojoType="dijit.MenuItem" onclick="App.onActionSelected('qmcEditFeed')"><?php echo __('Edit this feed...') ?></div>
<div dojoType="dijit.MenuItem" onclick="App.onActionSelected('qmcRemoveFeed')"><?php echo __('Unsubscribe') ?></div>
<div dojoType="dijit.MenuItem" disabled="1"><?php echo __('All feeds:') ?></div>
<div dojoType="dijit.MenuItem" onclick="quickMenuGo('qmcCatchupAll')"><?php echo __('Mark as read') ?></div>
<div dojoType="dijit.MenuItem" onclick="quickMenuGo('qmcShowOnlyUnread')"><?php echo __('(Un)hide read feeds') ?></div>
<div dojoType="dijit.MenuItem" onclick="App.onActionSelected('qmcCatchupAll')"><?php echo __('Mark as read') ?></div>
<div dojoType="dijit.MenuItem" onclick="App.onActionSelected('qmcShowOnlyUnread')"><?php echo __('(Un)hide read feeds') ?></div>
<div dojoType="dijit.MenuItem" disabled="1"><?php echo __('Other actions:') ?></div>
<div dojoType="dijit.MenuItem" onclick="quickMenuGo('qmcToggleWidescreen')"><?php echo __('Toggle widescreen mode') ?></div>
<div dojoType="dijit.MenuItem" onclick="quickMenuGo('qmcHKhelp')"><?php echo __('Keyboard shortcuts help') ?></div>
<div dojoType="dijit.MenuItem" onclick="App.onActionSelected('qmcToggleWidescreen')"><?php echo __('Toggle widescreen mode') ?></div>
<div dojoType="dijit.MenuItem" onclick="App.onActionSelected('qmcHKhelp')"><?php echo __('Keyboard shortcuts help') ?></div>
<?php
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ACTION_ITEM) as $p) {
@ -260,7 +252,7 @@
?>
<?php if (!$_SESSION["hide_logout"]) { ?>
<div dojoType="dijit.MenuItem" onclick="quickMenuGo('qmcLogout')"><?php echo __('Logout') ?></div>
<div dojoType="dijit.MenuItem" onclick="App.onActionSelected('qmcLogout')"><?php echo __('Logout') ?></div>
<?php } ?>
</div>
</div>

@ -0,0 +1,421 @@
'use strict'
/* global __, ngettext */
define(["dojo/_base/declare"], function (declare) {
return declare("fox.AppBase", null, {
_initParams: [],
_rpc_seq: 0,
hotkey_prefix: 0,
hotkey_prefix_pressed: false,
hotkey_prefix_timeout: 0,
constructor: function() {
window.onerror = this.Error.onWindowError;
},
getInitParam: function(k) {
return this._initParams[k];
},
setInitParam: function(k, v) {
this._initParams[k] = v;
},
enableCsrfSupport: function() {
Ajax.Base.prototype.initialize = Ajax.Base.prototype.initialize.wrap(
function (callOriginal, options) {
if (App.getInitParam("csrf_token") != undefined) {
Object.extend(options, options || { });
if (Object.isString(options.parameters))
options.parameters = options.parameters.toQueryParams();
else if (Object.isHash(options.parameters))
options.parameters = options.parameters.toObject();
options.parameters["csrf_token"] = App.getInitParam("csrf_token");
}
return callOriginal(options);
}
);
},
urlParam: function(param) {
return String(window.location.href).parseQuery()[param];
},
next_seq: function() {
this._rpc_seq += 1;
return this._rpc_seq;
},
get_seq: function() {
return this._rpc_seq;
},
setLoadingProgress: function(p) {
loading_progress += p;
if (dijit.byId("loading_bar"))
dijit.byId("loading_bar").update({progress: loading_progress});
if (loading_progress >= 90)
Element.hide("overlay");
},
keyeventToAction: function(event) {
const hotkeys_map = App.getInitParam("hotkeys");
const keycode = event.which;
const keychar = String.fromCharCode(keycode).toLowerCase();
if (keycode == 27) { // escape and drop prefix
this.hotkey_prefix = false;
}
if (keycode == 16 || keycode == 17) return; // ignore lone shift / ctrl
if (!this.hotkey_prefix && hotkeys_map[0].indexOf(keychar) != -1) {
this.hotkey_prefix = keychar;
$("cmdline").innerHTML = keychar;
Element.show("cmdline");
window.clearTimeout(this.hotkey_prefix_timeout);
this.hotkey_prefix_timeout = window.setTimeout(() => {
this.hotkey_prefix = false;
Element.hide("cmdline");
}, 3 * 1000);
event.stopPropagation();
return false;
}
Element.hide("cmdline");
let hotkey_name = keychar.search(/[a-zA-Z0-9]/) != -1 ? keychar : "(" + keycode + ")";
// ensure ^*char notation
if (event.shiftKey) hotkey_name = "*" + hotkey_name;
if (event.ctrlKey) hotkey_name = "^" + hotkey_name;
if (event.altKey) hotkey_name = "+" + hotkey_name;
if (event.metaKey) hotkey_name = "%" + hotkey_name;
const hotkey_full = this.hotkey_prefix ? this.hotkey_prefix + " " + hotkey_name : hotkey_name;
this.hotkey_prefix = false;
let action_name = false;
for (const sequence in hotkeys_map[1]) {
if (hotkeys_map[1].hasOwnProperty(sequence)) {
if (sequence == hotkey_full) {
action_name = hotkeys_map[1][sequence];
break;
}
}
}
console.log('keyeventToAction', hotkey_full, '=>', action_name);
return action_name;
},
cleanupMemory: function(root) {
const dijits = dojo.query("[widgetid]", dijit.byId(root).domNode).map(dijit.byNode);
dijits.each(function (d) {
dojo.destroy(d.domNode);
});
$$("#" + root + " *").each(function (i) {
i.parentNode ? i.parentNode.removeChild(i) : true;
});
},
helpDialog: function(topic) {
const query = "backend.php?op=backend&method=help&topic=" + encodeURIComponent(topic);
if (dijit.byId("helpDlg"))
dijit.byId("helpDlg").destroyRecursive();
const dialog = new dijit.Dialog({
id: "helpDlg",
title: __("Help"),
style: "width: 600px",
href: query,
});
dialog.show();
},
displayDlg: function(title, id, param, callback) {
Notify.progress("Loading, please wait...", true);
const query = {op: "dlg", method: id, param: param};
xhrPost("backend.php", query, (transport) => {
try {
const content = transport.responseText;
let dialog = dijit.byId("infoBox");
if (!dialog) {
dialog = new dijit.Dialog({
title: title,
id: 'infoBox',
style: "width: 600px",
onCancel: function () {
return true;
},
onExecute: function () {
return true;
},
onClose: function () {
return true;
},
content: content
});
} else {
dialog.attr('title', title);
dialog.attr('content', content);
}
dialog.show();
Notify.close();
if (callback) callback(transport);
} catch (e) {
this.Error.report(e);
}
});
return false;
},
handleRpcJson: function(transport) {
const netalert_dijit = dijit.byId("net-alert");
let netalert = false;
if (netalert_dijit) netalert = netalert_dijit.domNode;
try {
const reply = JSON.parse(transport.responseText);
if (reply) {
const error = reply['error'];
if (error) {
const code = error['code'];
const msg = error['msg'];
console.warn("[handleRpcJson] received fatal error " + code + "/" + msg);
if (code != 0) {
fatalError(code, msg);
return false;
}
}
const seq = reply['seq'];
if (seq && this.get_seq() != seq) {
console.log("[handleRpcJson] sequence mismatch: " + seq +
" (want: " + this.get_seq() + ")");
return true;
}
const message = reply['message'];
if (message == "UPDATE_COUNTERS") {
console.log("need to refresh counters...");
App.setInitParam("last_article_id", -1);
Feeds.requestCounters(true);
}
const counters = reply['counters'];
if (counters)
Feeds.parseCounters(counters);
const runtime_info = reply['runtime-info'];
if (runtime_info)
App.parseRuntimeInfo(runtime_info);
if (netalert) netalert.hide();
return reply;
} else {
if (netalert)
netalert.show();
else
Notify.error("Communication problem with server.");
}
} catch (e) {
if (netalert)
netalert.show();
else
Notify.error("Communication problem with server.");
console.error(e);
}
return false;
},
parseRuntimeInfo: function(data) {
for (const k in data) {
if (data.hasOwnProperty(k)) {
const v = data[k];
console.log("RI:", k, "=>", v);
if (k == "daemon_is_running" && v != 1) {
Notify.error("<span onclick=\"App.explainError(1)\">Update daemon is not running.</span>", true);
return;
}
if (k == "update_result") {
const updatesIcon = dijit.byId("updatesIcon").domNode;
if (v) {
Element.show(updatesIcon);
} else {
Element.hide(updatesIcon);
}
}
if (k == "daemon_stamp_ok" && v != 1) {
Notify.error("<span onclick=\"App.explainError(3)\">Update daemon is not updating feeds.</span>", true);
return;
}
if (k == "max_feed_id" || k == "num_feeds") {
if (App.getInitParam(k) != v) {
console.log("feed count changed, need to reload feedlist.");
Feeds.reload();
}
}
this.setInitParam(k, v);
}
}
PluginHost.run(PluginHost.HOOK_RUNTIME_INFO_LOADED, data);
},
backendSanityCallback: function (transport) {
const reply = JSON.parse(transport.responseText);
if (!reply) {
fatalError(3, "Sanity check: invalid RPC reply", transport.responseText);
return;
}
const error_code = reply['error']['code'];
if (error_code && error_code != 0) {
return fatalError(error_code, reply['error']['message']);
}
console.log("sanity check ok");
const params = reply['init-params'];
if (params) {
console.log('reading init-params...');
for (const k in params) {
if (params.hasOwnProperty(k)) {
switch (k) {
case "label_base_index":
_label_base_index = parseInt(params[k]);
break;
case "hotkeys":
// filter mnemonic definitions (used for help panel) from hotkeys map
// i.e. *(191)|Ctrl-/ -> *(191)
const tmp = [];
for (const sequence in params[k][1]) {
if (params[k][1].hasOwnProperty(sequence)) {
const filtered = sequence.replace(/\|.*$/, "");
tmp[filtered] = params[k][1][sequence];
}
}
params[k][1] = tmp;
break;
}
console.log("IP:", k, "=>", params[k]);
this.setInitParam(k, params[k]);
}
}
// PluginHost might not be available on non-index pages
if (typeof PluginHost !== 'undefined')
PluginHost.run(PluginHost.HOOK_PARAMS_LOADED, App._initParams);
}
this.initSecondStage();
},
explainError: function(code) {
return this.displayDlg(__("Error explained"), "explainError", code);
},
Error: {
report: function(error, params) {
params = params || {};
if (!error) return;
console.error("[Error.report]", error, params);
const message = params.message ? params.message : error.toString();
try {
xhrPost("backend.php",
{op: "rpc", method: "log",
file: params.filename ? params.filename : error.fileName,
line: params.lineno ? params.lineno : error.lineNumber,
msg: message,
context: error.stack},
(transport) => {
console.warn("[Error.report] log response", transport.responseText);
});
} catch (re) {
console.error("[Error.report] exception while saving logging error on server", re);
}
try {
if (dijit.byId("exceptionDlg"))
dijit.byId("exceptionDlg").destroyRecursive();
let content = "<div class='fatalError'><p>" + message + "</p>";
if (error.stack)
content += "<div><b>Stack trace:</b></div>" +
"<textarea name=\"stack\" readonly=\"1\">" + error.stack + "</textarea>";
content += "<div style='text-align : center'>";
content += "<button dojoType=\"dijit.form.Button\" " +
"onclick=\"dijit.byId('exceptionDlg').hide()\">" +
__('Close this window') + "</button>";
content += "</div>";
const dialog = new dijit.Dialog({
id: "exceptionDlg",
title: "Unhandled exception",
style: "width: 600px",
content: content
});
dialog.show();
} catch (de) {
console.error("[Error.report] exception while showing error dialog", de);
alert(error.stack ? error.stack : message);
}
},
onWindowError: function (message, filename, lineno, colno, error) {
// called without context (this) from window.onerror
App.Error.report(error,
{message: message, filename: filename, lineno: lineno, colno: colno});
},
}
});
});

@ -0,0 +1,339 @@
'use strict'
/* global __, ngettext */
define(["dojo/_base/declare"], function (declare) {
Article = {
_active_article_id: 0,
selectionSetScore: function () {
const ids = Headlines.getSelected();
if (ids.length > 0) {
console.log(ids);
const score = prompt(__("Please enter new score for selected articles:"));
if (score != undefined) {
const query = {
op: "article", method: "setScore", id: ids.toString(),
score: score
};
xhrJson("backend.php", query, (reply) => {
if (reply) {
reply.id.each((id) => {
const row = $("RROW-" + id);
if (row) {
const pic = row.getElementsByClassName("score-pic")[0];
if (pic) {
pic.src = pic.src.replace(/score_.*?\.png/,
reply["score_pic"]);
pic.setAttribute("score", reply["score"]);
}
}
});
}
});
}
} else {
alert(__("No articles selected."));
}
},
setScore: function (id, pic) {
const score = pic.getAttribute("score");
const new_score = prompt(__("Please enter new score for this article:"), score);
if (new_score != undefined) {
const query = {op: "article", method: "setScore", id: id, score: new_score};
xhrJson("backend.php", query, (reply) => {
if (reply) {
pic.src = pic.src.replace(/score_.*?\.png/, reply["score_pic"]);
pic.setAttribute("score", new_score);
pic.setAttribute("title", new_score);
}
});
}
},
cdmUnsetActive: function (event) {
const row = $("RROW-" + Article.getActive());
if (row) {
row.removeClassName("active");
const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]);
if (cb && !row.hasClassName("Selected"))
cb.attr("checked", false);
Article.setActive(0);
if (event)
event.stopPropagation();
return false;
}
},
close: function () {
if (dijit.byId("content-insert"))
dijit.byId("headlines-wrap-inner").removeChild(
dijit.byId("content-insert"));
},
displayUrl: function (id) {
const query = {op: "rpc", method: "getlinktitlebyid", id: id};
xhrJson("backend.php", query, (reply) => {
if (reply && reply.link) {
prompt(__("Article URL:"), reply.link);
}
});
},
openInNewWindow: function (id) {
const w = window.open("");
w.opener = null;
w.location = "backend.php?op=article&method=redirect&id=" + id;
Article.setActive(id);
},
render: function (article) {
App.cleanupMemory("content-insert");
dijit.byId("headlines-wrap-inner").addChild(
dijit.byId("content-insert"));
const c = dijit.byId("content-insert");
try {
c.domNode.scrollTop = 0;
} catch (e) {
}
c.attr('content', article);
PluginHost.run(PluginHost.HOOK_ARTICLE_RENDERED, c.domNode);
Headlines.correctHeadlinesOffset(Article.getActive());
try {
c.focus();
} catch (e) {
}
},
view: function (id, noexpand) {
this.setActive(id);
if (!noexpand) {
console.log("loading article", id);
const cids = [];
/* only request uncached articles */
this.getRelativeIds(id).each((n) => {
if (!ArticleCache.get(n))
cids.push(n);
});
const cached_article = ArticleCache.get(id);
if (cached_article) {
console.log('rendering cached', id);
this.render(cached_article);
return false;
}
xhrPost("backend.php", {op: "article", method: "view", id: id, cids: cids.toString()}, (transport) => {
try {
const reply = App.handleRpcJson(transport);
if (reply) {
reply.each(function (article) {
if (Article.getActive() == article['id']) {
Article.render(article['content']);
}
ArticleCache.set(article['id'], article['content']);
});
} else {
console.error("Invalid object received: " + transport.responseText);
Article.render("<div class='whiteBox'>" +
__('Could not display article (invalid object received - see error console for details)') + "</div>");
}
//const unread_in_buffer = $$("#headlines-frame > div[id*=RROW][class*=Unread]").length;
//request_counters(unread_in_buffer == 0);
Notify.close();
} catch (e) {
App.Error.report(e);
}
})
}
return false;
},
editTags: function (id) {
const query = "backend.php?op=article&method=editArticleTags&param=" + encodeURIComponent(id);
if (dijit.byId("editTagsDlg"))
dijit.byId("editTagsDlg").destroyRecursive();
const dialog = new dijit.Dialog({
id: "editTagsDlg",
title: __("Edit article Tags"),
style: "width: 600px",
execute: function () {
if (this.validate()) {
Notify.progress("Saving article tags...", true);
xhrPost("backend.php", this.attr('value'), (transport) => {
try {
Notify.close();
dialog.hide();
const data = JSON.parse(transport.responseText);
if (data) {
const id = data.id;
const tags = $("ATSTR-" + id);
const tooltip = dijit.byId("ATSTRTIP-" + id);
if (tags) tags.innerHTML = data.content;
if (tooltip) tooltip.attr('label', data.content_full);
}
} catch (e) {
App.Error.report(e);
}
});
}
},
href: query
});
const tmph = dojo.connect(dialog, 'onLoad', function () {
dojo.disconnect(tmph);
new Ajax.Autocompleter('tags_str', 'tags_choices',
"backend.php?op=article&method=completeTags",
{tokens: ',', paramName: "search"});
});
dialog.show();
},
cdmScrollToId: function (id, force) {
const ctr = $("headlines-frame");
const e = $("RROW-" + id);
if (!e || !ctr) return;
if (force || e.offsetTop + e.offsetHeight > (ctr.scrollTop + ctr.offsetHeight) ||
e.offsetTop < ctr.scrollTop) {
// expanded cdm has a 4px margin now
ctr.scrollTop = parseInt(e.offsetTop) - 4;
Element.hide("floatingTitle");
}
},
setActive: function (id) {
console.log("setActive", id);
$$("div[id*=RROW][class*=active]").each((e) => {
e.removeClassName("active");
if (!e.hasClassName("Selected")) {
const cb = dijit.getEnclosingWidget(e.select(".rchk")[0]);
if (cb) cb.attr("checked", false);
}
});
this._active_article_id = id;
const row = $("RROW-" + id);
if (row) {
if (row.hasAttribute("data-content")) {
console.log("unpacking: " + row.id);
row.select(".content-inner")[0].innerHTML = row.getAttribute("data-content");
row.removeAttribute("data-content");
PluginHost.run(PluginHost.HOOK_ARTICLE_RENDERED_CDM, row);
}
if (row.hasClassName("Unread")) {
Headlines.catchupBatched(() => {
Feeds.decrementFeedCounter(Feeds.getActive(), Feeds.activeIsCat());
Headlines.toggleUnread(id, 0);
Headlines.updateFloatingTitle(true);
});
}
row.addClassName("active");
if (!row.hasClassName("Selected")) {
const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]);
if (cb) cb.attr("checked", true);
}
PluginHost.run(PluginHost.HOOK_ARTICLE_SET_ACTIVE, this._active_article_id);
}
Headlines.updateSelectedPrompt();
},
getActive: function () {
return this._active_article_id;
},
scroll: function (offset) {
if (!App.isCombinedMode()) {
const ci = $("content-insert");
if (ci) {
ci.scrollTop += offset;
}
} else {
const hi = $("headlines-frame");
if (hi) {
hi.scrollTop += offset;
}
}
},
getRelativeIds: function (id, limit) {
const tmp = [];
if (!limit) limit = 6; //3
const ids = Headlines.getLoaded();
for (let i = 0; i < ids.length; i++) {
if (ids[i] == id) {
for (let k = 1; k <= limit; k++) {
//if (i > k-1) tmp.push(ids[i-k]);
if (i < ids.length - k) tmp.push(ids[i + k]);
}
break;
}
}
return tmp;
},
mouseIn: function (id) {
this.post_under_pointer = id;
},
mouseOut: function (id) {
this.post_under_pointer = false;
},
getUnderPointer: function () {
return this.post_under_pointer;
}
}
return Article;
});

@ -0,0 +1,29 @@
'use strict'
/* global __, ngettext */
define(["dojo/_base/declare"], function (declare) {
ArticleCache = {
has_storage: 'sessionStorage' in window && window['sessionStorage'] !== null,
set: function (id, obj) {
if (this.has_storage)
try {
sessionStorage["article:" + id] = obj;
} catch (e) {
sessionStorage.clear();
}
},
get: function (id) {
if (this.has_storage)
return sessionStorage["article:" + id];
},
clear: function () {
if (this.has_storage)
sessionStorage.clear();
},
del: function (id) {
if (this.has_storage)
sessionStorage.removeItem("article:" + id);
},
}
return ArticleCache;
});

@ -0,0 +1,449 @@
'use strict'
/* global __, ngettext */
define(["dojo/_base/declare"], function (declare) {
// noinspection JSUnusedGlobalSymbols
CommonDialogs = {
closeInfoBox: function() {
const dialog = dijit.byId("infoBox");
if (dialog) dialog.hide();
},
uploadIconHandler: function(rc) {
switch (rc) {
case 0:
Notify.info("Upload complete.");
if (App.isPrefs()) {
Feeds.reload();
} else {
setTimeout('Feeds.reload(false, false)', 50);
}
break;
case 1:
Notify.error("Upload failed: icon is too big.");
break;
case 2:
Notify.error("Upload failed.");
break;
}
},
removeFeedIcon: function(id) {
if (confirm(__("Remove stored feed icon?"))) {
Notify.progress("Removing feed icon...", true);
const query = {op: "pref-feeds", method: "removeicon", feed_id: id};
xhrPost("backend.php", query, () => {
Notify.info("Feed icon removed.");
if (App.isPrefs()) {
Feeds.reload();
} else {
setTimeout('Feeds.reload(false, false)', 50);
}
});
}
return false;
},
uploadFeedIcon: function() {
const file = $("icon_file");
if (file.value.length == 0) {
alert(__("Please select an image file to upload."));
} else if (confirm(__("Upload new icon for this feed?"))) {
Notify.progress("Uploading, please wait...", true);
return true;
}
return false;
},
quickAddFeed: function() {
const query = "backend.php?op=feeds&method=quickAddFeed";
// overlapping widgets
if (dijit.byId("batchSubDlg")) dijit.byId("batchSubDlg").destroyRecursive();
if (dijit.byId("feedAddDlg")) dijit.byId("feedAddDlg").destroyRecursive();
const dialog = new dijit.Dialog({
id: "feedAddDlg",
title: __("Subscribe to Feed"),
style: "width: 600px",
show_error: function (msg) {
const elem = $("fadd_error_message");
elem.innerHTML = msg;
if (!Element.visible(elem))
new Effect.Appear(elem);
},
execute: function () {
if (this.validate()) {
console.log(dojo.objectToQuery(this.attr('value')));
const feed_url = this.attr('value').feed;
Element.show("feed_add_spinner");
Element.hide("fadd_error_message");
xhrPost("backend.php", this.attr('value'), (transport) => {
try {
try {
var reply = JSON.parse(transport.responseText);
} catch (e) {
Element.hide("feed_add_spinner");
alert(__("Failed to parse output. This can indicate server timeout and/or network issues. Backend output was logged to browser console."));
console.log('quickAddFeed, backend returned:' + transport.responseText);
return;
}
const rc = reply['result'];
Notify.close();
Element.hide("feed_add_spinner");
console.log(rc);
switch (parseInt(rc['code'])) {
case 1:
dialog.hide();
Notify.info(__("Subscribed to %s").replace("%s", feed_url));
Feeds.reload();
break;
case 2:
dialog.show_error(__("Specified URL seems to be invalid."));
break;
case 3:
dialog.show_error(__("Specified URL doesn't seem to contain any feeds."));
break;
case 4:
const feeds = rc['feeds'];
Element.show("fadd_multiple_notify");
const select = dijit.byId("feedDlg_feedContainerSelect");
while (select.getOptions().length > 0)
select.removeOption(0);
select.addOption({value: '', label: __("Expand to select feed")});
let count = 0;
for (const feedUrl in feeds) {
if (feeds.hasOwnProperty(feedUrl)) {
select.addOption({value: feedUrl, label: feeds[feedUrl]});
count++;
}
}
Effect.Appear('feedDlg_feedsContainer', {duration: 0.5});
break;
case 5:
dialog.show_error(__("Couldn't download the specified URL: %s").replace("%s", rc['message']));
break;
case 6:
dialog.show_error(__("XML validation failed: %s").replace("%s", rc['message']));
break;
case 0:
dialog.show_error(__("You are already subscribed to this feed."));
break;
}
} catch (e) {
console.error(transport.responseText);
App.Error.report(e);
}
});
}
},
href: query
});
dialog.show();
},
showFeedsWithErrors: function() {
const query = {op: "pref-feeds", method: "feedsWithErrors"};
if (dijit.byId("errorFeedsDlg"))
dijit.byId("errorFeedsDlg").destroyRecursive();
const dialog = new dijit.Dialog({
id: "errorFeedsDlg",
title: __("Feeds with update errors"),
style: "width: 600px",
getSelectedFeeds: function () {
return Tables.getSelected("prefErrorFeedList");
},
removeSelected: function () {
const sel_rows = this.getSelectedFeeds();
if (sel_rows.length > 0) {
if (confirm(__("Remove selected feeds?"))) {
Notify.progress("Removing selected feeds...", true);
const query = {
op: "pref-feeds", method: "remove",
ids: sel_rows.toString()
};
xhrPost("backend.php", query, () => {
Notify.close();
dialog.hide();
Feeds.reload();
});
}
} else {
alert(__("No feeds selected."));
}
},
execute: function () {
if (this.validate()) {
//
}
},
href: "backend.php?" + dojo.objectToQuery(query)
});
dialog.show();
},
feedBrowser: function() {
const query = {op: "feeds", method: "feedBrowser"};
if (dijit.byId("feedAddDlg"))
dijit.byId("feedAddDlg").hide();
if (dijit.byId("feedBrowserDlg"))
dijit.byId("feedBrowserDlg").destroyRecursive();
// noinspection JSUnusedGlobalSymbols
const dialog = new dijit.Dialog({
id: "feedBrowserDlg",
title: __("More Feeds"),
style: "width: 600px",
getSelectedFeedIds: function () {
const list = $$("#browseFeedList li[id*=FBROW]");
const selected = [];
list.each(function (child) {
const id = child.id.replace("FBROW-", "");
if (child.hasClassName('Selected')) {
selected.push(id);
}
});
return selected;
},
getSelectedFeeds: function () {
const list = $$("#browseFeedList li.Selected");
const selected = [];
list.each(function (child) {
const title = child.getElementsBySelector("span.fb_feedTitle")[0].innerHTML;
const url = child.getElementsBySelector("a.fb_feedUrl")[0].href;
selected.push([title, url]);
});
return selected;
},
subscribe: function () {
const mode = this.attr('value').mode;
let selected = [];
if (mode == "1")
selected = this.getSelectedFeeds();
else
selected = this.getSelectedFeedIds();
if (selected.length > 0) {
dijit.byId("feedBrowserDlg").hide();
Notify.progress("Loading, please wait...", true);
const query = {
op: "rpc", method: "massSubscribe",
payload: JSON.stringify(selected), mode: mode
};
xhrPost("backend.php", query, () => {
Notify.close();
Feeds.reload();
});
} else {
alert(__("No feeds selected."));
}
},
update: function () {
Element.show('feed_browser_spinner');
xhrPost("backend.php", dialog.attr("value"), (transport) => {
Notify.close();
Element.hide('feed_browser_spinner');
const reply = JSON.parse(transport.responseText);
const mode = reply['mode'];
if ($("browseFeedList") && reply['content']) {
$("browseFeedList").innerHTML = reply['content'];
}
dojo.parser.parse("browseFeedList");
if (mode == 2) {
Element.show(dijit.byId('feed_archive_remove').domNode);
} else {
Element.hide(dijit.byId('feed_archive_remove').domNode);
}
});
},
removeFromArchive: function () {
const selected = this.getSelectedFeedIds();
if (selected.length > 0) {
if (confirm(__("Remove selected feeds from the archive? Feeds with stored articles will not be removed."))) {
Element.show('feed_browser_spinner');
const query = {op: "rpc", method: "remarchive", ids: selected.toString()};
xhrPost("backend.php", query, () => {
dialog.update();
});
}
}
},
execute: function () {
if (this.validate()) {
this.subscribe();
}
},
href: "backend.php?" + dojo.objectToQuery(query)
});
dialog.show();
},
addLabel: function(select, callback) {
const caption = prompt(__("Please enter label caption:"), "");
if (caption != undefined && caption.trim().length > 0) {
const query = {op: "pref-labels", method: "add", caption: caption.trim()};
if (select)
Object.extend(query, {output: "select"});
Notify.progress("Loading, please wait...", true);
xhrPost("backend.php", query, (transport) => {
if (callback) {
callback(transport);
} else if (App.isPrefs()) {
dijit.byId("labelTree").reload();
} else {
Feeds.reload();
}
});
}
},
unsubscribeFeed: function(feed_id, title) {
const msg = __("Unsubscribe from %s?").replace("%s", title);
if (title == undefined || confirm(msg)) {
Notify.progress("Removing feed...");
const query = {op: "pref-feeds", quiet: 1, method: "remove", ids: feed_id};
xhrPost("backend.php", query, () => {
if (dijit.byId("feedEditDlg")) dijit.byId("feedEditDlg").hide();
if (App.isPrefs()) {
Feeds.reload();
} else {
if (feed_id == Feeds.getActive())
setTimeout(() => {
Feeds.open({feed: -5})
},
100);
if (feed_id < 0) Feeds.reload();
}
});
}
return false;
},
editFeed: function (feed) {
if (feed <= 0)
return alert(__("You can't edit this kind of feed."));
const query = {op: "pref-feeds", method: "editfeed", id: feed};
console.log("editFeed", query);
if (dijit.byId("filterEditDlg"))
dijit.byId("filterEditDlg").destroyRecursive();
if (dijit.byId("feedEditDlg"))
dijit.byId("feedEditDlg").destroyRecursive();
const dialog = new dijit.Dialog({
id: "feedEditDlg",
title: __("Edit Feed"),
style: "width: 600px",
execute: function () {
if (this.validate()) {
Notify.progress("Saving data...", true);
xhrPost("backend.php", dialog.attr('value'), () => {
dialog.hide();
Notify.close();
Feeds.reload();
});
}
},
href: "backend.php?" + dojo.objectToQuery(query)
});
dialog.show();
},
genUrlChangeKey: function(feed, is_cat) {
if (confirm(__("Generate new syndication address for this feed?"))) {
Notify.progress("Trying to change address...", true);
const query = {op: "pref-feeds", method: "regenFeedKey", id: feed, is_cat: is_cat};
xhrJson("backend.php", query, (reply) => {
const new_link = reply.link;
const e = $('gen_feed_url');
if (new_link) {
e.innerHTML = e.innerHTML.replace(/&amp;key=.*$/,
"&amp;key=" + new_link);
e.href = e.href.replace(/&key=.*$/,
"&key=" + new_link);
new Effect.Highlight(e);
Notify.close();
} else {
Notify.error("Could not change feed URL.");
}
});
}
return false;
}
};
return CommonDialogs;
});

@ -0,0 +1,393 @@
'use strict'
/* global __, ngettext */
define(["dojo/_base/declare"], function (declare) {
Filters = {
filterDlgCheckAction: function(sender) {
const action = sender.value;
const action_param = $("filterDlg_paramBox");
if (!action_param) {
console.log("filterDlgCheckAction: can't find action param box!");
return;
}
// if selected action supports parameters, enable params field
if (action == 4 || action == 6 || action == 7 || action == 9) {
new Effect.Appear(action_param, {duration: 0.5});
Element.hide(dijit.byId("filterDlg_actionParam").domNode);
Element.hide(dijit.byId("filterDlg_actionParamLabel").domNode);
Element.hide(dijit.byId("filterDlg_actionParamPlugin").domNode);
if (action == 7) {
Element.show(dijit.byId("filterDlg_actionParamLabel").domNode);
} else if (action == 9) {
Element.show(dijit.byId("filterDlg_actionParamPlugin").domNode);
} else {
Element.show(dijit.byId("filterDlg_actionParam").domNode);
}
} else {
Element.hide(action_param);
}
},
createNewRuleElement: function(parentNode, replaceNode) {
const form = document.forms["filter_new_rule_form"];
const query = {op: "pref-filters", method: "printrulename", rule: dojo.formToJson(form)};
xhrPost("backend.php", query, (transport) => {
try {
const li = dojo.create("li");
const cb = dojo.create("input", {type: "checkbox"}, li);
new dijit.form.CheckBox({
onChange: function () {
Lists.onRowChecked(this);
},
}, cb);
dojo.create("input", {
type: "hidden",
name: "rule[]",
value: dojo.formToJson(form)
}, li);
dojo.create("span", {
onclick: function () {
dijit.byId('filterEditDlg').editRule(this);
},
innerHTML: transport.responseText
}, li);
if (replaceNode) {
parentNode.replaceChild(li, replaceNode);
} else {
parentNode.appendChild(li);
}
} catch (e) {
App.Error.report(e);
}
});
},
createNewActionElement: function(parentNode, replaceNode) {
const form = document.forms["filter_new_action_form"];
if (form.action_id.value == 7) {
form.action_param.value = form.action_param_label.value;
} else if (form.action_id.value == 9) {
form.action_param.value = form.action_param_plugin.value;
}
const query = {
op: "pref-filters", method: "printactionname",
action: dojo.formToJson(form)
};
xhrPost("backend.php", query, (transport) => {
try {
const li = dojo.create("li");
const cb = dojo.create("input", {type: "checkbox"}, li);
new dijit.form.CheckBox({
onChange: function () {
Lists.onRowChecked(this);
},
}, cb);
dojo.create("input", {
type: "hidden",
name: "action[]",
value: dojo.formToJson(form)
}, li);
dojo.create("span", {
onclick: function () {
dijit.byId('filterEditDlg').editAction(this);
},
innerHTML: transport.responseText
}, li);
if (replaceNode) {
parentNode.replaceChild(li, replaceNode);
} else {
parentNode.appendChild(li);
}
} catch (e) {
App.Error.report(e);
}
});
},
addFilterRule: function(replaceNode, ruleStr) {
if (dijit.byId("filterNewRuleDlg"))
dijit.byId("filterNewRuleDlg").destroyRecursive();
const query = "backend.php?op=pref-filters&method=newrule&rule=" +
encodeURIComponent(ruleStr);
const rule_dlg = new dijit.Dialog({
id: "filterNewRuleDlg",
title: ruleStr ? __("Edit rule") : __("Add rule"),
style: "width: 600px",
execute: function () {
if (this.validate()) {
Filters.createNewRuleElement($("filterDlg_Matches"), replaceNode);
this.hide();
}
},
href: query
});
rule_dlg.show();
},
addFilterAction: function(replaceNode, actionStr) {
if (dijit.byId("filterNewActionDlg"))
dijit.byId("filterNewActionDlg").destroyRecursive();
const query = "backend.php?op=pref-filters&method=newaction&action=" +
encodeURIComponent(actionStr);
const rule_dlg = new dijit.Dialog({
id: "filterNewActionDlg",
title: actionStr ? __("Edit action") : __("Add action"),
style: "width: 600px",
execute: function () {
if (this.validate()) {
Filters.createNewActionElement($("filterDlg_Actions"), replaceNode);
this.hide();
}
},
href: query
});
rule_dlg.show();
},
editFilterTest: function(query) {
if (dijit.byId("filterTestDlg"))
dijit.byId("filterTestDlg").destroyRecursive();
const test_dlg = new dijit.Dialog({
id: "filterTestDlg",
title: "Test Filter",
style: "width: 600px",
results: 0,
limit: 100,
max_offset: 10000,
getTestResults: function (query, offset) {
const updquery = query + "&offset=" + offset + "&limit=" + test_dlg.limit;
console.log("getTestResults:" + offset);
xhrPost("backend.php", updquery, (transport) => {
try {
const result = JSON.parse(transport.responseText);
if (result && dijit.byId("filterTestDlg") && dijit.byId("filterTestDlg").open) {
test_dlg.results += result.length;
console.log("got results:" + result.length);
$("prefFilterProgressMsg").innerHTML = __("Looking for articles (%d processed, %f found)...")
.replace("%f", test_dlg.results)
.replace("%d", offset);
console.log(offset + " " + test_dlg.max_offset);
for (let i = 0; i < result.length; i++) {
const tmp = new Element("table");
tmp.innerHTML = result[i];
dojo.parser.parse(tmp);
$("prefFilterTestResultList").innerHTML += tmp.innerHTML;
}
if (test_dlg.results < 30 && offset < test_dlg.max_offset) {
// get the next batch
window.setTimeout(function () {
test_dlg.getTestResults(query, offset + test_dlg.limit);
}, 0);
} else {
// all done
Element.hide("prefFilterLoadingIndicator");
if (test_dlg.results == 0) {
$("prefFilterTestResultList").innerHTML = "<tr><td align='center'>No recent articles matching this filter have been found.</td></tr>";
$("prefFilterProgressMsg").innerHTML = "Articles matching this filter:";
} else {
$("prefFilterProgressMsg").innerHTML = __("Found %d articles matching this filter:")
.replace("%d", test_dlg.results);
}
}
} else if (!result) {
console.log("getTestResults: can't parse results object");
Element.hide("prefFilterLoadingIndicator");
Notify.error("Error while trying to get filter test results.");
} else {
console.log("getTestResults: dialog closed, bailing out.");
}
} catch (e) {
App.Error.report(e);
}
});
},
href: query
});
dojo.connect(test_dlg, "onLoad", null, function (e) {
test_dlg.getTestResults(query, 0);
});
test_dlg.show();
},
quickAddFilter: function() {
let query;
if (!App.isPrefs()) {
query = {
op: "pref-filters", method: "newfilter",
feed: Feeds.getActive(), is_cat: Feeds.activeIsCat()
};
} else {
query = {op: "pref-filters", method: "newfilter"};
}
console.log('quickAddFilter', query);
if (dijit.byId("feedEditDlg"))
dijit.byId("feedEditDlg").destroyRecursive();
if (dijit.byId("filterEditDlg"))
dijit.byId("filterEditDlg").destroyRecursive();
const dialog = new dijit.Dialog({
id: "filterEditDlg",
title: __("Create Filter"),
style: "width: 600px",
test: function () {
const query = "backend.php?" + dojo.formToQuery("filter_new_form") + "&savemode=test";
Filters.editFilterTest(query);
},
selectRules: function (select) {
$$("#filterDlg_Matches input[type=checkbox]").each(function (e) {
e.checked = select;
if (select)
e.parentNode.addClassName("Selected");
else
e.parentNode.removeClassName("Selected");
});
},
selectActions: function (select) {
$$("#filterDlg_Actions input[type=checkbox]").each(function (e) {
e.checked = select;
if (select)
e.parentNode.addClassName("Selected");
else
e.parentNode.removeClassName("Selected");
});
},
editRule: function (e) {
const li = e.parentNode;
const rule = li.getElementsByTagName("INPUT")[1].value;
Filters.addFilterRule(li, rule);
},
editAction: function (e) {
const li = e.parentNode;
const action = li.getElementsByTagName("INPUT")[1].value;
Filters.addFilterAction(li, action);
},
addAction: function () {
Filters.addFilterAction();
},
addRule: function () {
Filters.addFilterRule();
},
deleteAction: function () {
$$("#filterDlg_Actions li[class*=Selected]").each(function (e) {
e.parentNode.removeChild(e)
});
},
deleteRule: function () {
$$("#filterDlg_Matches li[class*=Selected]").each(function (e) {
e.parentNode.removeChild(e)
});
},
execute: function () {
if (this.validate()) {
const query = dojo.formToQuery("filter_new_form");
xhrPost("backend.php", query, () => {
if (App.isPrefs()) {
dijit.byId("filterTree").reload();
}
dialog.hide();
});
}
},
href: "backend.php?" + dojo.objectToQuery(query)
});
if (!App.isPrefs()) {
const selectedText = getSelectionText();
const lh = dojo.connect(dialog, "onLoad", function () {
dojo.disconnect(lh);
if (selectedText != "") {
const feed_id = Feeds.activeIsCat() ? 'CAT:' + parseInt(Feeds.getActive()) :
Feeds.getActive();
const rule = {reg_exp: selectedText, feed_id: [feed_id], filter_type: 1};
Filters.addFilterRule(null, dojo.toJson(rule));
} else {
const query = {op: "rpc", method: "getlinktitlebyid", id: Article.getActive()};
xhrPost("backend.php", query, (transport) => {
const reply = JSON.parse(transport.responseText);
let title = false;
if (reply && reply.title) title = reply.title;
if (title || Feeds.getActive() || Feeds.activeIsCat()) {
console.log(title + " " + Feeds.getActive());
const feed_id = Feeds.activeIsCat() ? 'CAT:' + parseInt(Feeds.getActive()) :
Feeds.getActive();
const rule = {reg_exp: title, feed_id: [feed_id], filter_type: 1};
Filters.addFilterRule(null, dojo.toJson(rule));
}
});
}
});
}
dialog.show();
},
};
return Filters;
});

@ -41,14 +41,14 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"],
menu.addChild(new dijit.MenuItem({
label: __("Mark as read"),
onClick: function() {
catchupFeed(this.getParent().row_id);
Feeds.catchupFeed(this.getParent().row_id);
}}));
if (bare_id > 0) {
menu.addChild(new dijit.MenuItem({
label: __("Edit feed"),
onClick: function() {
editFeed(this.getParent().row_id, false);
CommonDialogs.editFeed(this.getParent().row_id, false);
}}));
/* menu.addChild(new dijit.MenuItem({
@ -69,7 +69,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"],
menu.addChild(new dijit.MenuItem({
label: __("Mark as read"),
onClick: function() {
catchupFeed(this.getParent().row_id, true);
Feeds.catchupFeed(this.getParent().row_id, true);
}}));
menu.addChild(new dijit.MenuItem({
@ -97,7 +97,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"],
menu.addChild(new dijit.MenuItem({
label: __("Mark all feeds as read"),
onClick: function() {
catchupAllFeeds();
Feeds.catchupAllFeeds();
}}));
menu.bindDomNode(tnode.domNode);
@ -123,7 +123,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"],
postCreate: function() {
this.connect(this.model, "onChange", "updateCounter");
this.connect(this, "_expandNode", function() {
this.hideRead(getInitParam("hide_read_feeds"), getInitParam("hide_read_shows_special"));
this.hideRead(App.getInitParam("hide_read_feeds"), App.getInitParam("hide_read_shows_special"));
});
this.inherited(arguments);
@ -207,7 +207,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"],
}
}
} catch (e) {
exception_error(e);
App.Error.report(e);
}
},
findNodeParentsAndExpandThem: function(feed, is_cat, root, parents) {
@ -242,7 +242,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"],
this.expandParentNodes(feed, is_cat, parents.slice(0));
}
} catch (e) {
exception_error(e);
App.Error.report(e);
}
},
selectFeed: function(feed, is_cat) {

@ -0,0 +1,642 @@
'use strict'
/* global __, ngettext */
define(["dojo/_base/declare"], function (declare) {
Feeds = {
counters_last_request: 0,
_active_feed_id: 0,
_active_feed_is_cat: false,
infscroll_in_progress: 0,
infscroll_disabled: 0,
_infscroll_timeout: false,
_search_query: false,
last_search_query: [],
_viewfeed_wait_timeout: false,
_counters_prev: [],
// NOTE: this implementation is incomplete
// for general objects but good enough for counters
// http://adripofjavascript.com/blog/drips/object-equality-in-javascript.html
counterEquals: function(a, b) {
// Create arrays of property names
const aProps = Object.getOwnPropertyNames(a);
const bProps = Object.getOwnPropertyNames(b);
// If number of properties is different,
// objects are not equivalent
if (aProps.length != bProps.length) {
return false;
}
for (let i = 0; i < aProps.length; i++) {
const propName = aProps[i];
// If values of same property are not equal,
// objects are not equivalent
if (a[propName] !== b[propName]) {
return false;
}
}
// If we made it this far, objects
// are considered equivalent
return true;
},
resetCounters: function () {
this._counters_prev = [];
},
parseCounters: function (elems) {
for (let l = 0; l < elems.length; l++) {
if (Feeds._counters_prev[l] && this.counterEquals(elems[l], this._counters_prev[l])) {
continue;
}
const id = elems[l].id;
const kind = elems[l].kind;
const ctr = parseInt(elems[l].counter);
const error = elems[l].error;
const has_img = elems[l].has_img;
const updated = elems[l].updated;
const auxctr = parseInt(elems[l].auxcounter);
if (id == "global-unread") {
App.global_unread = ctr;
App.updateTitle();
continue;
}
if (id == "subscribed-feeds") {
/* feeds_found = ctr; */
continue;
}
/*if (this.getUnread(id, (kind == "cat")) != ctr ||
(kind == "cat")) {
}*/
this.setUnread(id, (kind == "cat"), ctr);
this.setValue(id, (kind == "cat"), 'auxcounter', auxctr);
if (kind != "cat") {
this.setValue(id, false, 'error', error);
this.setValue(id, false, 'updated', updated);
if (id > 0) {
if (has_img) {
this.setIcon(id, false,
App.getInitParam("icons_url") + "/" + id + ".ico?" + has_img);
} else {
this.setIcon(id, false, 'images/blank_icon.gif');
}
}
}
}
this.hideOrShowFeeds(App.getInitParam("hide_read_feeds") == 1);
this._counters_prev = elems;
},
reloadCurrent: function(method) {
console.log("reloadCurrent: " + method);
if (this.getActive() != undefined) {
this.open({feed: this.getActive(), is_cat: this.activeIsCat(), method: method});
}
return false; // block unneeded form submits
},
openNextUnread: function() {
const is_cat = this.activeIsCat();
const nuf = this.getNextUnread(this.getActive(), is_cat);
if (nuf) this.open({feed: nuf, is_cat: is_cat});
},
toggle: function() {
Element.toggle("feeds-holder");
const splitter = $("feeds-holder_splitter");
Element.visible("feeds-holder") ? splitter.show() : splitter.hide();
dijit.byId("main").resize();
},
cancelSearch: function() {
this._search_query = "";
this.reloadCurrent();
},
requestCounters: function(force) {
const date = new Date();
const timestamp = Math.round(date.getTime() / 1000);
if (force || timestamp - this.counters_last_request > 5) {
console.log("scheduling request of counters...");
this.counters_last_request = timestamp;
let query = {op: "rpc", method: "getAllCounters", seq: App.next_seq()};
if (!force)
query.last_article_id = App.getInitParam("last_article_id");
xhrPost("backend.php", query, (transport) => {
App.handleRpcJson(transport);
});
} else {
console.log("request_counters: rate limit reached: " + (timestamp - this.counters_last_request));
}
},
reload: function() {
try {
Element.show("feedlistLoading");
this.resetCounters();
if (dijit.byId("feedTree")) {
dijit.byId("feedTree").destroyRecursive();
}
const store = new dojo.data.ItemFileWriteStore({
url: "backend.php?op=pref_feeds&method=getfeedtree&mode=2"
});
// noinspection JSUnresolvedFunction
const treeModel = new fox.FeedStoreModel({
store: store,
query: {
"type": App.getInitParam('enable_feed_cats') == 1 ? "category" : "feed"
},
rootId: "root",
rootLabel: "Feeds",
childrenAttrs: ["items"]
});
// noinspection JSUnresolvedFunction
const tree = new fox.FeedTree({
model: treeModel,
onClick: function (item/*, node*/) {
const id = String(item.id);
const is_cat = id.match("^CAT:");
const feed = id.substr(id.indexOf(":") + 1);
Feeds.open({feed: feed, is_cat: is_cat});
return false;
},
openOnClick: false,
showRoot: false,
persist: true,
id: "feedTree",
}, "feedTree");
const tmph = dojo.connect(dijit.byId('feedMenu'), '_openMyself', function (event) {
console.log(dijit.getEnclosingWidget(event.target));
dojo.disconnect(tmph);
});
$("feeds-holder").appendChild(tree.domNode);
const tmph2 = dojo.connect(tree, 'onLoad', function () {
dojo.disconnect(tmph2);
Element.hide("feedlistLoading");
try {
Feeds.init();
App.setLoadingProgress(25);
} catch (e) {
App.Error.report(e);
}
});
tree.startup();
} catch (e) {
App.Error.report(e);
}
},
init: function() {
console.log("in feedlist init");
App.setLoadingProgress(50);
document.onkeydown = (event) => { App.hotkeyHandler(event) };
window.setInterval(() => { Headlines.catchupBatched() }, 10 * 1000);
if (!this.getActive()) {
this.open({feed: -3});
} else {
this.open({feed: this.getActive(), is_cat: this.activeIsCat()});
}
this.hideOrShowFeeds(App.getInitParam("hide_read_feeds") == 1);
if (App.getInitParam("is_default_pw")) {
console.warn("user password is at default value");
const dialog = new dijit.Dialog({
title: __("Your password is at default value"),
href: "backend.php?op=dlg&method=defaultpasswordwarning",
id: 'infoBox',
style: "width: 600px",
onCancel: function () {
return true;
},
onExecute: function () {
return true;
},
onClose: function () {
return true;
}
});
dialog.show();
}
// bw_limit disables timeout() so we request initial counters separately
if (App.getInitParam("bw_limit") == "1") {
this.requestCounters(true);
} else {
setTimeout(() => {
this.requestCounters(true);
setInterval(() => { this.requestCounters(); }, 60 * 1000)
}, 250);
}
},
activeIsCat: function() {
return !!this._active_feed_is_cat;
},
getActive: function() {
return this._active_feed_id;
},
setActive: function(id, is_cat) {
hash_set('f', id);
hash_set('c', is_cat ? 1 : 0);
this._active_feed_id = id;
this._active_feed_is_cat = is_cat;
$("headlines-frame").setAttribute("feed-id", id);
$("headlines-frame").setAttribute("is-cat", is_cat ? 1 : 0);
this.select(id, is_cat);
PluginHost.run(PluginHost.HOOK_FEED_SET_ACTIVE, [this._active_feed_id, this._active_feed_is_cat]);
},
select: function(feed, is_cat) {
const tree = dijit.byId("feedTree");
if (tree) return tree.selectFeed(feed, is_cat);
},
toggleUnread: function() {
const hide = !(App.getInitParam("hide_read_feeds") == "1");
xhrPost("backend.php", {op: "rpc", method: "setpref", key: "HIDE_READ_FEEDS", value: hide}, () => {
this.hideOrShowFeeds(hide);
App.setInitParam("hide_read_feeds", hide);
});
},
hideOrShowFeeds: function(hide) {
const tree = dijit.byId("feedTree");
if (tree)
return tree.hideRead(hide, App.getInitParam("hide_read_shows_special"));
},
open: function(params) {
const feed = params.feed;
const is_cat = !!params.is_cat || false;
const offset = params.offset || 0;
const viewfeed_debug = params.viewfeed_debug;
const method = params.method;
// this is used to quickly switch between feeds, sets active but xhr is on a timeout
const delayed = params.delayed || false;
if (feed != this.getActive() || this.activeIsCat() != is_cat) {
this._search_query = false;
Article.setActive(0);
}
if (offset != 0) {
if (this.infscroll_in_progress)
return;
this.infscroll_in_progress = 1;
window.clearTimeout(this._infscroll_timeout);
this._infscroll_timeout = window.setTimeout(() => {
console.log('infscroll request timed out, aborting');
this.infscroll_in_progress = 0;
// call scroll handler to maybe repeat infscroll request
Headlines.scrollHandler();
}, 10 * 1000);
}
Form.enable("main_toolbar_form");
let query = Object.assign({op: "feeds", method: "view", feed: feed},
dojo.formToObject("main_toolbar_form"));
if (method) query.m = method;
if (offset > 0) {
if (Headlines.current_first_id) {
query.fid = Headlines.current_first_id;
}
}
if (this._search_query) {
query = Object.assign(query, this._search_query);
}
if (offset != 0) {
query.skip = offset;
// to prevent duplicate feed titles when showing grouped vfeeds
if (Headlines.vgroup_last_feed != undefined) {
query.vgrlf = Headlines.vgroup_last_feed;
}
} else if (!is_cat && feed == this.getActive() && !params.method) {
query.m = "ForceUpdate";
}
Form.enable("main_toolbar_form");
if (!delayed)
if (!this.setExpando(feed, is_cat,
(is_cat) ? 'images/indicator_tiny.gif' : 'images/indicator_white.gif'))
Notify.progress("Loading, please wait...", true);
query.cat = is_cat;
this.setActive(feed, is_cat);
if (viewfeed_debug) {
window.open("backend.php?" +
dojo.objectToQuery(
Object.assign({debug: 1, csrf_token: App.getInitParam("csrf_token")}, query)
));
}
window.clearTimeout(this._viewfeed_wait_timeout);
this._viewfeed_wait_timeout = window.setTimeout(() => {
Headlines.catchupBatched(() => {
xhrPost("backend.php", query, (transport) => {
try {
window.clearTimeout(this._infscroll_timeout);
this.setExpando(feed, is_cat, 'images/blank_icon.gif');
Headlines.onLoaded(transport, offset);
PluginHost.run(PluginHost.HOOK_FEED_LOADED, [feed, is_cat]);
} catch (e) {
App.Error.report(e);
}
});
});
}, delayed ? 250 : 0);
},
catchupAll: function() {
const str = __("Mark all articles as read?");
if (App.getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
Notify.progress("Marking all feeds as read...");
xhrPost("backend.php", {op: "feeds", method: "catchupAll"}, () => {
this.requestCounters(true);
this.reloadCurrent();
});
App.global_unread = 0;
App.updateTitle();
}
},
decrementFeedCounter: function(feed, is_cat) {
let ctr = this.getUnread(feed, is_cat);
if (ctr > 0) {
this.setUnread(feed, is_cat, ctr - 1);
App.global_unread -= 1;
App.updateTitle();
if (!is_cat) {
const cat = parseInt(this.getCategory(feed));
if (!isNaN(cat)) {
ctr = this.getUnread(cat, true);
if (ctr > 0) {
this.setUnread(cat, true, ctr - 1);
}
}
}
}
},
catchupFeed: function(feed, is_cat, mode) {
if (is_cat == undefined) is_cat = false;
let str = false;
switch (mode) {
case "1day":
str = __("Mark %w in %s older than 1 day as read?");
break;
case "1week":
str = __("Mark %w in %s older than 1 week as read?");
break;
case "2week":
str = __("Mark %w in %s older than 2 weeks as read?");
break;
default:
str = __("Mark %w in %s as read?");
}
const mark_what = this.last_search_query && this.last_search_query[0] ? __("search results") : __("all articles");
const fn = this.getName(feed, is_cat);
str = str.replace("%s", fn)
.replace("%w", mark_what);
if (App.getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
return;
}
const catchup_query = {
op: 'rpc', method: 'catchupFeed', feed_id: feed,
is_cat: is_cat, mode: mode, search_query: this.last_search_query[0],
search_lang: this.last_search_query[1]
};
Notify.progress("Loading, please wait...", true);
xhrPost("backend.php", catchup_query, (transport) => {
App.handleRpcJson(transport);
const show_next_feed = App.getInitParam("on_catchup_show_next_feed") == "1";
if (show_next_feed) {
const nuf = this.getNextUnread(feed, is_cat);
if (nuf) {
this.open({feed: nuf, is_cat: is_cat});
}
} else if (feed == this.getActive() && is_cat == this.activeIsCat()) {
this.reloadCurrent();
}
Notify.close();
});
},
catchupCurrent: function(mode) {
this.catchupFeed(this.getActive(), this.activeIsCat(), mode);
},
catchupFeedInGroup: function(id) {
const title = this.getName(id);
const str = __("Mark all articles in %s as read?").replace("%s", title);
if (App.getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
const rows = $$("#headlines-frame > div[id*=RROW][data-orig-feed-id='" + id + "']");
if (rows.length > 0) {
rows.each(function (row) {
row.removeClassName("Unread");
if (row.getAttribute("data-article-id") != Article.getActive()) {
new Effect.Fade(row, {duration: 0.5});
}
});
const feedTitles = $$("#headlines-frame > div[class='feed-title']");
for (let i = 0; i < feedTitles.length; i++) {
if (feedTitles[i].getAttribute("data-feed-id") == id) {
if (i < feedTitles.length - 1) {
new Effect.Fade(feedTitles[i], {duration: 0.5});
}
break;
}
}
Headlines.updateFloatingTitle(true);
}
Notify.progress("Loading, please wait...", true);
xhrPost("backend.php", {op: "rpc", method: "catchupFeed", feed_id: id, is_cat: false}, (transport) => {
App.handleRpcJson(transport);
});
}
},
getUnread: function(feed, is_cat) {
try {
const tree = dijit.byId("feedTree");
if (tree && tree.model)
return tree.model.getFeedUnread(feed, is_cat);
} catch (e) {
//
}
return -1;
},
getCategory: function(feed) {
try {
const tree = dijit.byId("feedTree");
if (tree && tree.model)
return tree.getFeedCategory(feed);
} catch (e) {
//
}
return false;
},
getName: function(feed, is_cat) {
if (isNaN(feed)) return feed; // it's a tag
const tree = dijit.byId("feedTree");
if (tree && tree.model)
return tree.model.getFeedValue(feed, is_cat, 'name');
},
setUnread: function(feed, is_cat, unread) {
const tree = dijit.byId("feedTree");
if (tree && tree.model)
return tree.model.setFeedUnread(feed, is_cat, unread);
},
setValue: function(feed, is_cat, key, value) {
try {
const tree = dijit.byId("feedTree");
if (tree && tree.model)
return tree.model.setFeedValue(feed, is_cat, key, value);
} catch (e) {
//
}
},
getValue: function(feed, is_cat, key) {
try {
const tree = dijit.byId("feedTree");
if (tree && tree.model)
return tree.model.getFeedValue(feed, is_cat, key);
} catch (e) {
//
}
return '';
},
setIcon: function(feed, is_cat, src) {
const tree = dijit.byId("feedTree");
if (tree) return tree.setFeedIcon(feed, is_cat, src);
},
setExpando: function(feed, is_cat, src) {
const tree = dijit.byId("feedTree");
if (tree) return tree.setFeedExpandoIcon(feed, is_cat, src);
return false;
},
getNextUnread: function(feed, is_cat) {
const tree = dijit.byId("feedTree");
const nuf = tree.model.getNextUnreadFeed(feed, is_cat);
if (nuf)
return tree.model.store.getValue(nuf, 'bare_id');
},
search: function() {
const query = "backend.php?op=feeds&method=search&param=" +
encodeURIComponent(Feeds.getActive() + ":" + Feeds.activeIsCat());
if (dijit.byId("searchDlg"))
dijit.byId("searchDlg").destroyRecursive();
const dialog = new dijit.Dialog({
id: "searchDlg",
title: __("Search"),
style: "width: 600px",
execute: function () {
if (this.validate()) {
Feeds._search_query = this.attr('value');
this.hide();
Feeds.reloadCurrent();
}
},
href: query
});
dialog.show();
},
updateRandom: function() {
console.log("in update_random_feed");
xhrPost("backend.php", {op: "rpc", method: "updateRandom"}, (transport) => {
App.handleRpcJson(transport, true);
});
},
};
return Feeds;
});

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
// based on http://www.velvetcache.org/2010/08/19/a-simple-javascript-hooks-system
const PluginHost = {
PluginHost = {
HOOK_ARTICLE_RENDERED: 1,
HOOK_ARTICLE_RENDERED_CDM: 2,
HOOK_ARTICLE_SET_ACTIVE: 3,
@ -19,10 +19,10 @@ const PluginHost = {
this.hooks[name].push(callback);
},
run: function (name, args) {
console.warn('PluginHost::run ' + name);
//console.warn('PluginHost::run ' + name);
if (typeof(this.hooks[name]) != 'undefined')
for (var i = 0; i < this.hooks[name].length; i++)
for (let i = 0; i < this.hooks[name].length; i++)
if (!this.hooks[name][i](args)) break;
}
};

@ -35,14 +35,14 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], functio
menu.addChild(new dijit.MenuItem({
label: __("Edit category"),
onClick: function() {
editCat(this.getParent().row_id, this.getParent().item, null);
dijit.byId("feedTree").editCategory(this.getParent().row_id, this.getParent().item, null);
}}));
menu.addChild(new dijit.MenuItem({
label: __("Remove category"),
onClick: function() {
removeCategory(this.getParent().row_id, this.getParent().item);
dijit.byId("feedTree").removeCategory(this.getParent().row_id, this.getParent().item);
}}));
menu.bindDomNode(tnode.domNode);
@ -55,13 +55,13 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], functio
menu.addChild(new dijit.MenuItem({
label: __("Edit feed"),
onClick: function() {
editFeed(this.getParent().row_id);
CommonDialogs.editFeed(this.getParent().row_id);
}}));
menu.addChild(new dijit.MenuItem({
label: __("Unsubscribe"),
onClick: function() {
unsubscribeFeed(this.getParent().row_id, this.getParent().item.name);
CommonDialogs.unsubscribeFeed(this.getParent().row_id, this.getParent().item.name);
}}));
menu.bindDomNode(tnode.domNode);
@ -82,6 +82,15 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], functio
getIconClass: function (item, opened) {
return (!item || this.model.store.getValue(item, 'type') == 'category') ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "feedIcon";
},
reload: function() {
const searchElem = $("feed_search");
let search = (searchElem) ? searchElem.value : "";
xhrPost("backend.php", { op: "pref-feeds", search: search }, (transport) => {
dijit.byId('feedConfigTab').attr('content', transport.responseText);
Notify.close();
});
},
checkItemAcceptance: function(target, source, position) {
const item = dijit.getEnclosingWidget(target).item;
@ -109,6 +118,291 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], functio
(id.match("root") && position == "over"));
}
},
resetFeedOrder: function() {
Notify.progress("Loading, please wait...");
xhrPost("backend.php", {op: "pref-feeds", method: "feedsortreset"}, () => {
this.reload();
});
},
resetCatOrder: function() {
Notify.progress("Loading, please wait...");
xhrPost("backend.php", {op: "pref-feeds", method: "catsortreset"}, () => {
this.reload();
});
},
removeCategory: function(id, item) {
if (confirm(__("Remove category %s? Any nested feeds would be placed into Uncategorized.").replace("%s", item.name))) {
Notify.progress("Removing category...");
xhrPost("backend.php", {op: "pref-feeds", method: "removeCat", ids: id}, () => {
Notify.close();
this.reload();
});
}
},
removeSelectedFeeds: function() {
const sel_rows = this.getSelectedFeeds();
if (sel_rows.length > 0) {
if (confirm(__("Unsubscribe from selected feeds?"))) {
Notify.progress("Unsubscribing from selected feeds...", true);
const query = {
op: "pref-feeds", method: "remove",
ids: sel_rows.toString()
};
xhrPost("backend.php", query, () => {
this.reload();
});
}
} else {
alert(__("No feeds selected."));
}
return false;
},
checkInactiveFeeds: function() {
xhrPost("backend.php", {op: "pref-feeds", method: "getinactivefeeds"}, (transport) => {
if (parseInt(transport.responseText) > 0) {
Element.show(dijit.byId("pref_feeds_inactive_btn").domNode);
}
});
},
getSelectedCategories: function() {
const tree = this;
const items = tree.model.getCheckedItems();
const rv = [];
items.each(function (item) {
if (item.id[0].match("CAT:"))
rv.push(tree.model.store.getValue(item, 'bare_id'));
});
return rv;
},
removeSelectedCategories: function() {
const sel_rows = this.getSelectedCategories();
if (sel_rows.length > 0) {
if (confirm(__("Remove selected categories?"))) {
Notify.progress("Removing selected categories...");
const query = {
op: "pref-feeds", method: "removeCat",
ids: sel_rows.toString()
};
xhrPost("backend.php", query, () => {
this.reload();
});
}
} else {
alert(__("No categories selected."));
}
return false;
},
getSelectedFeeds: function() {
const tree = this;
const items = tree.model.getCheckedItems();
const rv = [];
items.each(function (item) {
if (item.id[0].match("FEED:"))
rv.push(tree.model.store.getValue(item, 'bare_id'));
});
return rv;
},
editSelectedFeed: function() {
const rows = this.getSelectedFeeds();
if (rows.length == 0) {
alert(__("No feeds selected."));
return;
}
Notify.close();
if (rows.length > 1) {
return this.editMultiple();
} else {
CommonDialogs.editFeed(rows[0], {});
}
},
editMultiple: function() {
const rows = this.getSelectedFeeds();
if (rows.length == 0) {
alert(__("No feeds selected."));
return;
}
Notify.progress("Loading, please wait...");
if (dijit.byId("feedEditDlg"))
dijit.byId("feedEditDlg").destroyRecursive();
xhrPost("backend.php", {op: "pref-feeds", method: "editfeeds", ids: rows.toString()}, (transport) => {
Notify.close();
const dialog = new dijit.Dialog({
id: "feedEditDlg",
title: __("Edit Multiple Feeds"),
style: "width: 600px",
getChildByName: function (name) {
let rv = null;
this.getChildren().each(
function (child) {
if (child.name == name) {
rv = child;
return;
}
});
return rv;
},
toggleField: function (checkbox, elem, label) {
this.getChildByName(elem).attr('disabled', !checkbox.checked);
if ($(label))
if (checkbox.checked)
$(label).removeClassName('insensitive');
else
$(label).addClassName('insensitive');
},
execute: function () {
if (this.validate() && confirm(__("Save changes to selected feeds?"))) {
const query = this.attr('value');
/* normalize unchecked checkboxes because [] is not serialized */
Object.keys(query).each((key) => {
let val = query[key];
if (typeof val == "object" && val.length == 0)
query[key] = ["off"];
});
Notify.progress("Saving data...", true);
xhrPost("backend.php", query, () => {
dialog.hide();
dijit.byId("feedTree").reload();
});
}
},
content: transport.responseText
});
dialog.show();
});
},
editCategory: function(id, item) {
// uncategorized
if (String(item.id) == "CAT:0")
return;
const new_name = prompt(__('Rename category to:'), item.name);
if (new_name && new_name != item.name) {
Notify.progress("Loading, please wait...");
xhrPost("backend.php", { op: 'pref-feeds', method: 'renamecat', id: id, title: new_name }, () => {
this.reload();
});
}
},
createCategory: function() {
const title = prompt(__("Category title:"));
if (title) {
Notify.progress("Creating category...");
xhrPost("backend.php", {op: "pref-feeds", method: "addCat", cat: title}, () => {
Notify.close();
this.reload();
});
}
},
batchSubscribe: function() {
const query = "backend.php?op=pref-feeds&method=batchSubscribe";
// overlapping widgets
if (dijit.byId("batchSubDlg")) dijit.byId("batchSubDlg").destroyRecursive();
if (dijit.byId("feedAddDlg")) dijit.byId("feedAddDlg").destroyRecursive();
const dialog = new dijit.Dialog({
id: "batchSubDlg",
title: __("Batch subscribe"),
style: "width: 600px",
execute: function () {
if (this.validate()) {
Notify.progress(__("Subscribing to feeds..."), true);
xhrPost("backend.php", this.attr('value'), () => {
Notify.close();
dijit.byId("feedTree").reload();
dialog.hide();
});
}
},
href: query
});
dialog.show();
},
showInactiveFeeds: function() {
const query = "backend.php?op=pref-feeds&method=inactiveFeeds";
if (dijit.byId("inactiveFeedsDlg"))
dijit.byId("inactiveFeedsDlg").destroyRecursive();
const dialog = new dijit.Dialog({
id: "inactiveFeedsDlg",
title: __("Feeds without recent updates"),
style: "width: 600px",
getSelectedFeeds: function () {
return Tables.getSelected("prefInactiveFeedList");
},
removeSelected: function () {
const sel_rows = this.getSelectedFeeds();
if (sel_rows.length > 0) {
if (confirm(__("Remove selected feeds?"))) {
Notify.progress("Removing selected feeds...", true);
const query = {
op: "pref-feeds", method: "remove",
ids: sel_rows.toString()
};
xhrPost("backend.php", query, () => {
Notify.close();
dijit.byId("feedTree").reload();
dialog.hide();
});
}
} else {
alert(__("No feeds selected."));
}
},
execute: function () {
if (this.validate()) {
}
},
href: query
});
dialog.show();
}
});
});

@ -75,8 +75,190 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], functio
this.inherited(arguments);
this.tree.model.store.save();
},
});
getSelectedFilters: function() {
const tree = this;
const items = tree.model.getCheckedItems();
const rv = [];
items.each(function (item) {
rv.push(tree.model.store.getValue(item, 'bare_id'));
});
return rv;
},
reload: function() {
const user_search = $("filter_search");
let search = "";
if (user_search) { search = user_search.value; }
xhrPost("backend.php", { op: "pref-filters", search: search }, (transport) => {
dijit.byId('filterConfigTab').attr('content', transport.responseText);
Notify.close();
});
},
resetFilterOrder: function() {
Notify.progress("Loading, please wait...");
xhrPost("backend.php", {op: "pref-filters", method: "filtersortreset"}, () => {
this.reload();
});
},
joinSelectedFilters: function() {
const rows = getSelectedFilters();
if (rows.length == 0) {
alert(__("No filters selected."));
return;
}
if (confirm(__("Combine selected filters?"))) {
Notify.progress("Joining filters...");
xhrPost("backend.php", {op: "pref-filters", method: "join", ids: rows.toString()}, () => {
this.reload();
});
}
},
editSelectedFilter: function() {
const rows = this.getSelectedFilters();
if (rows.length == 0) {
alert(__("No filters selected."));
return;
}
if (rows.length > 1) {
alert(__("Please select only one filter."));
return;
}
Notify.close();
this.editFilter(rows[0]);
},
editFilter: function(id) {
const query = "backend.php?op=pref-filters&method=edit&id=" + encodeURIComponent(id);
if (dijit.byId("feedEditDlg"))
dijit.byId("feedEditDlg").destroyRecursive();
if (dijit.byId("filterEditDlg"))
dijit.byId("filterEditDlg").destroyRecursive();
const dialog = new dijit.Dialog({
id: "filterEditDlg",
title: __("Edit Filter"),
style: "width: 600px",
test: function () {
const query = "backend.php?" + dojo.formToQuery("filter_edit_form") + "&savemode=test";
Filters.editFilterTest(query);
},
selectRules: function (select) {
$$("#filterDlg_Matches input[type=checkbox]").each(function (e) {
e.checked = select;
if (select)
e.parentNode.addClassName("Selected");
else
e.parentNode.removeClassName("Selected");
});
},
selectActions: function (select) {
$$("#filterDlg_Actions input[type=checkbox]").each(function (e) {
e.checked = select;
if (select)
e.parentNode.addClassName("Selected");
else
e.parentNode.removeClassName("Selected");
});
},
editRule: function (e) {
const li = e.parentNode;
const rule = li.getElementsByTagName("INPUT")[1].value;
Filters.addFilterRule(li, rule);
},
editAction: function (e) {
const li = e.parentNode;
const action = li.getElementsByTagName("INPUT")[1].value;
Filters.addFilterAction(li, action);
},
removeFilter: function () {
const msg = __("Remove filter?");
if (confirm(msg)) {
this.hide();
Notify.progress("Removing filter...");
const query = {op: "pref-filters", method: "remove", ids: this.attr('value').id};
xhrPost("backend.php", query, () => {
dijit.byId("filterTree").reload();
});
}
},
addAction: function () {
Filters.addFilterAction();
},
addRule: function () {
Filters.addFilterRule();
},
deleteAction: function () {
$$("#filterDlg_Actions li[class*=Selected]").each(function (e) {
e.parentNode.removeChild(e)
});
},
deleteRule: function () {
$$("#filterDlg_Matches li[class*=Selected]").each(function (e) {
e.parentNode.removeChild(e)
});
},
execute: function () {
if (this.validate()) {
Notify.progress("Saving data...", true);
xhrPost("backend.php", dojo.formToObject("filter_edit_form"), () => {
dialog.hide();
dijit.byId("filterTree").reload();
});
}
},
href: query
});
dialog.show();
},
removeSelectedFilters: function() {
const sel_rows = this.getSelectedFilters();
if (sel_rows.length > 0) {
if (confirm(__("Remove selected filters?"))) {
Notify.progress("Removing selected filters...");
const query = {
op: "pref-filters", method: "remove",
ids: sel_rows.toString()
};
xhrPost("backend.php", query, () => {
this.reload();
});
}
} else {
alert(__("No filters selected."));
}
return false;
},
});
});

@ -0,0 +1,230 @@
define(["dojo/_base/declare"], function (declare) {
Helpers = {
clearFeedAccessKeys: function() {
if (confirm(__("This will invalidate all previously generated feed URLs. Continue?"))) {
Notify.progress("Clearing URLs...");
xhrPost("backend.php", {op: "pref-feeds", method: "clearKeys"}, () => {
Notify.info("Generated URLs cleared.");
});
}
return false;
},
updateEventLog: function() {
xhrPost("backend.php", { op: "pref-system" }, (transport) => {
dijit.byId('systemConfigTab').attr('content', transport.responseText);
Notify.close();
});
},
clearEventLog: function() {
if (confirm(__("Clear event log?"))) {
Notify.progress("Loading, please wait...");
xhrPost("backend.php", {op: "pref-system", method: "clearLog"}, () => {
this.updateEventLog();
});
}
},
editProfiles: function() {
if (dijit.byId("profileEditDlg"))
dijit.byId("profileEditDlg").destroyRecursive();
const query = "backend.php?op=pref-prefs&method=editPrefProfiles";
// noinspection JSUnusedGlobalSymbols
const dialog = new dijit.Dialog({
id: "profileEditDlg",
title: __("Settings Profiles"),
style: "width: 600px",
getSelectedProfiles: function () {
return Tables.getSelected("prefFeedProfileList");
},
removeSelected: function () {
const sel_rows = this.getSelectedProfiles();
if (sel_rows.length > 0) {
if (confirm(__("Remove selected profiles? Active and default profiles will not be removed."))) {
Notify.progress("Removing selected profiles...", true);
const query = {
op: "rpc", method: "remprofiles",
ids: sel_rows.toString()
};
xhrPost("backend.php", query, () => {
Notify.close();
Helpers.editProfiles();
});
}
} else {
alert(__("No profiles selected."));
}
},
activateProfile: function () {
const sel_rows = this.getSelectedProfiles();
if (sel_rows.length == 1) {
if (confirm(__("Activate selected profile?"))) {
Notify.progress("Loading, please wait...");
xhrPost("backend.php", {op: "rpc", method: "setprofile", id: sel_rows.toString()}, () => {
window.location.reload();
});
}
} else {
alert(__("Please choose a profile to activate."));
}
},
addProfile: function () {
if (this.validate()) {
Notify.progress("Creating profile...", true);
const query = {op: "rpc", method: "addprofile", title: dialog.attr('value').newprofile};
xhrPost("backend.php", query, () => {
Notify.close();
Helpers.editProfiles();
});
}
},
execute: function () {
if (this.validate()) {
}
},
href: query
});
dialog.show();
},
customizeCSS: function() {
const query = "backend.php?op=pref-prefs&method=customizeCSS";
if (dijit.byId("cssEditDlg"))
dijit.byId("cssEditDlg").destroyRecursive();
const dialog = new dijit.Dialog({
id: "cssEditDlg",
title: __("Customize stylesheet"),
style: "width: 600px",
execute: function () {
Notify.progress('Saving data...', true);
xhrPost("backend.php", this.attr('value'), () => {
window.location.reload();
});
},
href: query
});
dialog.show();
},
confirmReset: function() {
if (confirm(__("Reset to defaults?"))) {
xhrPost("backend.php", {op: "pref-prefs", method: "resetconfig"}, (transport) => {
Helpers.refresh();
Notify.info(transport.responseText);
});
}
},
clearPluginData: function(name) {
if (confirm(__("Clear stored data for this plugin?"))) {
Notify.progress("Loading, please wait...");
xhrPost("backend.php", {op: "pref-prefs", method: "clearplugindata", name: name}, () => {
Helpers.refresh();
});
}
},
refresh: function() {
xhrPost("backend.php", { op: "pref-prefs" }, (transport) => {
dijit.byId('genConfigTab').attr('content', transport.responseText);
Notify.close();
});
},
OPML: {
import: function() {
const opml_file = $("opml_file");
if (opml_file.value.length == 0) {
alert(__("Please choose an OPML file first."));
return false;
} else {
Notify.progress("Importing, please wait...", true);
Element.show("upload_iframe");
return true;
}
},
onImportComplete: function(iframe) {
if (!iframe.contentDocument.body.innerHTML) return false;
Element.show(iframe);
Notify.close();
if (dijit.byId('opmlImportDlg'))
dijit.byId('opmlImportDlg').destroyRecursive();
const content = iframe.contentDocument.body.innerHTML;
const dialog = new dijit.Dialog({
id: "opmlImportDlg",
title: __("OPML Import"),
style: "width: 600px",
onCancel: function () {
window.location.reload();
},
execute: function () {
window.location.reload();
},
content: content
});
dojo.connect(dialog, "onShow", function () {
Element.hide(iframe);
});
dialog.show();
},
export: function() {
console.log("export");
window.open("backend.php?op=opml&method=export&" + dojo.formToQuery("opmlExportForm"));
},
changeKey: function() {
if (confirm(__("Replace current OPML publishing address with a new one?"))) {
Notify.progress("Trying to change address...", true);
xhrJson("backend.php", {op: "pref-feeds", method: "regenOPMLKey"}, (reply) => {
if (reply) {
const new_link = reply.link;
const e = $('pub_opml_url');
if (new_link) {
e.href = new_link;
e.innerHTML = new_link;
new Effect.Highlight(e);
Notify.close();
} else {
Notify.error("Could not change feed URL.");
}
}
});
}
return false;
},
}
};
return Helpers;
});

@ -37,7 +37,129 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree", "dijit/f
getIconClass: function (item, opened) {
return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "invisible";
},
});
getSelectedLabels: function() {
const tree = this;
const items = tree.model.getCheckedItems();
const rv = [];
items.each(function(item) {
rv.push(tree.model.store.getValue(item, 'bare_id'));
});
return rv;
},
reload: function() {
xhrPost("backend.php", { op: "pref-labels" }, (transport) => {
dijit.byId('labelConfigTab').attr('content', transport.responseText);
Notify.close();
});
},
editLabel: function(id) {
const query = "backend.php?op=pref-labels&method=edit&id=" +
encodeURIComponent(id);
if (dijit.byId("labelEditDlg"))
dijit.byId("labelEditDlg").destroyRecursive();
const dialog = new dijit.Dialog({
id: "labelEditDlg",
title: __("Label Editor"),
style: "width: 600px",
setLabelColor: function (id, fg, bg) {
let kind = '';
let color = '';
if (fg && bg) {
kind = 'both';
} else if (fg) {
kind = 'fg';
color = fg;
} else if (bg) {
kind = 'bg';
color = bg;
}
const e = $("LICID-" + id);
if (e) {
if (fg) e.style.color = fg;
if (bg) e.style.backgroundColor = bg;
}
const query = {
op: "pref-labels", method: "colorset", kind: kind,
ids: id, fg: fg, bg: bg, color: color
};
xhrPost("backend.php", query, () => {
dijit.byId("filterTree").reload(); // maybe there's labels in there
});
},
execute: function () {
if (this.validate()) {
const caption = this.attr('value').caption;
const fg_color = this.attr('value').fg_color;
const bg_color = this.attr('value').bg_color;
dijit.byId('labelTree').setNameById(id, caption);
this.setLabelColor(id, fg_color, bg_color);
this.hide();
xhrPost("backend.php", this.attr('value'), () => {
dijit.byId("filterTree").reload(); // maybe there's labels in there
});
}
},
href: query
});
dialog.show();
},
resetColors: function() {
const labels = this.getSelectedLabels();
if (labels.length > 0) {
if (confirm(__("Reset selected labels to default colors?"))) {
const query = {
op: "pref-labels", method: "colorreset",
ids: labels.toString()
};
xhrPost("backend.php", query, () => {
this.reload();
});
}
} else {
alert(__("No labels selected."));
}
},
removeSelected: function() {
const sel_rows = this.getSelectedLabels();
if (sel_rows.length > 0) {
if (confirm(__("Remove selected labels?"))) {
Notify.progress("Removing selected labels...");
const query = {
op: "pref-labels", method: "remove",
ids: sel_rows.toString()
};
xhrPost("backend.php", query, () => {
this.reload();
});
}
} else {
alert(__("No labels selected."));
}
return false;
}
});
});

@ -0,0 +1,122 @@
'use strict'
/* global __, ngettext */
define(["dojo/_base/declare"], function (declare) {
Users = {
reload: function(sort) {
const user_search = $("user_search");
const search = user_search ? user_search.value : "";
xhrPost("backend.php", { op: "pref-users", sort: sort, search: search }, (transport) => {
dijit.byId('userConfigTab').attr('content', transport.responseText);
Notify.close();
});
},
add: function() {
const login = prompt(__("Please enter username:"), "");
if (login) {
Notify.progress("Adding user...");
xhrPost("backend.php", {op: "pref-users", method: "add", login: login}, (transport) => {
alert(transport.responseText);
Users.reload();
});
}
},
edit: function(id) {
const query = "backend.php?op=pref-users&method=edit&id=" +
encodeURIComponent(id);
if (dijit.byId("userEditDlg"))
dijit.byId("userEditDlg").destroyRecursive();
const dialog = new dijit.Dialog({
id: "userEditDlg",
title: __("User Editor"),
style: "width: 600px",
execute: function () {
if (this.validate()) {
Notify.progress("Saving data...", true);
xhrPost("backend.php", dojo.formToObject("user_edit_form"), (transport) => {
dialog.hide();
Users.reload();
});
}
},
href: query
});
dialog.show();
},
resetSelected: function() {
const rows = this.getSelection();
if (rows.length == 0) {
alert(__("No users selected."));
return;
}
if (rows.length > 1) {
alert(__("Please select one user."));
return;
}
if (confirm(__("Reset password of selected user?"))) {
Notify.progress("Resetting password for selected user...");
const id = rows[0];
xhrPost("backend.php", {op: "pref-users", method: "resetPass", id: id}, (transport) => {
Notify.close();
alert(transport.responseText);
});
}
},
removeSelected: function() {
const sel_rows = this.getSelection();
if (sel_rows.length > 0) {
if (confirm(__("Remove selected users? Neither default admin nor your account will be removed."))) {
Notify.progress("Removing selected users...");
const query = {
op: "pref-users", method: "remove",
ids: sel_rows.toString()
};
xhrPost("backend.php", query, () => {
this.reload();
});
}
} else {
alert(__("No users selected."));
}
},
editSelected: function() {
const rows = this.getSelection();
if (rows.length == 0) {
alert(__("No users selected."));
return;
}
if (rows.length > 1) {
alert(__("Please select one user."));
return;
}
this.edit(rows[0]);
},
getSelection :function() {
return Tables.getSelected("prefUserList");
}
}
return Users;
});

@ -0,0 +1,311 @@
'use strict'
/* global dijit, __ */
let _label_base_index = -1024;
let loading_progress = 0;
/* error reporting shim */
// TODO: deprecated; remove
function exception_error(e, e_compat, filename, lineno, colno) {
if (typeof e == "string")
e = e_compat;
App.Error.report(e, {filename: filename, lineno: lineno, colno: colno});
}
/* xhr shorthand helpers */
function xhrPost(url, params, complete) {
console.log("xhrPost:", params);
return new Ajax.Request(url, {
parameters: params,
onComplete: complete
});
}
function xhrJson(url, params, complete) {
return xhrPost(url, params, (reply) => {
try {
const obj = JSON.parse(reply.responseText);
complete(obj);
} catch (e) {
console.error("xhrJson", e, reply);
complete(null);
}
})
}
/* add method to remove element from array */
Array.prototype.remove = function(s) {
for (let i=0; i < this.length; i++) {
if (s == this[i]) this.splice(i, 1);
}
};
/* common helpers not worthy of separate Dojo modules */
const Lists = {
onRowChecked: function(elem) {
const checked = elem.domNode ? elem.attr("checked") : elem.checked;
// account for dojo checkboxes
elem = elem.domNode || elem;
const row = elem.up("li");
if (row)
checked ? row.addClassName("Selected") : row.removeClassName("Selected");
}
};
// noinspection JSUnusedGlobalSymbols
const Tables = {
onRowChecked: function(elem) {
// account for dojo checkboxes
const checked = elem.domNode ? elem.attr("checked") : elem.checked;
elem = elem.domNode || elem;
const row = elem.up("tr");
if (row)
checked ? row.addClassName("Selected") : row.removeClassName("Selected");
},
select: function(elemId, selected) {
$(elemId).select("tr").each((row) => {
const checkNode = row.select(".dijitCheckBox,input[type=checkbox]")[0];
if (checkNode) {
const widget = dijit.getEnclosingWidget(checkNode);
if (widget) {
widget.attr("checked", selected);
} else {
checkNode.checked = selected;
}
this.onRowChecked(widget);
}
});
},
getSelected: function(elemId) {
const rv = [];
$(elemId).select("tr").each((row) => {
if (row.hasClassName("Selected")) {
// either older prefix-XXX notation or separate attribute
const rowId = row.getAttribute("data-row-id") || row.id.replace(/^[A-Z]*?-/, "");
if (!isNaN(rowId))
rv.push(parseInt(rowId));
}
});
return rv;
}
};
const Cookie = {
set: function (name, value, lifetime) {
const d = new Date();
d.setTime(d.getTime() + lifetime * 1000);
const expires = "expires=" + d.toUTCString();
document.cookie = name + "=" + encodeURIComponent(value) + "; " + expires;
},
get: function (name) {
name = name + "=";
const ca = document.cookie.split(';');
for (let i=0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1);
if (c.indexOf(name) == 0) return decodeURIComponent(c.substring(name.length, c.length));
}
return "";
},
delete: function(name) {
const expires = "expires=Thu, 01-Jan-1970 00:00:01 GMT";
document.cookie = name + "=" + "" + "; " + expires;
}
};
/* runtime notifications */
const Notify = {
KIND_GENERIC: 0,
KIND_INFO: 1,
KIND_ERROR: 2,
KIND_PROGRESS: 3,
timeout: 0,
default_timeout: 5 * 1000,
close: function() {
this.msg("");
},
msg: function(msg, keep, kind) {
kind = kind || this.KIND_GENERIC;
keep = keep || false;
const notify = $("notify");
window.clearTimeout(this.timeout);
if (!msg) {
notify.removeClassName("visible");
return;
}
let msgfmt = "<span class=\"msg\">%s</span>".replace("%s", __(msg));
let icon = false;
notify.className = "notify";
console.warn('notify', msg, kind);
switch (kind) {
case this.KIND_INFO:
notify.addClassName("notify_info")
icon = App.getInitParam("icon_information");
break;
case this.KIND_ERROR:
notify.addClassName("notify_error");
icon = App.getInitParam("icon_alert");
break;
case this.KIND_PROGRESS:
notify.addClassName("notify_progress");
icon = App.getInitParam("icon_indicator_white")
break;
}
if (icon) msgfmt = "<span><img src=\"%s\"></span>".replace("%s", icon) + msgfmt;
msgfmt += (" <span><img src=\"%s\" class='close' title=\"" +
__("Click to close") + "\" onclick=\"Notify.close()\"></span>")
.replace("%s", App.getInitParam("icon_cross"));
notify.innerHTML = msgfmt;
notify.addClassName("visible");
if (!keep)
this.timeout = window.setTimeout(() => {
notify.removeClassName("visible");
}, this.default_timeout);
},
info: function(msg, keep) {
keep = keep || false;
this.msg(msg, keep, this.KIND_INFO);
},
progress: function(msg, keep) {
keep = keep || true;
this.msg(msg, keep, this.KIND_PROGRESS);
},
error: function(msg, keep) {
keep = keep || true;
this.msg(msg, keep, this.KIND_ERROR);
}
};
// noinspection JSUnusedGlobalSymbols
function displayIfChecked(checkbox, elemId) {
if (checkbox.checked) {
Effect.Appear(elemId, {duration : 0.5});
} else {
Effect.Fade(elemId, {duration : 0.5});
}
}
function fatalError(code, msg, ext_info) {
if (code == 6) {
window.location.href = "index.php";
} else if (code == 5) {
window.location.href = "public.php?op=dbupdate";
} else {
if (msg == "") msg = "Unknown error";
if (ext_info) {
if (ext_info.responseText) {
ext_info = ext_info.responseText;
}
}
/* global ERRORS */
if (ERRORS && ERRORS[code] && !msg) {
msg = ERRORS[code];
}
let content = "<div><b>Error code:</b> " + code + "</div>" +
"<p>" + msg + "</p>";
if (ext_info) {
content = content + "<div><b>Additional information:</b></div>" +
"<textarea style='width: 100%' readonly=\"1\">" +
ext_info + "</textarea>";
}
const dialog = new dijit.Dialog({
title: "Fatal error",
style: "width: 600px",
content: content});
dialog.show();
}
return false;
}
/* function strip_tags(s) {
return s.replace(/<\/?[^>]+(>|$)/g, "");
} */
// noinspection JSUnusedGlobalSymbols
function label_to_feed_id(label) {
return _label_base_index - 1 - Math.abs(label);
}
// noinspection JSUnusedGlobalSymbols
function feed_to_label_id(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
function getSelectionText() {
let text = "";
if (typeof window.getSelection != "undefined") {
const sel = window.getSelection();
if (sel.rangeCount) {
const container = document.createElement("div");
for (let i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
text = container.innerHTML;
}
} else if (typeof document.selection != "undefined") {
if (document.selection.type == "Text") {
text = document.selection.createRange().textText;
}
}
return text.stripTags();
}
// noinspection JSUnusedGlobalSymbols
function popupOpenUrl(url) {
const w = window.open("");
w.opener = null;
w.location = url;
}
// noinspection JSUnusedGlobalSymbols
function popupOpenArticle(id) {
const w = window.open("",
"ttrss_article_popup",
"height=900,width=900,resizable=yes,status=no,location=no,menubar=no,directories=no,scrollbars=yes,toolbar=no");
w.opener = null;
w.location = "backend.php?op=article&method=view&mode=raw&html=1&zoom=1&id=" + id + "&csrf_token=" + App.getInitParam("csrf_token");
}

@ -1,532 +0,0 @@
let infscroll_in_progress = 0;
let infscroll_disabled = 0;
let _infscroll_timeout = false;
let _search_query = false;
let _viewfeed_wait_timeout = false;
let counters_last_request = 0;
let _counters_prev = [];
function resetCounterCache() {
_counters_prev = [];
}
function loadMoreHeadlines() {
const view_mode = document.forms["main_toolbar_form"].view_mode.value;
const unread_in_buffer = $$("#headlines-frame > div[id*=RROW][class*=Unread]").length;
const num_all = $$("#headlines-frame > div[id*=RROW]").length;
const num_unread = getFeedUnread(getActiveFeedId(), activeFeedIsCat());
// TODO implement marked & published
let offset = num_all;
switch (view_mode) {
case "marked":
case "published":
console.warn("loadMoreHeadlines: ", view_mode, "not implemented");
break;
case "unread":
offset = unread_in_buffer;
break;
case "adaptive":
if (!(getActiveFeedId() == -1 && !activeFeedIsCat()))
offset = num_unread > 0 ? unread_in_buffer : num_all;
break;
}
console.log("loadMoreHeadlines, offset=", offset);
viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat(), offset: offset, infscroll_req: true});
}
function cleanup_memory(root) {
const dijits = dojo.query("[widgetid]", dijit.byId(root).domNode).map(dijit.byNode);
dijits.each(function (d) {
dojo.destroy(d.domNode);
});
$$("#" + root + " *").each(function (i) {
i.parentNode ? i.parentNode.removeChild(i) : true;
});
}
function viewfeed(params) {
const feed = params.feed;
const is_cat = !!params.is_cat || false;
const offset = params.offset || 0;
const viewfeed_debug = params.viewfeed_debug;
const method = params.method;
// this is used to quickly switch between feeds, sets active but xhr is on a timeout
const delayed = params.delayed || false;
if (feed != getActiveFeedId() || activeFeedIsCat() != is_cat) {
_search_query = false;
setActiveArticleId(0);
}
if (offset != 0) {
if (infscroll_in_progress)
return;
infscroll_in_progress = 1;
window.clearTimeout(_infscroll_timeout);
_infscroll_timeout = window.setTimeout(() => {
console.log('infscroll request timed out, aborting');
infscroll_in_progress = 0;
// call scroll handler to maybe repeat infscroll request
headlinesScrollHandler();
}, 10 * 1000);
}
Form.enable("main_toolbar_form");
let query = Object.assign({op: "feeds", method: "view", feed: feed},
dojo.formToObject("main_toolbar_form"));
if (method) query.m = method;
if (offset > 0) {
if (current_first_id) {
query.fid = current_first_id;
}
}
if (_search_query) {
query = Object.assign(query, _search_query);
}
if (offset != 0) {
query.skip = offset;
// to prevent duplicate feed titles when showing grouped vfeeds
if (vgroup_last_feed) {
query.vgrlf = vgroup_last_feed;
}
} else if (!is_cat && feed == getActiveFeedId() && !params.method) {
query.m = "ForceUpdate";
}
Form.enable("main_toolbar_form");
if (!delayed)
if (!setFeedExpandoIcon(feed, is_cat,
(is_cat) ? 'images/indicator_tiny.gif' : 'images/indicator_white.gif'))
notify_progress("Loading, please wait...", true);
query.cat = is_cat;
setActiveFeedId(feed, is_cat);
if (viewfeed_debug) {
window.open("backend.php?" +
dojo.objectToQuery(
Object.assign({debug: 1, csrf_token: getInitParam("csrf_token")}, query)
));
}
window.clearTimeout(_viewfeed_wait_timeout);
_viewfeed_wait_timeout = window.setTimeout(() => {
catchupBatchedArticles(() => {
xhrPost("backend.php", query, (transport) => {
try {
setFeedExpandoIcon(feed, is_cat, 'images/blank_icon.gif');
headlines_callback2(transport, offset);
PluginHost.run(PluginHost.HOOK_FEED_LOADED, [feed, is_cat]);
} catch (e) {
exception_error(e);
}
});
});
}, delayed ? 250 : 0);
}
function feedlist_init() {
console.log("in feedlist init");
setLoadingProgress(50);
document.onkeydown = hotkey_handler;
setInterval(hotkeyPrefixTimeout, 3*1000);
setInterval(catchupBatchedArticles, 10*1000);
if (!getActiveFeedId()) {
viewfeed({feed: -3});
} else {
viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat()});
}
hideOrShowFeeds(getInitParam("hide_read_feeds") == 1);
if (getInitParam("is_default_pw")) {
console.warn("user password is at default value");
const dialog = new dijit.Dialog({
title: __("Your password is at default value"),
href: "backend.php?op=dlg&method=defaultpasswordwarning",
id: 'infoBox',
style: "width: 600px",
onCancel: function() {
return true;
},
onExecute: function() {
return true;
},
onClose: function() {
return true;
}
});
dialog.show();
}
// bw_limit disables timeout() so we request initial counters separately
if (getInitParam("bw_limit") == "1") {
request_counters(true);
} else {
setTimeout(timeout, 250);
}
}
function request_counters(force) {
const date = new Date();
const timestamp = Math.round(date.getTime() / 1000);
if (force || timestamp - counters_last_request > 5) {
console.log("scheduling request of counters...");
counters_last_request = timestamp;
let query = {op: "rpc", method: "getAllCounters", seq: next_seq()};
if (!force)
query.last_article_id = getInitParam("last_article_id");
xhrPost("backend.php", query, (transport) => {
handle_rpc_json(transport);
});
} else {
console.log("request_counters: rate limit reached: " + (timestamp - counters_last_request));
}
}
// NOTE: this implementation is incomplete
// for general objects but good enough for counters
// http://adripofjavascript.com/blog/drips/object-equality-in-javascript.html
function counter_is_equal(a, b) {
// Create arrays of property names
const aProps = Object.getOwnPropertyNames(a);
const bProps = Object.getOwnPropertyNames(b);
// If number of properties is different,
// objects are not equivalent
if (aProps.length != bProps.length) {
return false;
}
for (let i = 0; i < aProps.length; i++) {
const propName = aProps[i];
// If values of same property are not equal,
// objects are not equivalent
if (a[propName] !== b[propName]) {
return false;
}
}
// If we made it this far, objects
// are considered equivalent
return true;
}
function parse_counters(elems) {
for (let l = 0; l < elems.length; l++) {
if (_counters_prev[l] && counter_is_equal(elems[l], _counters_prev[l])) {
continue;
}
const id = elems[l].id;
const kind = elems[l].kind;
const ctr = parseInt(elems[l].counter);
const error = elems[l].error;
const has_img = elems[l].has_img;
const updated = elems[l].updated;
const auxctr = parseInt(elems[l].auxcounter);
if (id == "global-unread") {
global_unread = ctr;
updateTitle();
continue;
}
if (id == "subscribed-feeds") {
/* feeds_found = ctr; */
continue;
}
/*if (getFeedUnread(id, (kind == "cat")) != ctr ||
(kind == "cat")) {
}*/
setFeedUnread(id, (kind == "cat"), ctr);
setFeedValue(id, (kind == "cat"), 'auxcounter', auxctr);
if (kind != "cat") {
setFeedValue(id, false, 'error', error);
setFeedValue(id, false, 'updated', updated);
if (id > 0) {
if (has_img) {
setFeedIcon(id, false,
getInitParam("icons_url") + "/" + id + ".ico?" + has_img);
} else {
setFeedIcon(id, false, 'images/blank_icon.gif');
}
}
}
}
hideOrShowFeeds(getInitParam("hide_read_feeds") == 1);
_counters_prev = elems;
}
function getFeedUnread(feed, is_cat) {
try {
const tree = dijit.byId("feedTree");
if (tree && tree.model)
return tree.model.getFeedUnread(feed, is_cat);
} catch (e) {
//
}
return -1;
}
function getFeedCategory(feed) {
try {
const tree = dijit.byId("feedTree");
if (tree && tree.model)
return tree.getFeedCategory(feed);
} catch (e) {
//
}
return false;
}
function hideOrShowFeeds(hide) {
const tree = dijit.byId("feedTree");
if (tree)
return tree.hideRead(hide, getInitParam("hide_read_shows_special"));
}
function getFeedName(feed, is_cat) {
if (isNaN(feed)) return feed; // it's a tag
const tree = dijit.byId("feedTree");
if (tree && tree.model)
return tree.model.getFeedValue(feed, is_cat, 'name');
}
/* function getFeedValue(feed, is_cat, key) {
try {
const tree = dijit.byId("feedTree");
if (tree && tree.model)
return tree.model.getFeedValue(feed, is_cat, key);
} catch (e) {
//
}
return '';
} */
function setFeedUnread(feed, is_cat, unread) {
const tree = dijit.byId("feedTree");
if (tree && tree.model)
return tree.model.setFeedUnread(feed, is_cat, unread);
}
function setFeedValue(feed, is_cat, key, value) {
try {
const tree = dijit.byId("feedTree");
if (tree && tree.model)
return tree.model.setFeedValue(feed, is_cat, key, value);
} catch (e) {
//
}
}
function selectFeed(feed, is_cat) {
const tree = dijit.byId("feedTree");
if (tree) return tree.selectFeed(feed, is_cat);
}
function setFeedIcon(feed, is_cat, src) {
const tree = dijit.byId("feedTree");
if (tree) return tree.setFeedIcon(feed, is_cat, src);
}
function setFeedExpandoIcon(feed, is_cat, src) {
const tree = dijit.byId("feedTree");
if (tree) return tree.setFeedExpandoIcon(feed, is_cat, src);
return false;
}
function getNextUnreadFeed(feed, is_cat) {
const tree = dijit.byId("feedTree");
const nuf = tree.model.getNextUnreadFeed(feed, is_cat);
if (nuf)
return tree.model.store.getValue(nuf, 'bare_id');
}
function catchupCurrentFeed(mode) {
catchupFeed(getActiveFeedId(), activeFeedIsCat(), mode);
}
function catchupFeedInGroup(id) {
const title = getFeedName(id);
const str = __("Mark all articles in %s as read?").replace("%s", title);
if (getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
const rows = $$("#headlines-frame > div[id*=RROW][data-orig-feed-id='"+id+"']");
if (rows.length > 0) {
rows.each(function (row) {
row.removeClassName("Unread");
if (row.getAttribute("data-article-id") != getActiveArticleId()) {
new Effect.Fade(row, {duration: 0.5});
}
});
const feedTitles = $$("#headlines-frame > div[class='feed-title']");
for (let i = 0; i < feedTitles.length; i++) {
if (feedTitles[i].getAttribute("data-feed-id") == id) {
if (i < feedTitles.length - 1) {
new Effect.Fade(feedTitles[i], {duration: 0.5});
}
break;
}
}
updateFloatingTitle(true);
}
notify_progress("Loading, please wait...", true);
xhrPost("backend.php", { op: "rpc", method: "catchupFeed", feed_id: id, is_cat: false}, (transport) => {
handle_rpc_json(transport);
});
}
}
function catchupFeed(feed, is_cat, mode) {
if (is_cat == undefined) is_cat = false;
let str = false;
switch (mode) {
case "1day":
str = __("Mark %w in %s older than 1 day as read?");
break;
case "1week":
str = __("Mark %w in %s older than 1 week as read?");
break;
case "2week":
str = __("Mark %w in %s older than 2 weeks as read?");
break;
default:
str = __("Mark %w in %s as read?");
}
const mark_what = last_search_query && last_search_query[0] ? __("search results") : __("all articles");
const fn = getFeedName(feed, is_cat);
str = str.replace("%s", fn)
.replace("%w", mark_what);
if (getInitParam("confirm_feed_catchup") == 1 && !confirm(str)) {
return;
}
const catchup_query = {op: 'rpc', method: 'catchupFeed', feed_id: feed,
is_cat: is_cat, mode: mode, search_query: last_search_query[0],
search_lang: last_search_query[1]};
notify_progress("Loading, please wait...", true);
xhrPost("backend.php", catchup_query, (transport) => {
handle_rpc_json(transport);
const show_next_feed = getInitParam("on_catchup_show_next_feed") == "1";
if (show_next_feed) {
const nuf = getNextUnreadFeed(feed, is_cat);
if (nuf) {
viewfeed({feed: nuf, is_cat: is_cat});
}
} else if (feed == getActiveFeedId() && is_cat == activeFeedIsCat()) {
viewCurrentFeed();
}
notify("");
});
}
function decrementFeedCounter(feed, is_cat) {
let ctr = getFeedUnread(feed, is_cat);
if (ctr > 0) {
setFeedUnread(feed, is_cat, ctr - 1);
global_unread = global_unread - 1;
updateTitle();
if (!is_cat) {
const cat = parseInt(getFeedCategory(feed));
if (!isNaN(cat)) {
ctr = getFeedUnread(cat, true);
if (ctr > 0) {
setFeedUnread(cat, true, ctr - 1);
}
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,7 +1,6 @@
function showTrgmRelated(id) {
try {
const query = "backend.php?op=pluginhandler&plugin=af_psql_trgm&method=showrelated&param=" + param_escape(id);
Plugins.Psql_Trgm = {
showRelated: function (id) {
const query = "backend.php?op=pluginhandler&plugin=af_psql_trgm&method=showrelated&param=" + encodeURIComponent(id);
if (dijit.byId("trgmRelatedDlg"))
dijit.byId("trgmRelatedDlg").destroyRecursive();
@ -10,16 +9,13 @@ function showTrgmRelated(id) {
id: "trgmRelatedDlg",
title: __("Related articles"),
style: "width: 600px",
execute: function() {
execute: function () {
},
href: query,
});
dialog.show();
} catch (e) {
exception_error("showTrgmRelated", e);
}
}
};

@ -93,7 +93,7 @@ class Af_Psql_Trgm extends Plugin {
print " <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"$article_link\">".
$line["title"]."</a>";
print " (<a href=\"#\" onclick=\"viewfeed({feed:".$line["feed_id"]."})\">".
print " (<a href=\"#\" onclick=\"Feeds.open({feed:".$line["feed_id"]."})\">".
htmlspecialchars($line["feed_title"])."</a>)";
print " <span class='insensitive'>($sm)</span>";
@ -115,7 +115,7 @@ class Af_Psql_Trgm extends Plugin {
function hook_article_button($line) {
return "<img src=\"plugins/af_psql_trgm/button.png\"
style=\"cursor : pointer\" style=\"cursor : pointer\"
onclick=\"showTrgmRelated(".$line["id"].")\"
onclick=\"Plugins.Psql_Trgm.showRelated(".$line["id"].")\"
class='tagsPic' title='".__('Show related articles')."'>";
}
@ -150,7 +150,7 @@ class Af_Psql_Trgm extends Plugin {
new Ajax.Request('backend.php', {
parameters: dojo.objectToQuery(this.getValues()),
onComplete: function(transport) {
notify_info(transport.responseText);
Notify.info(transport.responseText);
}
});
//this.reset();
@ -202,7 +202,7 @@ class Af_Psql_Trgm extends Plugin {
print "<li>" .
"<img src='images/pub_set.png'
style='vertical-align : middle'> <a href='#'
onclick='editFeed($f)'>" .
onclick='CommonDialogs.editFeed($f)'>" .
Feeds::getFeedTitle($f) . "</a></li>";
}
print "</ul>";

@ -61,7 +61,7 @@ class Af_Readability extends Plugin {
new Ajax.Request('backend.php', {
parameters: dojo.objectToQuery(this.getValues()),
onComplete: function(transport) {
notify_info(transport.responseText);
Notify.info(transport.responseText);
}
});
//this.reset();
@ -94,7 +94,7 @@ class Af_Readability extends Plugin {
print "<li>" .
"<img src='images/pub_set.png'
style='vertical-align : middle'> <a href='#'
onclick='editFeed($f)'>".
onclick='CommonDialogs.editFeed($f)'>".
Feeds::getFeedTitle($f) . "</a></li>";
}
print "</ul>";

@ -45,7 +45,7 @@ class Af_RedditImgur extends Plugin {
new Ajax.Request('backend.php', {
parameters: dojo.objectToQuery(this.getValues()),
onComplete: function(transport) {
notify_info(transport.responseText);
Notify.info(transport.responseText);
}
});
//this.reset();

@ -216,7 +216,7 @@ class Af_Zz_ImgProxy extends Plugin {
new Ajax.Request('backend.php', {
parameters: dojo.objectToQuery(this.getValues()),
onComplete: function(transport) {
notify_info(transport.responseText);
Notify.info(transport.responseText);
}
});
//this.reset();

@ -21,7 +21,7 @@ class Close_Button extends Plugin {
if (!get_pref("COMBINED_DISPLAY_MODE")) {
$rv = "<img src=\"plugins/close_button/button.png\"
class='tagsPic' style=\"cursor : pointer\"
onclick=\"closeArticlePanel()\"
onclick=\"Article.close()\"
title='".__('Close article')."'>";
}

@ -1,60 +1,56 @@
function embedOriginalArticle(id) {
try {
const hasSandbox = "sandbox" in document.createElement("iframe");
const hasSandbox = "sandbox" in document.createElement("iframe");
if (!hasSandbox) {
alert(__("Sorry, your browser does not support sandboxed iframes."));
return;
}
let c = false;
if (!hasSandbox) {
alert(__("Sorry, your browser does not support sandboxed iframes."));
return;
}
if (isCombinedMode()) {
c = $$("div#RROW-" + id + " div[class=content-inner]")[0];
} else if (id == getActiveArticleId()) {
c = $$(".post .content")[0];
}
let c = false;
if (c) {
const iframe = c.parentNode.getElementsByClassName("embeddedContent")[0];
if (App.isCombinedMode()) {
c = $$("div#RROW-" + id + " div[class=content-inner]")[0];
} else if (id == Article.getActive()) {
c = $$(".post .content")[0];
}
if (iframe) {
Element.show(c);
c.parentNode.removeChild(iframe);
if (c) {
const iframe = c.parentNode.getElementsByClassName("embeddedContent")[0];
if (isCombinedMode()) {
cdmScrollToArticleId(id, true);
}
if (iframe) {
Element.show(c);
c.parentNode.removeChild(iframe);
return;
if (App.isCombinedMode()) {
Article.cdmScrollToId(id, true);
}
return;
}
}
const query = { op: "pluginhandler", plugin: "embed_original", method: "getUrl", id: id };
xhrJson("backend.php", query, (reply) => {
if (reply) {
const iframe = new Element("iframe", {
class: "embeddedContent",
src: reply.url,
width: (c.parentNode.offsetWidth - 5) + 'px',
height: (c.parentNode.parentNode.offsetHeight - c.parentNode.firstChild.offsetHeight - 5) + 'px',
style: "overflow: auto; border: none; min-height: " + (document.body.clientHeight / 2) + "px;",
sandbox: 'allow-scripts',
});
if (c) {
Element.hide(c);
c.parentNode.insertBefore(iframe, c);
if (isCombinedMode()) {
cdmScrollToArticleId(id, true);
}
const query = { op: "pluginhandler", plugin: "embed_original", method: "getUrl", id: id };
xhrJson("backend.php", query, (reply) => {
if (reply) {
const iframe = new Element("iframe", {
class: "embeddedContent",
src: reply.url,
width: (c.parentNode.offsetWidth - 5) + 'px',
height: (c.parentNode.parentNode.offsetHeight - c.parentNode.firstChild.offsetHeight - 5) + 'px',
style: "overflow: auto; border: none; min-height: " + (document.body.clientHeight / 2) + "px;",
sandbox: 'allow-scripts',
});
if (c) {
Element.hide(c);
c.parentNode.insertBefore(iframe, c);
if (App.isCombinedMode()) {
Article.cdmScrollToId(id, true);
}
}
});
}
});
} catch (e) {
exception_error("embedOriginalArticle", e);
}
}

@ -14,7 +14,7 @@ function exportData() {
style: "width: 600px",
prepare: function() {
notify_progress("Loading, please wait...");
Notify.progress("Loading, please wait...");
new Ajax.Request("backend.php", {
parameters: "op=pluginhandler&plugin=import_export&method=exportrun&offset=" + exported,
@ -50,10 +50,10 @@ function exportData() {
"Error occured, could not export data.";
}
} catch (e) {
exception_error("exportData", e, transport.responseText);
App.Error.report(e);
}
notify('');
Notify.close();
} });
@ -71,7 +71,7 @@ function exportData() {
} catch (e) {
exception_error("exportData", e);
App.Error.report(e);
}
}
@ -81,7 +81,7 @@ function dataImportComplete(iframe) {
Element.hide(iframe);
notify('');
Notify.close();
if (dijit.byId('dataImportDlg'))
dijit.byId('dataImportDlg').destroyRecursive();
@ -100,7 +100,7 @@ function dataImportComplete(iframe) {
dialog.show();
} catch (e) {
exception_error("dataImportComplete", e);
App.Error.report(e);
}
}
@ -112,7 +112,7 @@ function importData() {
alert(__("Please choose the file first."));
return false;
} else {
notify_progress("Importing, please wait...", true);
Notify.progress("Importing, please wait...", true);
Element.show("data_upload_iframe");

@ -45,7 +45,7 @@ class Mail extends Plugin {
new Ajax.Request('backend.php', {
parameters: dojo.objectToQuery(this.getValues()),
onComplete: function(transport) {
notify_info(transport.responseText);
Notify.info(transport.responseText);
}
});
//this.reset();
@ -72,7 +72,7 @@ class Mail extends Plugin {
function hook_article_button($line) {
return "<img src=\"plugins/mail/mail.png\"
class='tagsPic' style=\"cursor : pointer\"
onclick=\"emailArticle(".$line["id"].")\"
onclick=\"Plugins.Mail.send(".$line["id"].")\"
alt='Zoom' title='".__('Forward by email')."'>";
}

@ -1,10 +1,10 @@
function emailArticle(id) {
try {
Plugins.Mail = {
send: function(id) {
if (!id) {
var ids = getSelectedArticleIds2();
let ids = Headlines.getSelected();
if (ids.length == 0) {
alert(__("No articles are selected."));
alert(__("No articles selected."));
return;
}
@ -14,13 +14,13 @@ function emailArticle(id) {
if (dijit.byId("emailArticleDlg"))
dijit.byId("emailArticleDlg").destroyRecursive();
var query = "backend.php?op=pluginhandler&plugin=mail&method=emailArticle&param=" + param_escape(id);
const query = "backend.php?op=pluginhandler&plugin=mail&method=emailArticle&param=" + encodeURIComponent(id);
dialog = new dijit.Dialog({
const dialog = new dijit.Dialog({
id: "emailArticleDlg",
title: __("Forward article by email"),
style: "width: 600px",
execute: function() {
execute: function () {
if (this.validate()) {
xhrJson("backend.php", this.attr('value'), (reply) => {
if (reply) {
@ -29,7 +29,7 @@ function emailArticle(id) {
if (error) {
alert(__('Error sending email:') + ' ' + error);
} else {
notify_info('Your message has been sent.');
Notify.info('Your message has been sent.');
dialog.hide();
}
@ -37,10 +37,11 @@ function emailArticle(id) {
});
}
},
href: query});
href: query
});
/* var tmph = dojo.connect(dialog, 'onLoad', function() {
dojo.disconnect(tmph);
dojo.disconnect(tmph);
new Ajax.Autocompleter('emailArticleDlg_destination', 'emailArticleDlg_dst_choices',
"backend.php?op=pluginhandler&plugin=mail&method=completeEmails",
@ -48,10 +49,8 @@ function emailArticle(id) {
}); */
dialog.show();
} catch (e) {
exception_error("emailArticle", e);
},
onHotkey: function(id) {
Plugins.Mail.send(id);
}
}
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save