Automated build for v0.01
This commit is contained in:
473
js/AppBase.js
Normal file
473
js/AppBase.js
Normal 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
394
js/Article.js
Normal 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 += " ";
|
||||
|
||||
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>
|
||||
<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¶m=" + 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
483
js/CommonDialogs.js
Normal 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(/&key=.*$/,
|
||||
"&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
380
js/CommonFilters.js
Normal 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
98
js/FeedStoreModel.js
Normal 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
541
js/FeedTree.js
Executable 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(/"/g, "\"");
|
||||
name = name.replace(/&/g, "&");
|
||||
name = name.replace(/—/g, "-");
|
||||
name = name.replace(/</g, "<");
|
||||
name = name.replace(/>/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
565
js/Feeds.js
Normal 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¶m=" +
|
||||
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
1616
js/Headlines.js
Executable file
File diff suppressed because it is too large
Load Diff
38
js/PluginHost.js
Normal file
38
js/PluginHost.js
Normal 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
20
js/PrefFeedStore.js
Normal 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
412
js/PrefFeedTree.js
Normal 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
22
js/PrefFilterStore.js
Normal 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
247
js/PrefFilterTree.js
Normal 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
230
js/PrefHelpers.js
Normal 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
168
js/PrefLabelTree.js
Normal 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
122
js/PrefUsers.js
Normal 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
316
js/common.js
Executable 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 = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
|
||||
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
||||
}
|
0
js/index.html
Normal file
0
js/index.html
Normal file
155
js/prefs.js
Executable file
155
js/prefs.js
Executable 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
666
js/tt-rss.js
Normal 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
64
js/updates.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user