Automated build for v0.01

This commit is contained in:
Fmstrat
2019-03-22 10:17:29 -04:00
commit 791b998489
2771 changed files with 222096 additions and 0 deletions

473
js/AppBase.js Normal file
View File

@ -0,0 +1,473 @@
'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) {
$("overlay").hide();
}
},
keyeventToAction: function(event) {
const hotkeys_map = App.getInitParam("hotkeys");
const keycode = event.which;
const keychar = String.fromCharCode(keycode);
if (keycode == 27) { // escape and drop prefix
this.hotkey_prefix = false;
}
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 = "";
if (event.type == "keydown") {
hotkey_name = "(" + 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;
} else {
hotkey_name = keychar ? keychar : "(" + keycode + ")";
}
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 = $$("#toolbar .net-alert")[0];
try {
const reply = JSON.parse(transport.responseText);
if (reply) {
const error = reply['error'];
if (error) {
const code = error['code'];
const msg = error['message'];
console.warn("[handleRpcJson] received fatal error ", code, msg);
if (code != 0) {
/* global ERRORS */
this.Error.fatal(ERRORS[code], {info: msg, code: code});
return false;
}
}
const seq = reply['seq'];
if (seq && this.get_seq() != seq) {
console.log("[handleRpcJson] sequence mismatch: ", seq, '!=', this.get_seq());
return true;
}
const message = reply['message'];
if (message == "UPDATE_COUNTERS") {
console.log("need to refresh counters...");
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();
Notify.error("Communication problem with server.");
}
} catch (e) {
if (netalert) netalert.show();
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 == "recent_log_events") {
const alert = $$(".log-alert")[0];
if (alert) {
v > 0 ? alert.show() : alert.hide();
}
}
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);
/* global ERRORS */
if (!reply) {
this.Error.fatal(ERRORS[3], {info: transport.responseText});
return;
}
if (reply['error']) {
const code = reply['error']['code'];
if (code && code != 0) {
return this.Error.fatal(ERRORS[code],
{code: code, info: 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 "cdm_auto_catchup":
if (params[k] == 1) {
const hl = $("headlines-frame");
if (hl) hl.addClassName("auto_catchup");
}
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();
},
toggleNightMode: function() {
const link = $("theme_css");
if (link) {
let user_theme = "";
let user_css = "";
if (link.getAttribute("href").indexOf("themes/agriget-night.css") == -1) {
user_css = "themes/agriget-night.css?" + Date.now();
user_theme = "agriget-night.css";
} else {
user_theme = "agriget.css";
user_css = "css/default.css?" + Date.now();
}
$("main").fade({duration: 0.5, afterFinish: () => {
link.setAttribute("href", user_css);
$("main").appear({duration: 0.5});
xhrPost("backend.php", {op: "rpc", method: "setpref", key: "USER_CSS_THEME", value: user_theme}, (transport) => { window.location.reload(false); });
}});
}
},
explainError: function(code) {
return this.displayDlg(__("Error explained"), "explainError", code);
},
Error: {
fatal: function (error, params) {
params = params || {};
if (params.code) {
if (params.code == 6) {
window.location.href = "index.php";
return;
} else if (params.code == 5) {
window.location.href = "public.php?op=dbupdate";
return;
}
}
return this.report(error,
Object.extend({title: __("Fatal error")}, params));
},
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 stack_msg = "";
if (error.stack)
stack_msg += `<div><b>Stack trace:</b></div>
<textarea name="stack" readonly="1">${error.stack}</textarea>`;
if (params.info)
stack_msg += `<div><b>Additional information:</b></div>
<textarea name="stack" readonly="1">${params.info}</textarea>`;
let content = `<div class="error-contents">
<p class="message">${message}</p>
${stack_msg}
<div class="dlgButtons">
<button dojoType="dijit.form.Button"
onclick=\"dijit.byId('exceptionDlg').hide()">${__('Close this window')}</button>
</div>
</div>`;
const dialog = new dijit.Dialog({
id: "exceptionDlg",
title: params.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});
},
}
});
});

394
js/Article.js Normal file
View File

@ -0,0 +1,394 @@
'use strict'
/* global __, ngettext */
define(["dojo/_base/declare"], function (declare) {
Article = {
getScoreClass: function (score) {
if (score > 500) {
return "score-high";
} else if (score > 0) {
return "score-half-high";
} else if (score < -100) {
return "score-low";
} else if (score < 0) {
return "score-half-low";
} else {
return "score-neutral";
}
},
getScorePic: function (score) {
if (score > 500) {
return "trending_up";
} else if (score > 0) {
return "trending_up";
} else if (score < 0) {
return "trending_down";
} else {
return "trending_neutral";
}
},
selectionSetScore: function () {
const ids = Headlines.getSelected();
if (ids.length > 0) {
const score = prompt(__("Please enter new score for selected articles:"));
if (parseInt(score) != undefined) {
ids.each((id) => {
const row = $("RROW-" + id);
if (row) {
row.setAttribute("data-score", score);
const pic = row.select(".icon-score")[0];
pic.innerHTML = Article.getScorePic(score);
pic.setAttribute("title", score);
["score-low", "score-high", "score-half-low", "score-half-high", "score-neutral"]
.each(function(scl) {
if (row.hasClassName(scl))
row.removeClassName(scl);
});
row.addClassName(Article.getScoreClass(score));
}
});
}
} else {
alert(__("No articles selected."));
}
},
setScore: function (id, pic) {
const row = $("RROW-"+id);
const origpic = row.down("i[score-id*=score-]");
const contentpic = row.down("i[score-id*=score-content-]");
const so = $("slide-out");
if (row) {
const score_old = row.getAttribute("data-score");
const score = prompt(__("Please enter new score for this article:"), score_old);
if (parseInt(score) != undefined) {
row.setAttribute("data-score", score);
const pic = row.select(".icon-score")[0];
if (pic) {
pic.innerHTML = Article.getScorePic(score);
pic.setAttribute("title", score);
}
if (origpic) {
origpic.innerHTML = Article.getScorePic(score);
origpic.setAttribute("title", score);
}
if (contentpic) {
contentpic.innerHTML = Article.getScorePic(score);
contentpic.setAttribute("title", score);
}
console.log(so);
if (so) {
const sopic = so.down("i[score-id*=score-content-]");
console.log(sopic);
sopic.innerHTML = Article.getScorePic(score);
sopic.setAttribute("title", score);
}
["score-low", "score-high", "score-half-low", "score-half-high", "score-neutral"]
.each(function(scl) {
if (row.hasClassName(scl))
row.removeClassName(scl);
if (so && so.hasClassName(scl))
so.removeClassName(scl);
});
row.addClassName(Article.getScoreClass(score));
if (so) so.addClassName(Article.getScoreClass(score));
}
}
},
cdmUnsetActive: function (event) {
const row = $("RROW-" + Article.getActive());
if (row) {
row.removeClassName("active");
if (event)
event.stopPropagation();
return false;
}
},
close: function () {
if (dijit.byId("content-insert"))
dijit.byId("headlines-wrap-inner").removeChild(
dijit.byId("content-insert"));
Article.setActive(0);
},
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("");
if (w) {
w.opener = null;
w.location = "backend.php?op=article&method=redirect&id=" + id;
Headlines.toggleUnread(id, 0);
}
},
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) {
}
},
formatComments: function(hl) {
let comments = "";
if (hl.comments) {
let comments_msg = __("comments");
if (hl.num_comments > 0) {
comments_msg = hl.num_comments + " " + ngettext("comment", "comments", hl.num_comments)
}
comments = `<a href="${hl.comments}">(${comments_msg})</a>`;
}
return comments;
},
formatOriginallyFrom: function(hl) {
return hl.orig_feed ? `<span>
${__('Originally from:')} <a target="_blank" rel="noopener noreferrer" href="${hl.orig_feed[1]}">${hl.orig_feed[0]}</a>
</span>` : "";
},
unpack: function(row) {
if (row.hasAttribute("data-content")) {
console.log("unpacking: " + row.id);
const container = row.querySelector(".content-inner");
container.innerHTML = row.getAttribute("data-content").trim();
// blank content element might screw up onclick selection and keyboard moving
if (container.textContent.length == 0)
container.innerHTML += "&nbsp;";
row.removeAttribute("data-content");
PluginHost.run(PluginHost.HOOK_ARTICLE_RENDERED_CDM, row);
}
},
view: function (id, noexpand) {
this.setActive(id);
if (!noexpand) {
const hl = Headlines.objectById(id);
if (hl) {
const comments = this.formatComments(hl);
const originally_from = this.formatOriginallyFrom(hl);
const article = `<div class="post post-${hl.id}">
<div class="header">
<div class="row">
<div class="title"><a target="_blank" rel="noopener noreferrer" title="${hl.title}" href="${hl.link}">${hl.title}</a></div>
<div class="date">${hl.updated_long}</div>
</div>
<div class="row">
<div class="buttons left">${hl.buttons_left}</div>
<div class="comments">${comments}</div>
<div class="author">${hl.author}</div>
<i class="material-icons">label_outline</i>
<span id="ATSTR-${hl.id}">${hl.tags_str}</span>
&nbsp;<a title="${__("Edit tags for this article")}" href="#"
onclick="Article.editTags(${hl.id})">(+)</a>
<div class="buttons right">${hl.buttons}</div>
</div>
</div>
<div id="POSTNOTE-${hl.id}">${hl.note}</div>
<div class="content" lang="${hl.lang ? hl.lang : 'en'}">
${originally_from}
${hl.content}
${hl.enclosures}
</div>
</div>`;
Headlines.toggleUnread(id, 0);
this.render(article);
}
}
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) {
ctr.scrollTop = e.offsetTop;
Element.hide("floatingTitle");
}
},
setActiveHeadline: function (id) {
console.log("setActiveHeadline", id);
$$("div[id*=RROW][class*=activeHeadline]").each((e) => {
e.removeClassName("activeHeadline");
});
$$("div[id*=RROW][class*=passiveActiveHeadline]").each((e) => {
e.removeClassName("passiveActiveHeadline");
});
const row = $("RROW-" + id);
if (row) {
Article.unpack(row);
row.removeClassName("Unread");
row.addClassName("passiveActiveHeadline");
PluginHost.run(PluginHost.HOOK_ARTICLE_SET_ACTIVE, row.getAttribute("data-article-id"));
}
},
getActiveHeadline: function () {
const row = document.querySelector("#headlines-frame > div[id*=RROW][class*=passiveActiveHeadline]");
if (row)
return row.getAttribute("data-article-id");
else
return 0;
},
setActive: function (id) {
console.log("setActive", id);
$$("div[id*=RROW][class*=active]").each((e) => {
e.removeClassName("active");
});
const row = $("RROW-" + id);
if (row) {
Article.unpack(row);
row.removeClassName("Unread");
PluginHost.run(PluginHost.HOOK_ARTICLE_SET_ACTIVE, row.getAttribute("data-article-id"));
}
},
getActive: function () {
const row = document.querySelector("#headlines-frame > div[id*=RROW][class*=active]");
if (row)
return row.getAttribute("data-article-id");
else
return 0;
},
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;
}
}
},
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;
});

483
js/CommonDialogs.js Normal file
View File

@ -0,0 +1,483 @@
'use strict'
/* global __, ngettext */
define(["dojo/_base/declare"], function (declare) {
// noinspection JSUnusedGlobalSymbols
CommonDialogs = {
closeInfoBox: function() {
const dialog = dijit.byId("infoBox");
if (dialog) dialog.hide();
},
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())
dijit.byId("feedTree").reload();
else
Feeds.reload();
const icon = $$(".feed-editor-icon")[0];
if (icon)
icon.src = icon.src.replace(/\?[0-9]+$/, "?" + new Date().getTime());
});
}
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);
const xhr = new XMLHttpRequest();
xhr.open( 'POST', 'backend.php', true );
xhr.onload = function () {
switch (parseInt(this.responseText)) {
case 0:
Notify.info("Upload complete.");
if (App.isPrefs())
dijit.byId("feedTree").reload();
else
Feeds.reload();
const icon = $$(".feed-editor-icon")[0];
if (icon)
icon.src = icon.src.replace(/\?[0-9]+$/, "?" + new Date().getTime());
break;
case 1:
Notify.error("Upload failed: icon is too big.");
break;
case 2:
Notify.error("Upload failed.");
break;
}
};
xhr.send(new FormData($("feed_icon_upload_form")));
}
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));
if (App.isPrefs())
dijit.byId("feedTree").reload();
else
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("error-feeds-list");
},
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();
if (App.isPrefs())
dijit.byId("feedTree").reload();
else
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();
if (App.isPrefs())
dijit.byId("feedTree").reload();
else
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()) {
dijit.byId("feedTree").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();
if (App.isPrefs())
dijit.byId("feedTree").reload();
else
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;
});

380
js/CommonFilters.js Normal file
View File

@ -0,0 +1,380 @@
'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) {
Lists.select("filterDlg_Matches", select);
},
selectActions: function (select) {
Lists.select("filterDlg_Actions", select);
},
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;
});

98
js/FeedStoreModel.js Normal file
View File

@ -0,0 +1,98 @@
define(["dojo/_base/declare", "dijit/tree/ForestStoreModel"], function (declare) {
return declare("fox.FeedStoreModel", dijit.tree.ForestStoreModel, {
getItemsInCategory: function (id) {
if (!this.store._itemsByIdentity) return undefined;
let cat = this.store._itemsByIdentity['CAT:' + id];
if (cat && cat.items)
return cat.items;
else
return undefined;
},
getItemById: function (id) {
return this.store._itemsByIdentity[id];
},
getFeedValue: function (feed, is_cat, key) {
if (!this.store._itemsByIdentity) return undefined;
if (is_cat)
treeItem = this.store._itemsByIdentity['CAT:' + feed];
else
treeItem = this.store._itemsByIdentity['FEED:' + feed];
if (treeItem)
return this.store.getValue(treeItem, key);
},
getFeedName: function (feed, is_cat) {
return this.getFeedValue(feed, is_cat, 'name');
},
getFeedUnread: function (feed, is_cat) {
const unread = parseInt(this.getFeedValue(feed, is_cat, 'unread'));
return (isNaN(unread)) ? 0 : unread;
},
setFeedUnread: function (feed, is_cat, unread) {
return this.setFeedValue(feed, is_cat, 'unread', parseInt(unread));
},
setFeedValue: function (feed, is_cat, key, value) {
if (!value) value = '';
if (!this.store._itemsByIdentity) return undefined;
if (is_cat)
treeItem = this.store._itemsByIdentity['CAT:' + feed];
else
treeItem = this.store._itemsByIdentity['FEED:' + feed];
if (treeItem)
return this.store.setValue(treeItem, key, value);
},
getNextUnreadFeed: function (feed, is_cat) {
if (!this.store._itemsByIdentity)
return null;
if (is_cat) {
treeItem = this.store._itemsByIdentity['CAT:' + feed];
} else {
treeItem = this.store._itemsByIdentity['FEED:' + feed];
}
let items = this.store._arrayOfAllItems;
for (let i = 0; i < items.length; i++) {
if (items[i] == treeItem) {
for (var j = i + 1; j < items.length; j++) {
let unread = this.store.getValue(items[j], 'unread');
let id = this.store.getValue(items[j], 'id');
if (unread > 0 && ((is_cat && id.match("CAT:")) || (!is_cat && id.match("FEED:")))) {
if (!is_cat || !(this.store.hasAttribute(items[j], 'parent_id') && this.store.getValue(items[j], 'parent_id') == feed)) return items[j];
}
}
for (var j = 0; j < i; j++) {
let unread = this.store.getValue(items[j], 'unread');
let id = this.store.getValue(items[j], 'id');
if (unread > 0 && ((is_cat && id.match("CAT:")) || (!is_cat && id.match("FEED:")))) {
if (!is_cat || !(this.store.hasAttribute(items[j], 'parent_id') && this.store.getValue(items[j], 'parent_id') == feed)) return items[j];
}
}
}
}
return null;
},
hasCats: function () {
if (this.store && this.store._itemsByIdentity)
return this.store._itemsByIdentity['CAT:-1'] != undefined;
else
return false;
},
});
});

541
js/FeedTree.js Executable file
View File

@ -0,0 +1,541 @@
/* global dijit */
define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"], function (declare, domConstruct) {
return declare("fox.FeedTree", dijit.Tree, {
_onKeyPress: function(/* Event */ e) {
return; // Stop dijit.Tree from interpreting keystrokes
},
_createTreeNode: function(args) {
const tnode = new dijit._TreeNode(args);
const iconName = args.item.icon ? String(args.item.icon[0]) : null;
let iconNode;
if (iconName) {
if (iconName.indexOf("/") == -1) {
iconNode = dojo.doc.createElement("i");
iconNode.className = "material-icons icon icon-" + iconName;
iconNode.innerHTML = iconName;
} else {
iconNode = dojo.doc.createElement('img');
if (args.item.icon && args.item.icon[0]) {
iconNode.src = args.item.icon[0];
} else {
iconNode.src = 'images/blank_icon.gif';
}
iconNode.className = 'icon';
}
}
if (iconNode)
domConstruct.place(iconNode, tnode.iconNode, 'only');
const id = args.item.id[0];
const bare_id = parseInt(id.substr(id.indexOf(':')+1));
if (bare_id < _label_base_index) {
const label = dojo.doc.createElement('i');
//const fg_color = args.item.fg_color[0];
const bg_color = args.item.bg_color[0];
label.className = "material-icons icon icon-label";
label.innerHTML = "label";
label.setStyle({
color: bg_color,
});
domConstruct.place(label, tnode.iconNode, 'only');
}
if (id.match("FEED:")) {
let menu = new dijit.Menu();
menu.row_id = bare_id;
menu.addChild(new dijit.MenuItem({
label: __("Mark as read"),
onClick: function() {
Feeds.catchupFeed(this.getParent().row_id);
}}));
if (bare_id > 0) {
menu.addChild(new dijit.MenuItem({
label: __("Edit feed"),
onClick: function() {
CommonDialogs.editFeed(this.getParent().row_id, false);
}}));
/* menu.addChild(new dijit.MenuItem({
label: __("Update feed"),
onClick: function() {
heduleFeedUpdate(this.getParent().row_id, false);
}})); */
}
menu.bindDomNode(tnode.domNode);
tnode._menu = menu;
}
if (id.match("CAT:") && bare_id >= 0) {
let menu = new dijit.Menu();
menu.row_id = bare_id;
menu.addChild(new dijit.MenuItem({
label: __("Mark as read"),
onClick: function() {
Feeds.catchupFeed(this.getParent().row_id, true);
}}));
menu.addChild(new dijit.MenuItem({
label: __("(Un)collapse"),
onClick: function() {
dijit.byId("feedTree").collapseCat(this.getParent().row_id);
}}));
menu.bindDomNode(tnode.domNode);
tnode._menu = menu;
}
if (id.match("CAT:")) {
loading = dojo.doc.createElement('img');
loading.className = 'loadingNode';
loading.src = 'images/blank_icon.gif';
domConstruct.place(loading, tnode.labelNode, 'after');
tnode.loadingNode = loading;
}
if (id.match("CAT:") && bare_id == -1) {
let menu = new dijit.Menu();
menu.row_id = bare_id;
menu.addChild(new dijit.MenuItem({
label: __("Mark all feeds as read"),
onClick: function() {
Feeds.catchupAll();
}}));
menu.bindDomNode(tnode.domNode);
tnode._menu = menu;
}
ctr = dojo.doc.createElement('span');
ctr.className = 'counterNode';
ctr.innerHTML = args.item.unread > 0 ? args.item.unread : args.item.auxcounter;
//args.item.unread > 0 ? ctr.addClassName("unread") : ctr.removeClassName("unread");
args.item.unread > 0 || args.item.auxcounter > 0 ? Element.show(ctr) : Element.hide(ctr);
args.item.unread == 0 && args.item.auxcounter > 0 ? ctr.addClassName("aux") : ctr.removeClassName("aux");
domConstruct.place(ctr, tnode.rowNode, 'first');
tnode.counterNode = ctr;
//tnode.labelNode.innerHTML = args.label;
return tnode;
},
postCreate: function() {
this.connect(this.model, "onChange", "updateCounter");
this.connect(this, "_expandNode", function() {
this.hideRead(App.getInitParam("hide_read_feeds"), App.getInitParam("hide_read_shows_special"));
});
this.inherited(arguments);
},
updateCounter: function (item) {
const tree = this;
//console.log("updateCounter: " + item.id[0] + " " + item.unread + " " + tree);
let node = tree._itemNodesMap[item.id];
if (node) {
node = node[0];
if (node.counterNode) {
ctr = node.counterNode;
ctr.innerHTML = item.unread > 0 ? item.unread : item.auxcounter;
item.unread > 0 || item.auxcounter > 0 ?
item.unread > 0 ?
Effect.Appear(ctr, {duration : 0.3,
queue: { position: 'end', scope: 'CAPPEAR-' + item.id, limit: 1 }}) :
Element.show(ctr) :
Element.hide(ctr);
item.unread == 0 && item.auxcounter > 0 ? ctr.addClassName("aux") : ctr.removeClassName("aux");
}
}
},
getTooltip: function (item) {
return [item.updated, item.error].filter(x => x && x != "").join(" - ");
},
getIconClass: function (item, opened) {
return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "feed-icon";
},
getLabelClass: function (item, opened) {
return (item.unread == 0) ? "dijitTreeLabel" : "dijitTreeLabel Unread";
},
getRowClass: function (item, opened) {
let rc = (!item.error || item.error == '') ? "dijitTreeRow" :
"dijitTreeRow Error";
if (item.unread > 0) rc += " Unread";
if (item.updates_disabled > 0) rc += " UpdatesDisabled";
return rc;
},
getLabel: function(item) {
let name = String(item.name);
/* Horrible */
name = name.replace(/&quot;/g, "\"");
name = name.replace(/&amp;/g, "&");
name = name.replace(/&mdash;/g, "-");
name = name.replace(/&lt;/g, "<");
name = name.replace(/&gt;/g, ">");
/* var label;
if (item.unread > 0) {
label = name + " (" + item.unread + ")";
} else {
label = name;
} */
return name;
},
expandParentNodes: function(feed, is_cat, list) {
try {
for (let i = 0; i < list.length; i++) {
const id = String(list[i].id);
let item = this._itemNodesMap[id];
if (item) {
item = item[0];
this._expandNode(item);
}
}
} catch (e) {
App.Error.report(e);
}
},
findNodeParentsAndExpandThem: function(feed, is_cat, root, parents) {
// expands all parents of specified feed to properly mark it as active
// my fav thing about frameworks is doing everything myself
try {
const test_id = is_cat ? 'CAT:' + feed : 'FEED:' + feed;
if (!root) {
if (!this.model || !this.model.store) return false;
const items = this.model.store._arrayOfTopLevelItems;
for (let i = 0; i < items.length; i++) {
if (String(items[i].id) == test_id) {
this.expandParentNodes(feed, is_cat, parents);
} else {
this.findNodeParentsAndExpandThem(feed, is_cat, items[i], []);
}
}
} else if (root.items) {
parents.push(root);
for (let i = 0; i < root.items.length; i++) {
if (String(root.items[i].id) == test_id) {
this.expandParentNodes(feed, is_cat, parents);
} else {
this.findNodeParentsAndExpandThem(feed, is_cat, root.items[i], parents.slice(0));
}
}
} else if (String(root.id) == test_id) {
this.expandParentNodes(feed, is_cat, parents.slice(0));
}
} catch (e) {
App.Error.report(e);
}
},
selectFeed: function(feed, is_cat) {
this.findNodeParentsAndExpandThem(feed, is_cat, false, false);
if (is_cat)
treeNode = this._itemNodesMap['CAT:' + feed];
else
treeNode = this._itemNodesMap['FEED:' + feed];
if (treeNode) {
treeNode = treeNode[0];
if (!is_cat) this._expandNode(treeNode);
this.set("selectedNodes", [treeNode]);
this.focusNode(treeNode);
// focus headlines to route key events there
setTimeout(() => {
$("headlines-frame").focus();
if (treeNode) {
const node = treeNode.rowNode;
const tree = this.domNode;
if (node && tree) {
// scroll tree to selection if needed
if (node.offsetTop < tree.scrollTop || node.offsetTop > tree.scrollTop + tree.clientHeight) {
$("feedTree").scrollTop = node.offsetTop;
}
}
}
}, 0);
}
},
setFeedIcon: function(feed, is_cat, src) {
if (is_cat)
treeNode = this._itemNodesMap['CAT:' + feed];
else
treeNode = this._itemNodesMap['FEED:' + feed];
if (treeNode) {
treeNode = treeNode[0];
const icon = dojo.doc.createElement('img');
icon.src = src;
icon.className = 'icon';
domConstruct.place(icon, treeNode.iconNode, 'only');
return true;
}
return false;
},
setFeedExpandoIcon: function(feed, is_cat, src) {
if (is_cat)
treeNode = this._itemNodesMap['CAT:' + feed];
else
treeNode = this._itemNodesMap['FEED:' + feed];
if (treeNode) {
treeNode = treeNode[0];
if (treeNode.loadingNode) {
treeNode.loadingNode.src = src;
return true;
} else {
const icon = dojo.doc.createElement('img');
icon.src = src;
icon.className = 'loadingExpando';
domConstruct.place(icon, treeNode.expandoNode, 'only');
return true;
}
}
return false;
},
hasCats: function() {
return this.model.hasCats();
},
hideReadCat: function (cat, hide, show_special) {
if (this.hasCats()) {
const tree = this;
if (cat && cat.items) {
let cat_unread = tree.hideReadFeeds(cat.items, hide, show_special);
const id = String(cat.id);
const node = tree._itemNodesMap[id];
const bare_id = parseInt(id.substr(id.indexOf(":")+1));
if (node) {
const check_unread = tree.model.getFeedUnread(bare_id, true);
if (hide && cat_unread == 0 && check_unread == 0 && (id != "CAT:-1" || !show_special)) {
Effect.Fade(node[0].rowNode, {duration : 0.3,
queue: { position: 'end', scope: 'FFADE-' + id, limit: 1 }});
} else {
Element.show(node[0].rowNode);
++cat_unread;
}
}
}
}
},
hideRead: function (hide, show_special) {
if (this.hasCats()) {
const tree = this;
const cats = this.model.store._arrayOfTopLevelItems;
cats.each(function(cat) {
tree.hideReadCat(cat, hide, show_special);
});
} else {
this.hideReadFeeds(this.model.store._arrayOfTopLevelItems, hide,
show_special);
}
},
hideReadFeeds: function (items, hide, show_special) {
const tree = this;
let cat_unread = 0;
items.each(function(feed) {
const id = String(feed.id);
// it's a subcategory
if (feed.items) {
tree.hideReadCat(feed, hide, show_special);
} else { // it's a feed
const bare_id = parseInt(feed.bare_id);
const unread = feed.unread[0];
const has_error = feed.error[0] != '';
const node = tree._itemNodesMap[id];
if (node) {
if (hide && unread == 0 && !has_error && (bare_id > 0 || bare_id < _label_base_index || !show_special)) {
Effect.Fade(node[0].rowNode, {duration : 0.3,
queue: { position: 'end', scope: 'FFADE-' + id, limit: 1 }});
} else {
Element.show(node[0].rowNode);
++cat_unread;
}
}
}
});
return cat_unread;
},
collapseCat: function(id) {
if (!this.model.hasCats()) return;
const tree = this;
const node = tree._itemNodesMap['CAT:' + id][0];
const item = tree.model.store._itemsByIdentity['CAT:' + id];
if (node && item) {
if (!node.isExpanded)
tree._expandNode(node);
else
tree._collapseNode(node);
}
},
getVisibleUnreadFeeds: function() {
const items = this.model.store._arrayOfAllItems;
const rv = [];
for (let i = 0; i < items.length; i++) {
const id = String(items[i].id);
const box = this._itemNodesMap[id];
if (box) {
const row = box[0].rowNode;
let cat = false;
try {
cat = box[0].rowNode.parentNode.parentNode;
} catch (e) { }
if (row) {
if (Element.visible(row) && (!cat || Element.visible(cat))) {
const feed_id = String(items[i].bare_id);
const is_cat = !id.match('FEED:');
const unread = this.model.getFeedUnread(feed_id, is_cat);
if (unread > 0)
rv.push([feed_id, is_cat]);
}
}
}
}
return rv;
},
getNextFeed: function (feed, is_cat) {
if (is_cat) {
treeItem = this.model.store._itemsByIdentity['CAT:' + feed];
} else {
treeItem = this.model.store._itemsByIdentity['FEED:' + feed];
}
const items = this.model.store._arrayOfAllItems;
let item = items[0];
for (let i = 0; i < items.length; i++) {
if (items[i] == treeItem) {
for (let j = i+1; j < items.length; j++) {
const id = String(items[j].id);
const box = this._itemNodesMap[id];
if (box) {
const row = box[0].rowNode;
const cat = box[0].rowNode.parentNode.parentNode;
if (Element.visible(cat) && Element.visible(row)) {
item = items[j];
break;
}
}
}
break;
}
}
if (item) {
return [this.model.store.getValue(item, 'bare_id'),
!this.model.store.getValue(item, 'id').match('FEED:')];
} else {
return false;
}
},
getPreviousFeed: function (feed, is_cat) {
if (is_cat) {
treeItem = this.model.store._itemsByIdentity['CAT:' + feed];
} else {
treeItem = this.model.store._itemsByIdentity['FEED:' + feed];
}
const items = this.model.store._arrayOfAllItems;
let item = items[0] == treeItem ? items[items.length-1] : items[0];
for (let i = 0; i < items.length; i++) {
if (items[i] == treeItem) {
for (let j = i-1; j > 0; j--) {
const id = String(items[j].id);
const box = this._itemNodesMap[id];
if (box) {
const row = box[0].rowNode;
const cat = box[0].rowNode.parentNode.parentNode;
if (Element.visible(cat) && Element.visible(row)) {
item = items[j];
break;
}
}
}
break;
}
}
if (item) {
return [this.model.store.getValue(item, 'bare_id'),
!this.model.store.getValue(item, 'id').match('FEED:')];
} else {
return false;
}
},
getFeedCategory: function(feed) {
try {
return this.getNodesByItem(this.model.store.
_itemsByIdentity["FEED:" + feed])[0].
getParent().item.bare_id[0];
} catch (e) {
return false;
}
},
});
});

565
js/Feeds.js Normal file
View File

@ -0,0 +1,565 @@
'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() {
xhrPost("backend.php", {op: "rpc", method: "getAllCounters", seq: App.next_seq()}, (transport) => {
App.handleRpcJson(transport);
});
},
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) => { return App.hotkeyHandler(event) };
document.onkeypress = (event) => { return App.hotkeyHandler(event) };
window.onresize = () => { Headlines.scrollHandler(); }
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: 'defaultPasswordDlg',
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 append = params.append || false;
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 (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("toolbar-main");
let query = Object.assign({op: "feeds", method: "view", feed: feed},
dojo.formToObject("toolbar-main"));
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;
} else if (!is_cat && feed == this.getActive() && !params.method) {
query.m = "ForceUpdate";
}
Form.enable("toolbar-main");
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({csrf_token: App.getInitParam("csrf_token")}, query)
));
}
window.clearTimeout(this._viewfeed_wait_timeout);
this._viewfeed_wait_timeout = window.setTimeout(() => {
xhrPost("backend.php", query, (transport) => {
try {
window.clearTimeout(this._infscroll_timeout);
this.setExpando(feed, is_cat, 'images/blank_icon.gif');
Headlines.onLoaded(transport, offset, append);
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();
}
},
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][class*=Unread][data-orig-feed-id='" + id + "']");
rows.each((row) => {
row.removeClassName("Unread");
})
}
},
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: "updaterandomfeed"}, (transport) => {
App.handleRpcJson(transport, true);
});
},
};
return Feeds;
});

1616
js/Headlines.js Executable file

File diff suppressed because it is too large Load Diff

38
js/PluginHost.js Normal file
View File

@ -0,0 +1,38 @@
// based on http://www.velvetcache.org/2010/08/19/a-simple-javascript-hooks-system
PluginHost = {
HOOK_ARTICLE_RENDERED: 1,
HOOK_ARTICLE_RENDERED_CDM: 2,
HOOK_ARTICLE_SET_ACTIVE: 3,
HOOK_FEED_SET_ACTIVE: 4,
HOOK_FEED_LOADED: 5,
HOOK_ARTICLE_EXPANDED: 6,
HOOK_ARTICLE_COLLAPSED: 7,
HOOK_PARAMS_LOADED: 8,
HOOK_RUNTIME_INFO_LOADED: 9,
HOOK_FLOATING_TITLE: 10,
HOOK_INIT_COMPLETE: 11,
HOOK_HEADLINE_RENDERED: 12,
hooks: [],
register: function (name, callback) {
if (typeof(this.hooks[name]) == 'undefined')
this.hooks[name] = [];
this.hooks[name].push(callback);
},
run: function (name, args) {
//console.warn('PluginHost::run ' + name);
if (typeof(this.hooks[name]) != 'undefined')
for (let i = 0; i < this.hooks[name].length; i++) {
this.hooks[name][i](args);
}
}
};
/* PluginHost.register(PluginHost.HOOK_ARTICLE_RENDERED,
function (args) { console.log('ARTICLE_RENDERED: ' + args); return true; });
PluginHost.register(PluginHost.HOOK_ARTICLE_RENDERED_CDM,
function (args) { console.log('ARTICLE_RENDERED_CDM: ' + args); return true; }); */

20
js/PrefFeedStore.js Normal file
View File

@ -0,0 +1,20 @@
define(["dojo/_base/declare", "dojo/data/ItemFileWriteStore"], function (declare) {
return declare("fox.PrefFeedStore", dojo.data.ItemFileWriteStore, {
_saveEverything: function(saveCompleteCallback, saveFailedCallback,
newFileContentString) {
dojo.xhrPost({
url: "backend.php",
content: {op: "pref-feeds", method: "savefeedorder",
payload: newFileContentString},
error: saveFailedCallback,
load: saveCompleteCallback});
},
});
});

412
js/PrefFeedTree.js Normal file
View File

@ -0,0 +1,412 @@
/* global lib,dijit */
define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], function (declare, domConstruct) {
return declare("fox.PrefFeedTree", lib.CheckBoxTree, {
_createTreeNode: function(args) {
const tnode = this.inherited(arguments);
const icon = dojo.doc.createElement('img');
if (args.item.icon && args.item.icon[0]) {
icon.src = args.item.icon[0];
} else {
icon.src = 'images/blank_icon.gif';
}
icon.className = 'icon';
domConstruct.place(icon, tnode.iconNode, 'only');
let param = this.model.store.getValue(args.item, 'param');
if (param) {
param = dojo.doc.createElement('span');
param.className = 'feedParam';
param.innerHTML = args.item.param[0];
//domConstruct.place(param, tnode.labelNode, 'after');
domConstruct.place(param, tnode.rowNode, 'first');
}
const id = args.item.id[0];
const bare_id = parseInt(id.substr(id.indexOf(':')+1));
if (id.match("CAT:") && bare_id > 0) {
var menu = new dijit.Menu();
menu.row_id = bare_id;
menu.item = args.item;
menu.addChild(new dijit.MenuItem({
label: __("Edit category"),
onClick: function() {
dijit.byId("feedTree").editCategory(this.getParent().row_id, this.getParent().item, null);
}}));
menu.addChild(new dijit.MenuItem({
label: __("Remove category"),
onClick: function() {
dijit.byId("feedTree").removeCategory(this.getParent().row_id, this.getParent().item);
}}));
menu.bindDomNode(tnode.domNode);
tnode._menu = menu;
} else if (id.match("FEED:")) {
var menu = new dijit.Menu();
menu.row_id = bare_id;
menu.item = args.item;
menu.addChild(new dijit.MenuItem({
label: __("Edit feed"),
onClick: function() {
CommonDialogs.editFeed(this.getParent().row_id);
}}));
menu.addChild(new dijit.MenuItem({
label: __("Unsubscribe"),
onClick: function() {
CommonDialogs.unsubscribeFeed(this.getParent().row_id, this.getParent().item.name);
}}));
menu.bindDomNode(tnode.domNode);
tnode._menu = menu;
}
return tnode;
},
onDndDrop: function() {
this.inherited(arguments);
this.tree.model.store.save();
},
getRowClass: function (item, opened) {
let rc = (!item.error || item.error == '') ? "dijitTreeRow" :
"dijitTreeRow Error";
if (item.updates_disabled > 0) rc += " UpdatesDisabled";
return rc;
},
getIconClass: function (item, opened) {
return (!item || this.model.store.getValue(item, 'type') == 'category') ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "feed-icon";
},
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;
// disable copying items
source.copyState = function() { return false; };
let source_item = false;
source.forInSelectedItems(function(node) {
source_item = node.data.item;
});
if (!source_item || !item) return false;
const id = this.tree.model.store.getValue(item, 'id');
const source_id = source.tree.model.store.getValue(source_item, 'id');
//console.log(id + " " + position + " " + source_id);
if (source_id.match("FEED:")) {
return ((id.match("CAT:") && position == "over") ||
(id.match("FEED:") && position != "over"));
} else if (source_id.match("CAT:")) {
return ((id.match("CAT:") && !id.match("CAT:0")) ||
(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('text-muted');
else
$(label).addClassName('text-muted');
},
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("inactive-feeds-list");
},
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();
}
});
});

22
js/PrefFilterStore.js Normal file
View File

@ -0,0 +1,22 @@
define(["dojo/_base/declare", "dojo/data/ItemFileWriteStore"], function (declare) {
return declare("fox.PrefFilterStore", dojo.data.ItemFileWriteStore, {
_saveEverything: function (saveCompleteCallback, saveFailedCallback,
newFileContentString) {
dojo.xhrPost({
url: "backend.php",
content: {
op: "pref-filters", method: "savefilterorder",
payload: newFileContentString
},
error: saveFailedCallback,
load: saveCompleteCallback
});
},
});
});

247
js/PrefFilterTree.js Normal file
View File

@ -0,0 +1,247 @@
/* global dijit,lib */
define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], function (declare, domConstruct) {
return declare("fox.PrefFilterTree", lib.CheckBoxTree, {
_createTreeNode: function(args) {
const tnode = this.inherited(arguments);
const enabled = this.model.store.getValue(args.item, 'enabled');
let param = this.model.store.getValue(args.item, 'param');
const rules = this.model.store.getValue(args.item, 'rules');
if (param) {
param = dojo.doc.createElement('span');
param.className = (enabled != false) ? 'labelParam' : 'labelParam filterDisabled';
param.innerHTML = args.item.param[0];
domConstruct.place(param, tnode.rowNode, 'first');
}
if (rules) {
param = dojo.doc.createElement('ul');
param.className = 'filterRules';
param.innerHTML = rules;
domConstruct.place(param, tnode.rowNode, 'next');
}
/* if (this.model.store.getValue(args.item, 'id') != 'root') {
const i = dojo.doc.createElement('i');
i.className = 'material-icons filter';
i.innerHTML = 'label';
tnode._filterIconNode = i;
domConstruct.place(tnode._filterIconNode, tnode.labelNode, 'before');
} */
return tnode;
},
getLabel: function(item) {
let label = String(item.name);
const feed = this.model.store.getValue(item, 'feed');
const inverse = this.model.store.getValue(item, 'inverse');
const last_triggered = this.model.store.getValue(item, 'last_triggered');
if (feed)
label += " (" + __("in") + " " + feed + ")";
if (inverse)
label += " (" + __("Inverse") + ")";
if (last_triggered)
label += " — " + last_triggered;
return label;
},
getIconClass: function (item, opened) {
return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "invisible";
},
getRowClass: function (item, opened) {
const enabled = this.model.store.getValue(item, 'enabled');
return enabled ? "dijitTreeRow" : "dijitTreeRow filterDisabled";
},
checkItemAcceptance: function(target, source, position) {
const item = dijit.getEnclosingWidget(target).item;
// disable copying items
source.copyState = function() { return false; };
return position != 'over';
},
onDndDrop: function() {
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 = this.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) {
Lists.select("filterDlg_Matches", select);
},
selectActions: function (select) {
Lists.select("filterDlg_Actions", select);
},
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;
},
});
});

230
js/PrefHelpers.js Normal file
View File

@ -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("pref-profiles-list");
},
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;
});

168
js/PrefLabelTree.js Normal file
View File

@ -0,0 +1,168 @@
/* global lib,dijit */
define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree", "dijit/form/DropDownButton"], function (declare, domConstruct) {
return declare("fox.PrefLabelTree", lib.CheckBoxTree, {
setNameById: function (id, name) {
const item = this.model.store._itemsByIdentity['LABEL:' + id];
if (item)
this.model.store.setValue(item, 'name', name);
},
_createTreeNode: function(args) {
const tnode = this.inherited(arguments);
const fg_color = this.model.store.getValue(args.item, 'fg_color');
const bg_color = this.model.store.getValue(args.item, 'bg_color');
const type = this.model.store.getValue(args.item, 'type');
const bare_id = this.model.store.getValue(args.item, 'bare_id');
if (type == 'label') {
const label = dojo.doc.createElement('i');
//const fg_color = args.item.fg_color[0];
const bg_color = String(args.item.bg_color);
label.className = "material-icons icon-label";
label.id = 'icon-label-' + String(args.item.bare_id);
label.innerHTML = "label";
label.setStyle({
color: bg_color,
});
domConstruct.place(label, tnode.iconNode, 'before');
//tnode._labelIconNode = span;
//domConstruct.place(tnode._labelIconNode, tnode.labelNode, 'before');
}
return tnode;
},
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: 650px",
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 = $("icon-label-" + id);
if (e) {
if (bg) e.style.color = 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;
}
});
});

122
js/PrefUsers.js Normal file
View File

@ -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();
Notify.info(transport.responseText, true);
});
}
},
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;
});

316
js/common.js Executable file
View File

@ -0,0 +1,316 @@
'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 Promise((resolve, reject) => {
new Ajax.Request(url, {
parameters: params,
onComplete: function(reply) {
if (complete != undefined) complete(reply);
resolve(reply);
}
});
});
}
function xhrJson(url, params, complete) {
return new Promise((resolve, reject) => {
return xhrPost(url, params).then((reply) => {
let obj = null;
try {
obj = JSON.parse(reply.responseText);
} catch (e) {
console.error("xhrJson", e, reply);
}
if (complete != undefined) complete(obj);
resolve(obj);
});
});
}
/* 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");
},
select: function(elemId, selected) {
$(elemId).select("li").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);
}
});
},
};
// 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 = "";
notify.className = "notify";
console.warn('notify', msg, kind);
switch (kind) {
case this.KIND_INFO:
notify.addClassName("notify_info")
icon = "notifications";
break;
case this.KIND_ERROR:
notify.addClassName("notify_error");
icon = "error";
break;
case this.KIND_PROGRESS:
notify.addClassName("notify_progress");
icon = App.getInitParam("icon_indicator_white")
break;
default:
icon = "notifications";
}
if (icon)
if (icon.indexOf("data:image") != -1)
msgfmt = "<img src=\"%s\">".replace("%s", icon) + msgfmt;
else
msgfmt = "<i class='material-icons icon-notify'>%s</i>".replace("%s", icon) + msgfmt;
msgfmt += "<i class='material-icons icon-close' title=\"" +
__("Click to close") + "\" onclick=\"Notify.close()\">close</i>";
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 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");
if (w) {
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");
}
}
// htmlspecialchars()-alike for headlines data-content attribute
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}

0
js/index.html Normal file
View File

155
js/prefs.js Executable file
View File

@ -0,0 +1,155 @@
'use strict'
/* global dijit, __ */
let App;
let CommonDialogs;
let Filters;
let Users;
let Helpers;
const Plugins = {};
require(["dojo/_base/kernel",
"dojo/_base/declare",
"dojo/ready",
"dojo/parser",
"fox/AppBase",
"dojo/_base/loader",
"dojo/_base/html",
"dijit/ColorPalette",
"dijit/Dialog",
"dijit/form/Button",
"dijit/form/CheckBox",
"dijit/form/DropDownButton",
"dijit/form/FilteringSelect",
"dijit/form/MultiSelect",
"dijit/form/Form",
"dijit/form/RadioButton",
"dijit/form/ComboButton",
"dijit/form/Select",
"dijit/form/SimpleTextarea",
"dijit/form/TextBox",
"dijit/form/NumberSpinner",
"dijit/form/ValidationTextBox",
"dijit/InlineEditBox",
"dijit/layout/AccordionContainer",
"dijit/layout/AccordionPane",
"dijit/layout/BorderContainer",
"dijit/layout/ContentPane",
"dijit/layout/TabContainer",
"dijit/Menu",
"dijit/ProgressBar",
"dijit/Toolbar",
"dijit/Tree",
"dijit/tree/dndSource",
"dojo/data/ItemFileWriteStore",
"lib/CheckBoxStoreModel",
"lib/CheckBoxTree",
"fox/CommonDialogs",
"fox/CommonFilters",
"fox/PrefUsers",
"fox/PrefHelpers",
"fox/PrefFeedStore",
"fox/PrefFilterStore",
"fox/PrefFeedTree",
"fox/PrefFilterTree",
"fox/PrefLabelTree"], function (dojo, declare, ready, parser, AppBase) {
ready(function () {
try {
const _App = declare("fox.App", AppBase, {
constructor: function() {
parser.parse();
this.setLoadingProgress(50);
const clientTzOffset = new Date().getTimezoneOffset() * 60;
const params = {op: "rpc", method: "sanityCheck", clientTzOffset: clientTzOffset};
xhrPost("backend.php", params, (transport) => {
try {
this.backendSanityCallback(transport);
} catch (e) {
this.Error.report(e);
}
});
},
initSecondStage: function() {
this.enableCsrfSupport();
document.onkeydown = (event) => { return App.hotkeyHandler(event) };
document.onkeypress = (event) => { return App.hotkeyHandler(event) };
App.setLoadingProgress(50);
Notify.close();
let tab = App.urlParam('tab');
if (tab) {
tab = dijit.byId(tab + "Tab");
if (tab) {
dijit.byId("pref-tabs").selectChild(tab);
switch (App.urlParam('method')) {
case "editfeed":
window.setTimeout(function () {
CommonDialogs.editFeed(App.urlParam('methodparam'))
}, 100);
break;
default:
console.warn("initSecondStage, unknown method:", App.urlParam("method"));
}
}
} else {
let tab = localStorage.getItem("ttrss:prefs-tab");
if (tab) {
tab = dijit.byId(tab);
if (tab) {
dijit.byId("pref-tabs").selectChild(tab);
}
}
}
dojo.connect(dijit.byId("pref-tabs"), "selectChild", function (elem) {
localStorage.setItem("ttrss:prefs-tab", elem.id);
});
},
hotkeyHandler: function (event) {
if (event.target.nodeName == "INPUT" || event.target.nodeName == "TEXTAREA") return;
const action_name = App.keyeventToAction(event);
if (action_name) {
switch (action_name) {
case "feed_subscribe":
CommonDialogs.quickAddFeed();
return false;
case "create_label":
CommonDialogs.addLabel();
return false;
case "create_filter":
Filters.quickAddFilter();
return false;
case "help_dialog":
App.helpDialog("main");
return false;
case "toggle_night_mode":
App.toggleNightMode();
default:
console.log("unhandled action: " + action_name + "; keycode: " + event.which);
}
}
},
isPrefs: function() {
return true;
}
});
App = new _App();
} catch (e) {
this.Error.report(e);
}
});
});

666
js/tt-rss.js Normal file
View File

@ -0,0 +1,666 @@
'use strict'
/* global dijit,__ */
let App;
let CommonDialogs;
let Filters;
let Feeds;
let Headlines;
let Article;
let PluginHost;
const Plugins = {};
require(["dojo/_base/kernel",
"dojo/_base/declare",
"dojo/ready",
"dojo/parser",
"fox/AppBase",
"dojo/_base/loader",
"dojo/_base/html",
"dojo/query",
"dijit/ProgressBar",
"dijit/ColorPalette",
"dijit/Dialog",
"dijit/form/Button",
"dijit/form/ComboButton",
"dijit/form/CheckBox",
"dijit/form/DropDownButton",
"dijit/form/FilteringSelect",
"dijit/form/Form",
"dijit/form/RadioButton",
"dijit/form/Select",
"dijit/form/MultiSelect",
"dijit/form/SimpleTextarea",
"dijit/form/TextBox",
"dijit/form/ComboBox",
"dijit/form/ValidationTextBox",
"dijit/InlineEditBox",
"dijit/layout/AccordionContainer",
"dijit/layout/BorderContainer",
"dijit/layout/ContentPane",
"dijit/layout/TabContainer",
"dijit/PopupMenuItem",
"dijit/Menu",
"dijit/Toolbar",
"dijit/Tree",
"dijit/tree/dndSource",
"dijit/tree/ForestStoreModel",
"dojo/data/ItemFileWriteStore",
"fox/PluginHost",
"fox/CommonFilters",
"fox/CommonDialogs",
"fox/Feeds",
"fox/Headlines",
"fox/Article",
"fox/FeedStoreModel",
"fox/FeedTree"], function (dojo, declare, ready, parser, AppBase) {
ready(function () {
try {
const _App = declare("fox.App", AppBase, {
global_unread: -1,
_widescreen_mode: false,
view_mode: 'magazine',
hotkey_actions: {},
constructor: function () {
parser.parse();
if (!this.checkBrowserFeatures())
return;
this.setLoadingProgress(30);
this.initHotkeyActions();
const a = document.createElement('audio');
const hasAudio = !!a.canPlayType;
const hasSandbox = "sandbox" in document.createElement("iframe");
const hasMp3 = !!(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
const clientTzOffset = new Date().getTimezoneOffset() * 60;
const params = {
op: "rpc", method: "sanityCheck", hasAudio: hasAudio,
hasMp3: hasMp3,
clientTzOffset: clientTzOffset,
hasSandbox: hasSandbox
};
xhrPost("backend.php", params, (transport) => {
try {
App.backendSanityCallback(transport);
} catch (e) {
App.Error.report(e);
}
});
},
checkBrowserFeatures: function() {
let errorMsg = "";
['MutationObserver'].each(function(wf) {
if (! (wf in window)) {
errorMsg = `Browser feature check failed: <code>window.${wf}</code> not found.`;
throw $break;
}
});
if (errorMsg) {
this.Error.fatal(errorMsg, {info: navigator.userAgent});
}
return errorMsg == "";
},
initSecondStage: function () {
this.enableCsrfSupport();
if (parseInt(Cookie.get("view_mode")) != "") {
App.setViewMode(Cookie.get("view_mode"));
} else {
App.setViewMode("magazine");
}
Feeds.reload();
Article.close();
if (parseInt(Cookie.get("ttrss_fh_width")) > 0) {
dijit.byId("feeds-holder").domNode.setStyle(
{width: Cookie.get("ttrss_fh_width") + "px"});
}
dijit.byId("main").resize();
dojo.connect(dijit.byId('feeds-holder'), 'resize',
function (args) {
if (args && args.w >= 0) {
Cookie.set("ttrss_fh_width", args.w, App.getInitParam("cookie_lifetime"));
}
});
dojo.connect(dijit.byId('content-insert'), 'resize',
function (args) {
if (args && args.w >= 0 && args.h >= 0) {
Cookie.set("ttrss_ci_width", args.w, App.getInitParam("cookie_lifetime"));
Cookie.set("ttrss_ci_height", args.h, App.getInitParam("cookie_lifetime"));
}
});
const toolbar = document.forms["toolbar-main"];
dijit.getEnclosingWidget(toolbar.view_mode).attr('value',
App.getInitParam("default_view_mode"));
dijit.getEnclosingWidget(toolbar.order_by).attr('value',
App.getInitParam("default_view_order_by"));
const hash_feed_id = hash_get('f');
const hash_feed_is_cat = hash_get('c') == "1";
if (hash_feed_id != undefined) {
Feeds.setActive(hash_feed_id, hash_feed_is_cat);
}
App.setLoadingProgress(50);
this._widescreen_mode = App.getInitParam("widescreen");
this.switchPanelMode(this._widescreen_mode);
Headlines.initScrollHandler();
if (App.getInitParam("simple_update")) {
console.log("scheduling simple feed updater...");
window.setInterval(() => { Feeds.updateRandom() }, 30 * 1000);
}
if (App.getInitParam('check_for_updates')) {
window.setTimeout(() => {
App.checkForUpdates();
window.setInterval(() => {
App.checkForUpdates();
}, 3600 * 1000);
}, 60 * 1000);
}
console.log("second stage ok");
PluginHost.run(PluginHost.HOOK_INIT_COMPLETE, null);
},
checkForUpdates: function() {
console.log('checking for updates...');
xhrJson("backend.php", {op: 'rpc', method: 'checkforupdates'})
.then((reply) => {
console.log('update reply', reply);
if (reply.id) {
if (reply.docker)
$("updates-available").outerHTML = $("updates-available").outerHTML.replace("Git", "Docker Hub")
$("updates-available").show();
} else {
$("updates-available").hide();
}
});
},
updateTitle: function() {
let tmp = "Agriget";
if (this.global_unread > 0) {
tmp = "(" + this.global_unread + ") " + tmp;
}
document.title = tmp;
},
onViewModeChanged: function() {
return Feeds.reloadCurrent('');
},
isCombinedMode: function() {
return true;
},
getViewMode: function() {
return App.view_mode;
},
setViewMode: function(invm) {
App.view_mode = invm;
Cookie.set("view_mode", invm, App.getInitParam("cookie_lifetime"));
App.displayViewMode();
},
displayViewMode: function() {
if (document.getElementById('display-buttons')) {
const rows = $$("#headlines-frame > div[id*=RROW]");
if (App.view_mode == "magazine") {
rows.each((row) => {
row.addClassName("view-magazine");
row.removeClassName("view-cards");
row.removeClassName("view-full");
})
$("headlines-frame").removeClassName("grid-cards-three");
$("headlines-frame").removeClassName("grid-cards-two");
$("headlines-frame").removeClassName("grid-cards-one");
$("display-magazine").addClassName("active-display");
$("display-cards").addClassName("inactive-display");
$("display-full").addClassName("inactive-display");
$("display-magazine").removeClassName("inactive-display");
$("display-cards").removeClassName("active-display");
$("display-full").removeClassName("active-display");
} else if (App.view_mode == "cards") {
rows.each((row) => {
row.removeClassName("view-magazine");
row.addClassName("view-cards");
row.removeClassName("view-full");
})
checkCardCount();
$("display-magazine").addClassName("inactive-display");
$("display-cards").addClassName("active-display");
$("display-full").addClassName("inactive-display");
$("display-magazine").removeClassName("active-display");
$("display-cards").removeClassName("inactive-display");
$("display-full").removeClassName("active-display");
} else {
rows.each((row) => {
row.removeClassName("view-magazine");
row.removeClassName("view-cards");
row.addClassName("view-full");
})
$("headlines-frame").removeClassName("grid-cards-three");
$("headlines-frame").removeClassName("grid-cards-two");
$("headlines-frame").removeClassName("grid-cards-one");
$("display-magazine").addClassName("inactive-display");
$("display-cards").addClassName("inactive-display");
$("display-full").addClassName("active-display");
$("display-magazine").removeClassName("active-display");
$("display-cards").removeClassName("active-display");
$("display-full").removeClassName("inactive-display");
}
}
},
hotkeyHandler(event) {
if (event.target.nodeName == "INPUT" || event.target.nodeName == "TEXTAREA") return;
// Arrow buttons and escape are not reported via keypress, handle them via keydown.
// escape = 27, left = 37, up = 38, right = 39, down = 40
if (event.type == "keydown" && event.which != 27 && (event.which < 37 || event.which > 40)) return;
const action_name = App.keyeventToAction(event);
if (action_name) {
const action_func = this.hotkey_actions[action_name];
if (action_func != null) {
action_func();
event.stopPropagation();
return false;
}
}
},
switchPanelMode: function(wide) {
//if (App.isCombinedMode()) return;
const article_id = Article.getActive();
if (wide) {
dijit.byId("headlines-wrap-inner").attr("design", 'sidebar');
dijit.byId("content-insert").attr("region", "trailing");
dijit.byId("content-insert").domNode.setStyle({width: '50%',
height: 'auto',
borderTopWidth: '0px' });
if (parseInt(Cookie.get("ttrss_ci_width")) > 0) {
dijit.byId("content-insert").domNode.setStyle(
{width: Cookie.get("ttrss_ci_width") + "px" });
}
$("headlines-frame").setStyle({ borderBottomWidth: '0px' });
$("headlines-frame").addClassName("wide");
} else {
dijit.byId("content-insert").attr("region", "bottom");
dijit.byId("content-insert").domNode.setStyle({width: 'auto',
height: '50%',
borderTopWidth: '0px'});
if (parseInt(Cookie.get("ttrss_ci_height")) > 0) {
dijit.byId("content-insert").domNode.setStyle(
{height: Cookie.get("ttrss_ci_height") + "px" });
}
$("headlines-frame").setStyle({ borderBottomWidth: '1px' });
$("headlines-frame").removeClassName("wide");
}
Article.close();
if (article_id) Article.view(article_id);
xhrPost("backend.php", {op: "rpc", method: "setpanelmode", wide: wide ? 1 : 0});
},
initHotkeyActions: function() {
this.hotkey_actions["next_feed"] = function () {
const rv = dijit.byId("feedTree").getNextFeed(
Feeds.getActive(), Feeds.activeIsCat());
if (rv) Feeds.open({feed: rv[0], is_cat: rv[1], delayed: true})
};
this.hotkey_actions["prev_feed"] = function () {
const rv = dijit.byId("feedTree").getPreviousFeed(
Feeds.getActive(), Feeds.activeIsCat());
if (rv) Feeds.open({feed: rv[0], is_cat: rv[1], delayed: true})
};
this.hotkey_actions["next_article"] = function () {
Headlines.move('next');
};
this.hotkey_actions["prev_article"] = function () {
Headlines.move('prev');
};
this.hotkey_actions["next_article_noscroll"] = function () {
Headlines.move('next', true);
};
this.hotkey_actions["prev_article_noscroll"] = function () {
Headlines.move('prev', true);
};
this.hotkey_actions["next_article_noexpand"] = function () {
Headlines.move('next', true, true);
};
this.hotkey_actions["prev_article_noexpand"] = function () {
Headlines.move('prev', true, true);
};
this.hotkey_actions["search_dialog"] = function () {
Feeds.search();
};
this.hotkey_actions["toggle_mark"] = function () {
Headlines.selectionToggleMarked();
};
this.hotkey_actions["toggle_publ"] = function () {
Headlines.selectionTogglePublished();
};
this.hotkey_actions["toggle_unread"] = function () {
Headlines.selectionToggleUnread({no_error: 1});
};
this.hotkey_actions["edit_tags"] = function () {
const id = Article.getActive();
if (id) {
Article.editTags(id);
}
};
this.hotkey_actions["open_in_new_window"] = function () {
if (Article.getActive()) {
Article.openInNewWindow(Article.getActive());
}
};
this.hotkey_actions["catchup_below"] = function () {
Headlines.catchupRelativeTo(1);
};
this.hotkey_actions["catchup_above"] = function () {
Headlines.catchupRelativeTo(0);
};
this.hotkey_actions["article_scroll_down"] = function () {
Article.scroll(40);
};
this.hotkey_actions["article_scroll_up"] = function () {
Article.scroll(-40);
};
this.hotkey_actions["close_article"] = function () {
if (App.isCombinedMode()) {
Article.cdmUnsetActive();
} else {
Article.close();
}
};
this.hotkey_actions["email_article"] = function () {
if (typeof Plugins.Mail != "undefined") {
Plugins.Mail.onHotkey(Headlines.getSelected());
} else {
alert(__("Please enable mail or mailto plugin first."));
}
};
this.hotkey_actions["select_all"] = function () {
Headlines.select('all');
};
this.hotkey_actions["select_unread"] = function () {
Headlines.select('unread');
};
this.hotkey_actions["select_marked"] = function () {
Headlines.select('marked');
};
this.hotkey_actions["select_published"] = function () {
Headlines.select('published');
};
this.hotkey_actions["select_invert"] = function () {
Headlines.select('invert');
};
this.hotkey_actions["select_none"] = function () {
Headlines.select('none');
};
this.hotkey_actions["feed_refresh"] = function () {
if (Feeds.getActive() != undefined) {
Feeds.open({feed: Feeds.getActive(), is_cat: Feeds.activeIsCat()});
}
};
this.hotkey_actions["feed_unhide_read"] = function () {
Feeds.toggleUnread();
};
this.hotkey_actions["feed_subscribe"] = function () {
CommonDialogs.quickAddFeed();
};
this.hotkey_actions["feed_debug_update"] = function () {
if (!Feeds.activeIsCat() && parseInt(Feeds.getActive()) > 0) {
window.open("backend.php?op=feeds&method=update_debugger&feed_id=" + Feeds.getActive() +
"&csrf_token=" + App.getInitParam("csrf_token"));
} else {
alert("You can't debug this kind of feed.");
}
};
this.hotkey_actions["feed_debug_viewfeed"] = function () {
Feeds.open({feed: Feeds.getActive(), is_cat: Feeds.activeIsCat(), viewfeed_debug: true});
};
this.hotkey_actions["feed_edit"] = function () {
if (Feeds.activeIsCat())
alert(__("You can't edit this kind of feed."));
else
CommonDialogs.editFeed(Feeds.getActive());
};
this.hotkey_actions["feed_catchup"] = function () {
if (Feeds.getActive() != undefined) {
Feeds.catchupCurrent();
}
};
this.hotkey_actions["feed_reverse"] = function () {
Headlines.reverse();
};
this.hotkey_actions["feed_toggle_vgroup"] = function () {
xhrPost("backend.php", {op: "rpc", method: "togglepref", key: "VFEED_GROUP_BY_FEED"}, () => {
Feeds.reloadCurrent();
})
};
this.hotkey_actions["catchup_all"] = function () {
Feeds.catchupAll();
};
this.hotkey_actions["cat_toggle_collapse"] = function () {
if (Feeds.activeIsCat()) {
dijit.byId("feedTree").collapseCat(Feeds.getActive());
}
};
this.hotkey_actions["goto_all"] = function () {
Feeds.open({feed: -4});
};
this.hotkey_actions["goto_fresh"] = function () {
Feeds.open({feed: -3});
};
this.hotkey_actions["goto_marked"] = function () {
Feeds.open({feed: -1});
};
this.hotkey_actions["goto_published"] = function () {
Feeds.open({feed: -2});
};
this.hotkey_actions["goto_tagcloud"] = function () {
App.displayDlg(__("Tag cloud"), "printTagCloud");
};
this.hotkey_actions["goto_prefs"] = function () {
document.location.href = "prefs.php";
};
this.hotkey_actions["select_article_cursor"] = function () {
const id = Article.getUnderPointer();
if (id) {
const row = $("RROW-" + id);
if (row)
row.toggleClassName("Selected");
}
};
this.hotkey_actions["create_label"] = function () {
CommonDialogs.addLabel();
};
this.hotkey_actions["create_filter"] = function () {
Filters.quickAddFilter();
};
this.hotkey_actions["collapse_sidebar"] = function () {
Feeds.toggle();
};
this.hotkey_actions["toggle_embed_original"] = function () {
if (typeof embedOriginalArticle != "undefined") {
if (Article.getActive())
embedOriginalArticle(Article.getActive());
} else {
alert(__("Please enable embed_original plugin first."));
}
};
this.hotkey_actions["toggle_widescreen"] = function () {
if (!App.isCombinedMode()) {
App._widescreen_mode = !App._widescreen_mode;
// reset stored sizes because geometry changed
Cookie.set("ttrss_ci_width", 0);
Cookie.set("ttrss_ci_height", 0);
App.switchPanelMode(App._widescreen_mode);
} else {
alert(__("Widescreen is not available in combined mode."));
}
};
this.hotkey_actions["help_dialog"] = function () {
App.helpDialog("main");
};
this.hotkey_actions["toggle_combined_mode"] = function () {
const value = App.isCombinedMode() ? "false" : "true";
xhrPost("backend.php", {op: "rpc", method: "setpref", key: "COMBINED_DISPLAY_MODE", value: value}, () => {
App.setInitParam("combined_display_mode",
!App.getInitParam("combined_display_mode"));
Article.close();
Headlines.renderAgain();
})
};
this.hotkey_actions["toggle_cdm_expanded"] = function () {
const value = App.getInitParam("cdm_expanded") ? "false" : "true";
xhrPost("backend.php", {op: "rpc", method: "setpref", key: "CDM_EXPANDED", value: value}, () => {
App.setInitParam("cdm_expanded", !App.getInitParam("cdm_expanded"));
Headlines.renderAgain();
});
};
this.hotkey_actions["toggle_night_mode"] = function () {
App.toggleNightMode();
};
},
onActionSelected: function(opid) {
switch (opid) {
case "qmcPrefs":
document.location.href = "prefs.php";
break;
case "qmcLogout":
document.location.href = "backend.php?op=logout";
break;
case "qmcTagCloud":
App.displayDlg(__("Tag cloud"), "printTagCloud");
break;
case "qmcSearch":
Feeds.search();
break;
case "qmcAddFeed":
CommonDialogs.quickAddFeed();
break;
case "qmcDigest":
window.location.href = "backend.php?op=digest";
break;
case "qmcEditFeed":
if (Feeds.activeIsCat())
alert(__("You can't edit this kind of feed."));
else
CommonDialogs.editFeed(Feeds.getActive());
break;
case "qmcRemoveFeed":
const actid = Feeds.getActive();
if (!actid) {
alert(__("Please select some feed first."));
return;
}
if (Feeds.activeIsCat()) {
alert(__("You can't unsubscribe from the category."));
return;
}
const fn = Feeds.getName(actid);
if (confirm(__("Unsubscribe from %s?").replace("%s", fn))) {
CommonDialogs.unsubscribeFeed(actid);
}
break;
case "qmcCatchupAll":
Feeds.catchupAll();
break;
case "qmcShowOnlyUnread":
Feeds.toggleUnread();
break;
case "qmcToggleWidescreen":
if (!App.isCombinedMode()) {
App._widescreen_mode = !App._widescreen_mode;
// reset stored sizes because geometry changed
Cookie.set("ttrss_ci_width", 0);
Cookie.set("ttrss_ci_height", 0);
App.switchPanelMode(App._widescreen_mode);
} else {
alert(__("Widescreen is not available in combined mode."));
}
break;
case "qmcToggleNightMode":
App.toggleNightMode();
break;
case "qmcHKhelp":
App.helpDialog("main");
break;
default:
console.log("quickMenuGo: unknown action: " + opid);
}
},
isPrefs: function() {
return false;
}
});
App = new _App();
} catch (e) {
App.Error.report(e);
}
});
});
function hash_get(key) {
const kv = window.location.hash.substring(1).toQueryParams();
return kv[key];
}
function hash_set(key, value) {
const kv = window.location.hash.substring(1).toQueryParams();
kv[key] = value;
window.location.hash = $H(kv).toQueryString();
}

64
js/updates.js Normal file
View File

@ -0,0 +1,64 @@
var changeStyle = function(selector, prop, value) {
var styles = document.styleSheets
var b = false;
for (var x = 0; x < styles.length; x++) {
var style = styles[x].cssRules || styles[x].rules;
for (var i = 0; i < style.length; i++) {
if (style[i].selectorText == selector) {
style[i].style[prop] = value;
b = true;
break;
}
}
if (b) break;
}
}
var toggleToolbar = function() {
var styles = document.styleSheets
var b = false;
for (var x = 0; x < styles.length; x++) {
var style = styles[x].cssRules || styles[x].rules;
for (var i = 0; i < style.length; i++) {
if (style[i].selectorText == '.toolbar-hide') {
if (style[i].style['visibility'] == 'hidden') {
style[i].style['visibility'] = 'visible';
document.getElementById('display-buttons').style.display = 'none';
} else {
style[i].style['visibility'] = 'hidden';
document.getElementById('display-buttons').style.display = 'inline';
}
b = true;
break;
}
}
if (b) break;
}
}
window.addEventListener("resize", checkCardCount);
function checkCardCount() {
var hf = $("headlines-frame");
if (hf) {
if (App.getViewMode() == "cards") {
setTimeout(function() {
w = hf.clientWidth;
if (w > 1100) {
$("headlines-frame").removeClassName("grid-cards-one");
$("headlines-frame").removeClassName("grid-cards-two");
$("headlines-frame").addClassName("grid-cards-three");
} else if (w < 780) {
$("headlines-frame").addClassName("grid-cards-one");
$("headlines-frame").removeClassName("grid-cards-two");
$("headlines-frame").removeClassName("grid-cards-three");
} else {
$("headlines-frame").removeClassName("grid-cards-one");
$("headlines-frame").addClassName("grid-cards-two");
$("headlines-frame").removeClassName("grid-cards-three");
}
}, 1);
}
}
}