Merge branch 'master' into german-translation

master
Heiko Adams 8 years ago
commit 2842cf6e45

@ -57,6 +57,20 @@
if (!init_plugins()) return; if (!init_plugins()) return;
if ($_SESSION["uid"]) {
if (!validate_session()) {
header("Content-Type: text/json");
print json_encode(array("seq" => -1,
"status" => 1,
"content" => array("error" => "NOT_LOGGED_IN")));
return;
}
load_user_plugins( $_SESSION["uid"]);
}
$method = strtolower($_REQUEST["op"]); $method = strtolower($_REQUEST["op"]);
$handler = new API($_REQUEST); $handler = new API($_REQUEST);

@ -330,7 +330,7 @@ class API extends Handler {
if ($article_id) { if ($article_id) {
$query = "SELECT id,title,link,content,feed_id,comments,int_id, $query = "SELECT id,guid,title,link,content,feed_id,comments,int_id,
marked,unread,published,score,note,lang, marked,unread,published,score,note,lang,
".SUBSTRING_FOR_DATE."(updated,1,16) as updated, ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
author,(SELECT title FROM ttrss_feeds WHERE id = feed_id) AS feed_title, author,(SELECT title FROM ttrss_feeds WHERE id = feed_id) AS feed_title,
@ -352,6 +352,7 @@ class API extends Handler {
$article = array( $article = array(
"id" => $line["id"], "id" => $line["id"],
"guid" => $line["guid"],
"title" => $line["title"], "title" => $line["title"],
"link" => $line["link"], "link" => $line["link"],
"labels" => get_article_labels($line['id']), "labels" => get_article_labels($line['id']),
@ -753,6 +754,7 @@ class API extends Handler {
$headline_row = array( $headline_row = array(
"id" => (int)$line["id"], "id" => (int)$line["id"],
"guid" => $line["guid"],
"unread" => sql_bool_to_bool($line["unread"]), "unread" => sql_bool_to_bool($line["unread"]),
"marked" => sql_bool_to_bool($line["marked"]), "marked" => sql_bool_to_bool($line["marked"]),
"published" => sql_bool_to_bool($line["published"]), "published" => sql_bool_to_bool($line["published"]),

@ -37,7 +37,7 @@ class Pref_Users extends Handler_Protected {
$access_level = $this->dbh->fetch_result($result, 0, "access_level"); $access_level = $this->dbh->fetch_result($result, 0, "access_level");
$email = $this->dbh->fetch_result($result, 0, "email"); $email = $this->dbh->fetch_result($result, 0, "email");
$sel_disabled = ($id == $_SESSION["uid"]) ? "disabled" : ""; $sel_disabled = ($id == $_SESSION["uid"] || $login == "admin") ? "disabled" : "";
print "<div class=\"dlgSec\">".__("User")."</div>"; print "<div class=\"dlgSec\">".__("User")."</div>";
print "<div class=\"dlgSecCont\">"; print "<div class=\"dlgSecCont\">";

@ -438,7 +438,7 @@ class RPC extends Handler_Protected {
if ($this->dbh->num_rows($result) == 0) { if ($this->dbh->num_rows($result) == 0) {
$result = $this->dbh->query("INSERT INTO ttrss_feeds $result = $this->dbh->query("INSERT INTO ttrss_feeds
(owner_uid,feed_url,title,cat_id,site_url) (owner_uid,feed_url,title,cat_id,site_url)
VALUES ('$id','".$_SESSION["uid"]."', VALUES ('".$_SESSION["uid"]."',
'$feed_url', '$title', NULL, '$site_url')"); '$feed_url', '$title', NULL, '$site_url')");
} }
} }

@ -16,7 +16,9 @@
libxml_disable_entity_loader(true); libxml_disable_entity_loader(true);
mb_internal_encoding("UTF-8"); // separate test because this is included before sanity checks
if (function_exists("mb_internal_encoding")) mb_internal_encoding("UTF-8");
date_default_timezone_set('UTC'); date_default_timezone_set('UTC');
if (defined('E_DEPRECATED')) { if (defined('E_DEPRECATED')) {
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED); error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
@ -831,14 +833,17 @@
return $csrf_token == $_SESSION['csrf_token']; return $csrf_token == $_SESSION['csrf_token'];
} }
function load_user_plugins($owner_uid) { function load_user_plugins($owner_uid, $pluginhost = false) {
if (!$pluginhost) $pluginhost = PluginHost::getInstance();
if ($owner_uid && SCHEMA_VERSION >= 100) { if ($owner_uid && SCHEMA_VERSION >= 100) {
$plugins = get_pref("_ENABLED_PLUGINS", $owner_uid); $plugins = get_pref("_ENABLED_PLUGINS", $owner_uid);
PluginHost::getInstance()->load($plugins, PluginHost::KIND_USER, $owner_uid); $pluginhost->load($plugins, PluginHost::KIND_USER, $owner_uid);
if (get_schema_version() > 100) { if (get_schema_version() > 100) {
PluginHost::getInstance()->load_data(); $pluginhost->load_data();
} }
} }
} }

@ -89,6 +89,7 @@
"feed_edit" => __("Edit feed"), "feed_edit" => __("Edit feed"),
"feed_catchup" => __("Mark as read"), "feed_catchup" => __("Mark as read"),
"feed_reverse" => __("Reverse headlines"), "feed_reverse" => __("Reverse headlines"),
"feed_toggle_vgroup" => __("Toggle headline grouping"),
"feed_debug_update" => __("Debug feed update"), "feed_debug_update" => __("Debug feed update"),
"feed_debug_viewfeed" => __("Debug viewfeed()"), "feed_debug_viewfeed" => __("Debug viewfeed()"),
"catchup_all" => __("Mark all feeds as read"), "catchup_all" => __("Mark all feeds as read"),
@ -158,6 +159,7 @@
"f e" => "feed_edit", "f e" => "feed_edit",
"f q" => "feed_catchup", "f q" => "feed_catchup",
"f x" => "feed_reverse", "f x" => "feed_reverse",
"f g" => "feed_toggle_vgroup",
"f *d" => "feed_debug_update", "f *d" => "feed_debug_update",
"f *g" => "feed_debug_viewfeed", "f *g" => "feed_debug_viewfeed",
"f *c" => "toggle_combined_mode", "f *c" => "toggle_combined_mode",
@ -1062,6 +1064,10 @@
array_push($attrs_to_remove, $attr); array_push($attrs_to_remove, $attr);
} }
if ($attr->nodeName == 'href' && stripos($attr->value, 'javascript:') === 0) {
array_push($attrs_to_remove, $attr);
}
if (in_array($attr->nodeName, $disallowed_attributes)) { if (in_array($attr->nodeName, $disallowed_attributes)) {
array_push($attrs_to_remove, $attr); array_push($attrs_to_remove, $attr);
} }
@ -2443,4 +2449,20 @@
return $tmp; return $tmp;
} }
function get_upload_error_message($code) {
$errors = array(
0 => __('There is no error, the file uploaded with success'),
1 => __('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
2 => __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
3 => __('The uploaded file was only partially uploaded'),
4 => __('No file was uploaded'),
6 => __('Missing a temporary folder'),
7 => __('Failed to write file to disk.'),
8 => __('A PHP extension stopped the file upload.'),
);
return $errors[$code];
}
?> ?>

@ -179,6 +179,8 @@
$nf = 0; $nf = 0;
$bstarted = microtime(true); $bstarted = microtime(true);
$batch_owners = array();
// For each feed, we call the feed update function. // For each feed, we call the feed update function.
foreach ($feeds_to_update as $feed) { foreach ($feeds_to_update as $feed) {
if($debug) _debug("Base feed: $feed"); if($debug) _debug("Base feed: $feed");
@ -204,6 +206,9 @@
while ($tline = db_fetch_assoc($tmp_result)) { while ($tline = db_fetch_assoc($tmp_result)) {
if($debug) _debug(" => " . $tline["last_updated"] . ", " . $tline["id"] . " " . $tline["owner_uid"]); if($debug) _debug(" => " . $tline["last_updated"] . ", " . $tline["id"] . " " . $tline["owner_uid"]);
if (array_search($tline["owner_uid"], $batch_owners) === FALSE)
array_push($batch_owners, $tline["owner_uid"]);
$fstarted = microtime(true); $fstarted = microtime(true);
$rss = update_rss_feed($tline["id"], true, false); $rss = update_rss_feed($tline["id"], true, false);
_debug_suppress(false); _debug_suppress(false);
@ -220,6 +225,12 @@
microtime(true) - $bstarted, (microtime(true) - $bstarted) / $nf)); microtime(true) - $bstarted, (microtime(true) - $bstarted) / $nf));
} }
foreach ($batch_owners as $owner_uid) {
_debug("Running housekeeping tasks for user $owner_uid...");
housekeeping_user($owner_uid);
}
require_once "digest.php"; require_once "digest.php";
// Send feed digests by email if needed. // Send feed digests by email if needed.
@ -726,7 +737,8 @@
"language" => $entry_language, "language" => $entry_language,
"feed" => array("id" => $feed, "feed" => array("id" => $feed,
"fetch_url" => $fetch_url, "fetch_url" => $fetch_url,
"site_url" => $site_url) "site_url" => $site_url,
"cache_images" => $cache_images)
); );
$entry_plugin_data = ""; $entry_plugin_data = "";
@ -778,7 +790,7 @@
foreach ($article as $k => $v) { foreach ($article as $k => $v) {
// i guess we'll have to take the risk of 4byte unicode labels & tags here // i guess we'll have to take the risk of 4byte unicode labels & tags here
if (!is_array($article[$k])) { if (is_string($article[$k])) {
$article[$k] = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $v); $article[$k] = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $v);
} }
} }
@ -1501,6 +1513,14 @@
_debug("Removed $frows (feeds) $crows (cats) orphaned counter cache entries."); _debug("Removed $frows (feeds) $crows (cats) orphaned counter cache entries.");
} }
function housekeeping_user($owner_uid) {
$tmph = new PluginHost();
load_user_plugins($owner_uid, $tmph);
$tmph->run_hooks(PluginHost::HOOK_HOUSE_KEEPING, "hook_house_keeping", "");
}
function housekeeping_common($debug) { function housekeeping_common($debug) {
expire_cached_files($debug); expire_cached_files($debug);
expire_lock_files($debug); expire_lock_files($debug);
@ -1516,6 +1536,5 @@
//_debug("Cleaned $rc cached tags."); //_debug("Cleaned $rc cached tags.");
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_HOUSE_KEEPING, "hook_house_keeping", ""); PluginHost::getInstance()->run_hooks(PluginHost::HOOK_HOUSE_KEEPING, "hook_house_keeping", "");
} }
?> ?>

@ -1,5 +1,5 @@
<?php <?php
define('VERSION_STATIC', '16.3'); define('VERSION_STATIC', '16.8');
function get_version() { function get_version() {
date_default_timezone_set('UTC'); date_default_timezone_set('UTC');

@ -88,6 +88,15 @@
<link rel="shortcut icon" type="image/png" href="images/favicon.png"/> <link rel="shortcut icon" type="image/png" href="images/favicon.png"/>
<link rel="icon" type="image/png" sizes="72x72" href="images/favicon-72px.png" /> <link rel="icon" type="image/png" sizes="72x72" href="images/favicon-72px.png" />
<script>
dojoConfig = {
async: true,
packages: [
{ name: "fox", location: "../../js" },
]
};
</script>
<?php <?php
foreach (array("lib/prototype.js", foreach (array("lib/prototype.js",
"lib/scriptaculous/scriptaculous.js?load=effects,controls", "lib/scriptaculous/scriptaculous.js?load=effects,controls",
@ -105,11 +114,16 @@
require_once 'lib/jshrink/Minifier.php'; require_once 'lib/jshrink/Minifier.php';
print get_minified_js(array("tt-rss", print get_minified_js(array("tt-rss",
"functions", "feedlist", "viewfeed", "FeedTree", "PluginHost")); "functions", "feedlist", "viewfeed", "PluginHost"));
foreach (PluginHost::getInstance()->get_plugins() as $n => $p) { foreach (PluginHost::getInstance()->get_plugins() as $n => $p) {
if (method_exists($p, "get_js")) { if (method_exists($p, "get_js")) {
echo "try {";
echo JShrink\Minifier::minify($p->get_js()); echo JShrink\Minifier::minify($p->get_js());
echo "} catch (e) {
console.warn('failed to initialize plugin JS: $n');
console.warn(e);
}";
} }
} }
@ -118,6 +132,7 @@
</script> </script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="referrer" content="no-referrer"/>
<script type="text/javascript"> <script type="text/javascript">
Event.observe(window, 'load', function() { Event.observe(window, 'load', function() {

File diff suppressed because it is too large Load Diff

@ -1,125 +1,126 @@
dojo.provide("fox.PrefFeedTree"); require(["dojo/_base/declare", "dojo/data/ItemFileWriteStore"], function (declare) {
dojo.provide("fox.PrefFeedStore");
dojo.require("lib.CheckBoxTree"); return declare("fox.PrefFeedStore", dojo.data.ItemFileWriteStore, {
dojo.require("dojo.data.ItemFileWriteStore");
dojo.declare("fox.PrefFeedStore", dojo.data.ItemFileWriteStore, { _saveEverything: function(saveCompleteCallback, saveFailedCallback,
newFileContentString) {
_saveEverything: function(saveCompleteCallback, saveFailedCallback, dojo.xhrPost({
newFileContentString) { url: "backend.php",
content: {op: "pref-feeds", method: "savefeedorder",
payload: newFileContentString},
error: saveFailedCallback,
load: saveCompleteCallback});
},
dojo.xhrPost({ });
url: "backend.php",
content: {op: "pref-feeds", method: "savefeedorder",
payload: newFileContentString},
error: saveFailedCallback,
load: saveCompleteCallback});
},
}); });
dojo.declare("fox.PrefFeedTree", lib.CheckBoxTree, { require(["dojo/_base/declare", "lib/CheckBoxTree"], function (declare) {
_createTreeNode: function(args) {
var tnode = this.inherited(arguments); return declare("fox.PrefFeedTree", lib.CheckBoxTree, {
_createTreeNode: function(args) {
if (args.item.icon) var tnode = this.inherited(arguments);
tnode.iconNode.src = args.item.icon[0];
if (args.item.icon)
var param = this.model.store.getValue(args.item, 'param'); tnode.iconNode.src = args.item.icon[0];
if (param) { var param = this.model.store.getValue(args.item, 'param');
param = dojo.doc.createElement('span');
param.className = 'feedParam'; if (param) {
param.innerHTML = args.item.param[0]; param = dojo.doc.createElement('span');
//dojo.place(param, tnode.labelNode, 'after'); param.className = 'feedParam';
dojo.place(param, tnode.rowNode, 'first'); param.innerHTML = args.item.param[0];
} //dojo.place(param, tnode.labelNode, 'after');
dojo.place(param, tnode.rowNode, 'first');
var id = args.item.id[0]; }
var bare_id = parseInt(id.substr(id.indexOf(':')+1));
var id = args.item.id[0];
if (id.match("CAT:") && bare_id > 0) { var bare_id = parseInt(id.substr(id.indexOf(':')+1));
var menu = new dijit.Menu();
menu.row_id = bare_id; if (id.match("CAT:") && bare_id > 0) {
menu.item = args.item; var menu = new dijit.Menu();
menu.row_id = bare_id;
menu.addChild(new dijit.MenuItem({ menu.item = args.item;
label: __("Edit category"),
onClick: function() { menu.addChild(new dijit.MenuItem({
editCat(this.getParent().row_id, this.getParent().item, null); label: __("Edit category"),
}})); onClick: function() {
editCat(this.getParent().row_id, this.getParent().item, null);
}}));
menu.addChild(new dijit.MenuItem({
label: __("Remove category"),
onClick: function() { menu.addChild(new dijit.MenuItem({
removeCategory(this.getParent().row_id, this.getParent().item); label: __("Remove category"),
}})); onClick: function() {
removeCategory(this.getParent().row_id, this.getParent().item);
menu.bindDomNode(tnode.domNode); }}));
tnode._menu = menu;
} else if (id.match("FEED:")) { menu.bindDomNode(tnode.domNode);
var menu = new dijit.Menu(); tnode._menu = menu;
menu.row_id = bare_id; } else if (id.match("FEED:")) {
menu.item = args.item; var menu = new dijit.Menu();
menu.row_id = bare_id;
menu.addChild(new dijit.MenuItem({ menu.item = args.item;
label: __("Edit feed"),
onClick: function() { menu.addChild(new dijit.MenuItem({
editFeed(this.getParent().row_id); label: __("Edit feed"),
}})); onClick: function() {
editFeed(this.getParent().row_id);
menu.addChild(new dijit.MenuItem({ }}));
label: __("Unsubscribe"),
onClick: function() { menu.addChild(new dijit.MenuItem({
unsubscribeFeed(this.getParent().row_id, this.getParent().item.name); label: __("Unsubscribe"),
}})); onClick: function() {
unsubscribeFeed(this.getParent().row_id, this.getParent().item.name);
menu.bindDomNode(tnode.domNode); }}));
tnode._menu = menu;
menu.bindDomNode(tnode.domNode);
} tnode._menu = menu;
return tnode; }
},
onDndDrop: function() { return tnode;
this.inherited(arguments); },
this.tree.model.store.save(); onDndDrop: function() {
}, this.inherited(arguments);
getRowClass: function (item, opened) { this.tree.model.store.save();
return (!item.error || item.error == '') ? "dijitTreeRow" : },
"dijitTreeRow Error"; getRowClass: function (item, opened) {
}, return (!item.error || item.error == '') ? "dijitTreeRow" :
getIconClass: function (item, opened) { "dijitTreeRow Error";
return (!item || this.model.store.getValue(item, 'type') == 'category') ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "feedIcon"; },
}, getIconClass: function (item, opened) {
checkItemAcceptance: function(target, source, position) { return (!item || this.model.store.getValue(item, 'type') == 'category') ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "feedIcon";
var item = dijit.getEnclosingWidget(target).item; },
checkItemAcceptance: function(target, source, position) {
// disable copying items var item = dijit.getEnclosingWidget(target).item;
source.copyState = function() { return false; };
// disable copying items
var source_item = false; source.copyState = function() { return false; };
source.forInSelectedItems(function(node) { var source_item = false;
source_item = node.data.item;
}); source.forInSelectedItems(function(node) {
source_item = node.data.item;
if (!source_item || !item) return false; });
var id = this.tree.model.store.getValue(item, 'id'); if (!source_item || !item) return false;
var source_id = source.tree.model.store.getValue(source_item, 'id');
var id = this.tree.model.store.getValue(item, 'id');
//console.log(id + " " + position + " " + source_id); var source_id = source.tree.model.store.getValue(source_item, 'id');
if (source_id.match("FEED:")) { //console.log(id + " " + position + " " + source_id);
return ((id.match("CAT:") && position == "over") ||
if (source_id.match("FEED:")) {
return ((id.match("CAT:") && position == "over") ||
(id.match("FEED:") && position != "over")); (id.match("FEED:") && position != "over"));
} else if (source_id.match("CAT:")) { } else if (source_id.match("CAT:")) {
return ((id.match("CAT:") && !id.match("CAT:0")) || return ((id.match("CAT:") && !id.match("CAT:0")) ||
(id.match("root") && position == "over")); (id.match("root") && position == "over"));
} }
}, },
});
}); });

@ -1,96 +1,102 @@
dojo.provide("fox.PrefFilterTree"); require(["dojo/_base/declare", "dojo/data/ItemFileWriteStore"], function (declare) {
dojo.require("lib.CheckBoxTree"); return declare("fox.PrefFilterStore", dojo.data.ItemFileWriteStore, {
dojo.require("dojo.data.ItemFileWriteStore");
dojo.declare("fox.PrefFilterStore", dojo.data.ItemFileWriteStore, { _saveEverything: function (saveCompleteCallback, saveFailedCallback,
newFileContentString) {
_saveEverything: function(saveCompleteCallback, saveFailedCallback, dojo.xhrPost({
newFileContentString) { url: "backend.php",
content: {
dojo.xhrPost({ op: "pref-filters", method: "savefilterorder",
url: "backend.php", payload: newFileContentString
content: {op: "pref-filters", method: "savefilterorder", },
payload: newFileContentString}, error: saveFailedCallback,
error: saveFailedCallback, load: saveCompleteCallback
load: saveCompleteCallback}); });
}, },
});
}); });
dojo.declare("fox.PrefFilterTree", lib.CheckBoxTree, { require(["dojo/_base/declare", "lib/CheckBoxTree"], function (declare) {
_createTreeNode: function(args) {
var tnode = this.inherited(arguments); return declare("fox.PrefFilterTree", lib.CheckBoxTree, {
_createTreeNode: function(args) {
var enabled = this.model.store.getValue(args.item, 'enabled'); var tnode = this.inherited(arguments);
var param = this.model.store.getValue(args.item, 'param');
var rules = this.model.store.getValue(args.item, 'rules'); var enabled = this.model.store.getValue(args.item, 'enabled');
var param = this.model.store.getValue(args.item, 'param');
if (param) { var rules = this.model.store.getValue(args.item, 'rules');
param = dojo.doc.createElement('span');
param.className = (enabled != false) ? 'labelParam' : 'labelParam filterDisabled'; if (param) {
param.innerHTML = args.item.param[0]; param = dojo.doc.createElement('span');
dojo.place(param, tnode.rowNode, 'first'); param.className = (enabled != false) ? 'labelParam' : 'labelParam filterDisabled';
} param.innerHTML = args.item.param[0];
dojo.place(param, tnode.rowNode, 'first');
if (rules) { }
param = dojo.doc.createElement('span');
param.className = 'filterRules'; if (rules) {
param.innerHTML = rules; param = dojo.doc.createElement('span');
dojo.place(param, tnode.rowNode, 'next'); param.className = 'filterRules';
} param.innerHTML = rules;
dojo.place(param, tnode.rowNode, 'next');
if (this.model.store.getValue(args.item, 'id') != 'root') { }
var img = dojo.doc.createElement('img');
img.src ='images/filter.png'; if (this.model.store.getValue(args.item, 'id') != 'root') {
img.className = 'markedPic'; var img = dojo.doc.createElement('img');
tnode._filterIconNode = img; img.src ='images/filter.png';
dojo.place(tnode._filterIconNode, tnode.labelNode, 'before'); img.className = 'markedPic';
} tnode._filterIconNode = img;
dojo.place(tnode._filterIconNode, tnode.labelNode, 'before');
return tnode; }
},
return tnode;
getLabel: function(item) { },
var label = item.name;
getLabel: function(item) {
var feed = this.model.store.getValue(item, 'feed'); var label = item.name;
var inverse = this.model.store.getValue(item, 'inverse');
var feed = this.model.store.getValue(item, 'feed');
if (feed) var inverse = this.model.store.getValue(item, 'inverse');
label += " (" + __("in") + " " + feed + ")";
if (feed)
if (inverse) label += " (" + __("in") + " " + feed + ")";
label += " (" + __("Inverse") + ")";
if (inverse)
/* if (item.param) label += " (" + __("Inverse") + ")";
label = "<span class=\"labelFixedLength\">" + label +
"</span>" + item.param[0]; */ /* if (item.param)
label = "<span class=\"labelFixedLength\">" + label +
return label; "</span>" + item.param[0]; */
},
getIconClass: function (item, opened) { return label;
return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "invisible"; },
}, getIconClass: function (item, opened) {
getLabelClass: function (item, opened) { return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "invisible";
var enabled = this.model.store.getValue(item, 'enabled'); },
return (enabled != false) ? "dijitTreeLabel labelFixedLength" : "dijitTreeLabel labelFixedLength filterDisabled"; getLabelClass: function (item, opened) {
}, var enabled = this.model.store.getValue(item, 'enabled');
getRowClass: function (item, opened) { return (enabled != false) ? "dijitTreeLabel labelFixedLength" : "dijitTreeLabel labelFixedLength filterDisabled";
return (!item.error || item.error == '') ? "dijitTreeRow" : },
"dijitTreeRow Error"; getRowClass: function (item, opened) {
}, return (!item.error || item.error == '') ? "dijitTreeRow" :
checkItemAcceptance: function(target, source, position) { "dijitTreeRow Error";
var item = dijit.getEnclosingWidget(target).item; },
checkItemAcceptance: function(target, source, position) {
// disable copying items var item = dijit.getEnclosingWidget(target).item;
source.copyState = function() { return false; };
// disable copying items
return position != 'over'; source.copyState = function() { return false; };
},
onDndDrop: function() { return position != 'over';
this.inherited(arguments); },
this.tree.model.store.save(); onDndDrop: function() {
}, this.inherited(arguments);
this.tree.model.store.save();
},
});
}); });

@ -1,43 +1,43 @@
dojo.provide("fox.PrefLabelTree"); require(["dojo/_base/declare", "lib/CheckBoxTree", "dijit/form/DropDownButton"], function (declare) {
dojo.require("lib.CheckBoxTree"); return declare("fox.PrefLabelTree", lib.CheckBoxTree, {
dojo.require("dijit.form.DropDownButton"); setNameById: function (id, name) {
var item = this.model.store._itemsByIdentity['LABEL:' + id];
dojo.declare("fox.PrefLabelTree", lib.CheckBoxTree, { if (item)
setNameById: function (id, name) { this.model.store.setValue(item, 'name', name);
var item = this.model.store._itemsByIdentity['LABEL:' + id];
if (item) },
this.model.store.setValue(item, 'name', name); _createTreeNode: function(args) {
var tnode = this.inherited(arguments);
}, var fg_color = this.model.store.getValue(args.item, 'fg_color');
_createTreeNode: function(args) { var bg_color = this.model.store.getValue(args.item, 'bg_color');
var tnode = this.inherited(arguments); var type = this.model.store.getValue(args.item, 'type');
var bare_id = this.model.store.getValue(args.item, 'bare_id');
var fg_color = this.model.store.getValue(args.item, 'fg_color'); if (type == 'label') {
var bg_color = this.model.store.getValue(args.item, 'bg_color'); var span = dojo.doc.createElement('span');
var type = this.model.store.getValue(args.item, 'type'); span.innerHTML = '&alpha;';
var bare_id = this.model.store.getValue(args.item, 'bare_id'); span.className = 'labelColorIndicator';
span.id = 'LICID-' + bare_id;
if (type == 'label') { span.setStyle({
var span = dojo.doc.createElement('span'); color: fg_color,
span.innerHTML = '&alpha;'; backgroundColor: bg_color});
span.className = 'labelColorIndicator';
span.id = 'LICID-' + bare_id;
span.setStyle({ tnode._labelIconNode = span;
color: fg_color,
backgroundColor: bg_color});
tnode._labelIconNode = span; dojo.place(tnode._labelIconNode, tnode.labelNode, 'before');
}
dojo.place(tnode._labelIconNode, tnode.labelNode, 'before'); return tnode;
} },
getIconClass: function (item, opened) {
return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "invisible";
},
});
return tnode;
},
getIconClass: function (item, opened) {
return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "invisible";
},
}); });

@ -889,22 +889,20 @@ function init_second_stage() {
loading_set_progress(50); loading_set_progress(50);
notify(""); notify("");
dojo.addOnLoad(function() { var tab = getURLParam('tab');
var tab = getURLParam('tab');
if (tab) { if (tab) {
tab = dijit.byId(tab + "Tab"); tab = dijit.byId(tab + "Tab");
if (tab) dijit.byId("pref-tabs").selectChild(tab); if (tab) dijit.byId("pref-tabs").selectChild(tab);
} }
var method = getURLParam('method'); var method = getURLParam('method');
if (method == 'editFeed') { if (method == 'editFeed') {
var param = getURLParam('methodparam'); var param = getURLParam('methodparam');
window.setTimeout('editFeed(' + param + ')', 100); window.setTimeout('editFeed(' + param + ')', 100);
} }
});
setTimeout("hotkey_prefix_timeout()", 5*1000); setTimeout("hotkey_prefix_timeout()", 5*1000);
@ -916,53 +914,55 @@ function init_second_stage() {
function init() { function init() {
try { try {
dojo.registerModulePath("lib", "..");
dojo.registerModulePath("fox", "../../js/");
dojo.require("dijit.ColorPalette");
dojo.require("dijit.Dialog");
dojo.require("dijit.form.Button");
dojo.require("dijit.form.CheckBox");
dojo.require("dijit.form.DropDownButton");
dojo.require("dijit.form.FilteringSelect");
dojo.require("dijit.form.Form");
dojo.require("dijit.form.RadioButton");
dojo.require("dijit.form.Select");
dojo.require("dijit.form.SimpleTextarea");
dojo.require("dijit.form.TextBox");
dojo.require("dijit.form.ValidationTextBox");
dojo.require("dijit.InlineEditBox");
dojo.require("dijit.layout.AccordionContainer");
dojo.require("dijit.layout.BorderContainer");
dojo.require("dijit.layout.ContentPane");
dojo.require("dijit.layout.TabContainer");
dojo.require("dijit.Menu");
dojo.require("dijit.ProgressBar");
dojo.require("dijit.ProgressBar");
dojo.require("dijit.Toolbar");
dojo.require("dijit.Tree");
dojo.require("dijit.tree.dndSource");
dojo.require("dojo.data.ItemFileWriteStore");
dojo.require("lib.CheckBoxTree");
dojo.require("fox.PrefFeedTree");
dojo.require("fox.PrefFilterTree");
dojo.require("fox.PrefLabelTree");
dojo.parser.parse();
dojo.addOnLoad(function() {
loading_set_progress(50);
var clientTzOffset = new Date().getTimezoneOffset() * 60;
new Ajax.Request("backend.php", { require(["dojo/_base/kernel",
parameters: {op: "rpc", method: "sanityCheck", "dojo/ready",
clientTzOffset: clientTzOffset }, "dojo/parser",
onComplete: function(transport) { "dojo/_base/loader",
backend_sanity_check_callback(transport); "dijit/ColorPalette",
} }); "dijit/Dialog",
}); "dijit/form/Button",
"dijit/form/CheckBox",
"dijit/form/DropDownButton",
"dijit/form/FilteringSelect",
"dijit/form/Form",
"dijit/form/RadioButton",
"dijit/form/ComboButton",
"dijit/form/Select",
"dijit/form/SimpleTextarea",
"dijit/form/TextBox",
"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",
"fox/PrefFeedTree",
"fox/PrefFilterTree",
"fox/PrefLabelTree" ], function (dojo, ready, parser) {
ready(function() {
parser.parse();
loading_set_progress(50);
var clientTzOffset = new Date().getTimezoneOffset() * 60;
new Ajax.Request("backend.php", {
parameters: {op: "rpc", method: "sanityCheck",
clientTzOffset: clientTzOffset },
onComplete: function(transport) {
backend_sanity_check_callback(transport);
} });
});
});
} catch (e) { } catch (e) {
exception_error("init", e); exception_error("init", e);

@ -218,372 +218,395 @@ function init() {
try { try {
//dojo.registerModulePath("fox", "../../js/"); //dojo.registerModulePath("fox", "../../js/");
dojo.require("fox.FeedTree"); require(["dojo/_base/kernel",
"dojo/ready",
dojo.require("dijit.ColorPalette"); "dojo/parser",
dojo.require("dijit.Dialog"); "dojo/_base/loader",
dojo.require("dijit.form.Button"); "dijit/ProgressBar",
dojo.require("dijit.form.CheckBox"); "dijit/ColorPalette",
dojo.require("dijit.form.DropDownButton"); "dijit/Dialog",
dojo.require("dijit.form.FilteringSelect"); "dijit/form/Button",
dojo.require("dijit.form.Form"); "dijit/form/ComboButton",
dojo.require("dijit.form.RadioButton"); "dijit/form/CheckBox",
dojo.require("dijit.form.Select"); "dijit/form/DropDownButton",
dojo.require("dijit.form.SimpleTextarea"); "dijit/form/FilteringSelect",
dojo.require("dijit.form.TextBox"); "dijit/form/Form",
dojo.require("dijit.form.ComboBox"); "dijit/form/RadioButton",
dojo.require("dijit.form.ValidationTextBox"); "dijit/form/Select",
dojo.require("dijit.InlineEditBox"); "dijit/form/SimpleTextarea",
dojo.require("dijit.layout.AccordionContainer"); "dijit/form/TextBox",
dojo.require("dijit.layout.BorderContainer"); "dijit/form/ComboBox",
dojo.require("dijit.layout.ContentPane"); "dijit/form/ValidationTextBox",
dojo.require("dijit.layout.TabContainer"); "dijit/InlineEditBox",
dojo.require("dijit.Menu"); "dijit/layout/AccordionContainer",
dojo.require("dijit.ProgressBar"); "dijit/layout/BorderContainer",
dojo.require("dijit.ProgressBar"); "dijit/layout/ContentPane",
dojo.require("dijit.Toolbar"); "dijit/layout/TabContainer",
dojo.require("dijit.Tree"); "dijit/PopupMenuItem",
dojo.require("dijit.tree.dndSource"); "dijit/Menu",
dojo.require("dojo.data.ItemFileWriteStore"); "dijit/Toolbar",
"dijit/Tree",
dojo.parser.parse(); "dijit/tree/dndSource",
"dijit/tree/ForestStoreModel",
if (!genericSanityCheck()) "dojo/data/ItemFileWriteStore",
return false; "fox/FeedTree" ], function (dojo, ready, parser) {
loading_set_progress(30); ready(function() {
var a = document.createElement('audio'); parser.parse();
if (!genericSanityCheck())
return false;
loading_set_progress(30);
var a = document.createElement('audio');
var hasAudio = !!a.canPlayType;
var hasSandbox = "sandbox" in document.createElement("iframe");
var hasMp3 = !!(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
var clientTzOffset = new Date().getTimezoneOffset() * 60;
init_hotkey_actions();
new Ajax.Request("backend.php", {
parameters: {op: "rpc", method: "sanityCheck", hasAudio: hasAudio,
hasMp3: hasMp3,
clientTzOffset: clientTzOffset,
hasSandbox: hasSandbox},
onComplete: function(transport) {
backend_sanity_check_callback(transport);
} });
});
var hasAudio = !!a.canPlayType;
var hasSandbox = "sandbox" in document.createElement("iframe");
var hasMp3 = !!(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
var clientTzOffset = new Date().getTimezoneOffset() * 60;
new Ajax.Request("backend.php", { });
parameters: {op: "rpc", method: "sanityCheck", hasAudio: hasAudio,
hasMp3: hasMp3,
clientTzOffset: clientTzOffset,
hasSandbox: hasSandbox},
onComplete: function(transport) {
backend_sanity_check_callback(transport);
} });
hotkey_actions["next_feed"] = function() {
var rv = dijit.byId("feedTree").getNextFeed(
getActiveFeedId(), activeFeedIsCat());
if (rv) viewfeed({feed: rv[0], is_cat: rv[1], can_wait: true}) } catch (e) {
}; exception_error("init", e);
hotkey_actions["prev_feed"] = function() { }
var rv = dijit.byId("feedTree").getPreviousFeed( }
getActiveFeedId(), activeFeedIsCat());
if (rv) viewfeed({feed: rv[0], is_cat: rv[1], can_wait: true}) function init_hotkey_actions() {
}; hotkey_actions["next_feed"] = function() {
hotkey_actions["next_article"] = function() { var rv = dijit.byId("feedTree").getNextFeed(
moveToPost('next'); getActiveFeedId(), activeFeedIsCat());
};
hotkey_actions["prev_article"] = function() { if (rv) viewfeed({feed: rv[0], is_cat: rv[1], can_wait: true})
moveToPost('prev'); };
}; hotkey_actions["prev_feed"] = function() {
hotkey_actions["next_article_noscroll"] = function() { var rv = dijit.byId("feedTree").getPreviousFeed(
moveToPost('next', true); getActiveFeedId(), activeFeedIsCat());
};
hotkey_actions["prev_article_noscroll"] = function() { if (rv) viewfeed({feed: rv[0], is_cat: rv[1], can_wait: true})
moveToPost('prev', true); };
}; hotkey_actions["next_article"] = function() {
hotkey_actions["next_article_noexpand"] = function() { moveToPost('next');
moveToPost('next', true, true); };
}; hotkey_actions["prev_article"] = function() {
hotkey_actions["prev_article_noexpand"] = function() { moveToPost('prev');
moveToPost('prev', true, true); };
}; hotkey_actions["next_article_noscroll"] = function() {
hotkey_actions["collapse_article"] = function() { moveToPost('next', true);
var id = getActiveArticleId(); };
var elem = $("CICD-"+id); hotkey_actions["prev_article_noscroll"] = function() {
moveToPost('prev', true);
if (elem) { };
if (elem.visible()) { hotkey_actions["next_article_noexpand"] = function() {
cdmCollapseArticle(null, id); moveToPost('next', true, true);
} };
else { hotkey_actions["prev_article_noexpand"] = function() {
cdmExpandArticle(id); moveToPost('prev', true, true);
} };
} hotkey_actions["collapse_article"] = function() {
}; var id = getActiveArticleId();
hotkey_actions["toggle_expand"] = function() { var elem = $("CICD-"+id);
var id = getActiveArticleId();
var elem = $("CICD-"+id); if (elem) {
if (elem.visible()) {
if (elem) { cdmCollapseArticle(null, id);
if (elem.visible()) {
cdmCollapseArticle(null, id, false);
}
else {
cdmExpandArticle(id);
}
}
};
hotkey_actions["search_dialog"] = function() {
search();
};
hotkey_actions["toggle_mark"] = function() {
selectionToggleMarked(undefined, false, true);
};
hotkey_actions["toggle_publ"] = function() {
selectionTogglePublished(undefined, false, true);
};
hotkey_actions["toggle_unread"] = function() {
selectionToggleUnread(undefined, false, true);
};
hotkey_actions["edit_tags"] = function() {
var id = getActiveArticleId();
if (id) {
editArticleTags(id);
};
} }
hotkey_actions["open_in_new_window"] = function() { else {
if (getActiveArticleId()) { cdmExpandArticle(id);
openArticleInNewWindow(getActiveArticleId()); }
return; }
} };
}; hotkey_actions["toggle_expand"] = function() {
hotkey_actions["catchup_below"] = function() { var id = getActiveArticleId();
catchupRelativeToArticle(1); var elem = $("CICD-"+id);
};
hotkey_actions["catchup_above"] = function() { if (elem) {
catchupRelativeToArticle(0); if (elem.visible()) {
cdmCollapseArticle(null, id, false);
}
else {
cdmExpandArticle(id);
}
}
};
hotkey_actions["search_dialog"] = function() {
search();
};
hotkey_actions["toggle_mark"] = function() {
selectionToggleMarked(undefined, false, true);
};
hotkey_actions["toggle_publ"] = function() {
selectionTogglePublished(undefined, false, true);
};
hotkey_actions["toggle_unread"] = function() {
selectionToggleUnread(undefined, false, true);
};
hotkey_actions["edit_tags"] = function() {
var id = getActiveArticleId();
if (id) {
editArticleTags(id);
}; };
hotkey_actions["article_scroll_down"] = function() { }
var ctr = $("content_insert") ? $("content_insert") : $("headlines-frame"); hotkey_actions["open_in_new_window"] = function() {
if (getActiveArticleId()) {
openArticleInNewWindow(getActiveArticleId());
return;
}
};
hotkey_actions["catchup_below"] = function() {
catchupRelativeToArticle(1);
};
hotkey_actions["catchup_above"] = function() {
catchupRelativeToArticle(0);
};
hotkey_actions["article_scroll_down"] = function() {
var ctr = $("content_insert") ? $("content_insert") : $("headlines-frame");
scrollArticle(40);
};
hotkey_actions["article_scroll_up"] = function() {
var ctr = $("content_insert") ? $("content_insert") : $("headlines-frame");
scrollArticle(-40);
};
hotkey_actions["close_article"] = function() {
if (isCdmMode()) {
if (!getInitParam("cdm_expanded")) {
cdmCollapseArticle(false, getActiveArticleId());
}
} else {
closeArticlePanel();
}
};
hotkey_actions["email_article"] = function() {
if (typeof emailArticle != "undefined") {
emailArticle();
} else if (typeof mailtoArticle != "undefined") {
mailtoArticle();
} else {
alert(__("Please enable mail plugin first."));
}
};
hotkey_actions["select_all"] = function() {
selectArticles('all');
};
hotkey_actions["select_unread"] = function() {
selectArticles('unread');
};
hotkey_actions["select_marked"] = function() {
selectArticles('marked');
};
hotkey_actions["select_published"] = function() {
selectArticles('published');
};
hotkey_actions["select_invert"] = function() {
selectArticles('invert');
};
hotkey_actions["select_none"] = function() {
selectArticles('none');
};
hotkey_actions["feed_refresh"] = function() {
if (getActiveFeedId() != undefined) {
viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat()});
return;
}
};
hotkey_actions["feed_unhide_read"] = function() {
toggleDispRead();
};
hotkey_actions["feed_subscribe"] = function() {
quickAddFeed();
};
hotkey_actions["feed_debug_update"] = function() {
if (!activeFeedIsCat() && parseInt(getActiveFeedId()) > 0) {
window.open("backend.php?op=feeds&method=update_debugger&feed_id=" + getActiveFeedId() +
"&csrf_token=" + getInitParam("csrf_token"));
} else {
alert("You can't debug this kind of feed.");
}
};
scrollArticle(40); hotkey_actions["feed_debug_viewfeed"] = function() {
}; viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat(), viewfeed_debug: true});
hotkey_actions["article_scroll_up"] = function() { };
var ctr = $("content_insert") ? $("content_insert") : $("headlines-frame");
scrollArticle(-40); hotkey_actions["feed_edit"] = function() {
}; if (activeFeedIsCat())
hotkey_actions["close_article"] = function() { alert(__("You can't edit this kind of feed."));
if (isCdmMode()) { else
if (!getInitParam("cdm_expanded")) { editFeed(getActiveFeedId());
cdmCollapseArticle(false, getActiveArticleId()); };
} hotkey_actions["feed_catchup"] = function() {
} else { if (getActiveFeedId() != undefined) {
closeArticlePanel(); catchupCurrentFeed();
} return;
}; }
hotkey_actions["email_article"] = function() { };
if (typeof emailArticle != "undefined") { hotkey_actions["feed_reverse"] = function() {
emailArticle(); reverseHeadlineOrder();
} else if (typeof mailtoArticle != "undefined") { };
mailtoArticle(); hotkey_actions["feed_toggle_vgroup"] = function() {
} else { var query_str = "?op=rpc&method=togglepref&key=VFEED_GROUP_BY_FEED";
alert(__("Please enable mail plugin first."));
}
};
hotkey_actions["select_all"] = function() {
selectArticles('all');
};
hotkey_actions["select_unread"] = function() {
selectArticles('unread');
};
hotkey_actions["select_marked"] = function() {
selectArticles('marked');
};
hotkey_actions["select_published"] = function() {
selectArticles('published');
};
hotkey_actions["select_invert"] = function() {
selectArticles('invert');
};
hotkey_actions["select_none"] = function() {
selectArticles('none');
};
hotkey_actions["feed_refresh"] = function() {
if (getActiveFeedId() != undefined) {
viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat()});
return;
}
};
hotkey_actions["feed_unhide_read"] = function() {
toggleDispRead();
};
hotkey_actions["feed_subscribe"] = function() {
quickAddFeed();
};
hotkey_actions["feed_debug_update"] = function() {
if (!activeFeedIsCat() && parseInt(getActiveFeedId()) > 0) {
window.open("backend.php?op=feeds&method=update_debugger&feed_id=" + getActiveFeedId() +
"&csrf_token=" + getInitParam("csrf_token"));
} else {
alert("You can't debug this kind of feed.");
}
};
hotkey_actions["feed_debug_viewfeed"] = function() { new Ajax.Request("backend.php", {
viewfeed({feed: getActiveFeedId(), is_cat: activeFeedIsCat(), viewfeed_debug: true}); parameters: query_str,
}; onComplete: function(transport) {
viewCurrentFeed();
} });
hotkey_actions["feed_edit"] = function() { };
if (activeFeedIsCat()) hotkey_actions["catchup_all"] = function() {
alert(__("You can't edit this kind of feed.")); catchupAllFeeds();
else };
editFeed(getActiveFeedId()); hotkey_actions["cat_toggle_collapse"] = function() {
}; if (activeFeedIsCat()) {
hotkey_actions["feed_catchup"] = function() { dijit.byId("feedTree").collapseCat(getActiveFeedId());
if (getActiveFeedId() != undefined) { return;
catchupCurrentFeed(); }
return; };
} hotkey_actions["goto_all"] = function() {
}; viewfeed({feed: -4});
hotkey_actions["feed_reverse"] = function() { };
reverseHeadlineOrder(); hotkey_actions["goto_fresh"] = function() {
}; viewfeed({feed: -3});
hotkey_actions["catchup_all"] = function() { };
catchupAllFeeds(); hotkey_actions["goto_marked"] = function() {
}; viewfeed({feed: -1});
hotkey_actions["cat_toggle_collapse"] = function() { };
if (activeFeedIsCat()) { hotkey_actions["goto_published"] = function() {
dijit.byId("feedTree").collapseCat(getActiveFeedId()); viewfeed({feed: -2});
return; };
} hotkey_actions["goto_tagcloud"] = function() {
}; displayDlg(__("Tag cloud"), "printTagCloud");
hotkey_actions["goto_all"] = function() { };
viewfeed({feed: -4}); hotkey_actions["goto_prefs"] = function() {
}; gotoPreferences();
hotkey_actions["goto_fresh"] = function() { };
viewfeed({feed: -3}); hotkey_actions["select_article_cursor"] = function() {
}; var id = getArticleUnderPointer();
hotkey_actions["goto_marked"] = function() { if (id) {
viewfeed({feed: -1}); var row = $("RROW-" + id);
};
hotkey_actions["goto_published"] = function() { if (row) {
viewfeed({feed: -2}); var cb = dijit.getEnclosingWidget(
}; row.getElementsByClassName("rchk")[0]);
hotkey_actions["goto_tagcloud"] = function() {
displayDlg(__("Tag cloud"), "printTagCloud"); if (cb) {
}; cb.attr("checked", !cb.attr("checked"));
hotkey_actions["goto_prefs"] = function() { toggleSelectRowById(cb, "RROW-" + id);
gotoPreferences(); return false;
};
hotkey_actions["select_article_cursor"] = function() {
var id = getArticleUnderPointer();
if (id) {
var row = $("RROW-" + id);
if (row) {
var cb = dijit.getEnclosingWidget(
row.getElementsByClassName("rchk")[0]);
if (cb) {
cb.attr("checked", !cb.attr("checked"));
toggleSelectRowById(cb, "RROW-" + id);
return false;
}
}
}
};
hotkey_actions["create_label"] = function() {
addLabel();
};
hotkey_actions["create_filter"] = function() {
quickAddFilter();
};
hotkey_actions["collapse_sidebar"] = function() {
collapse_feedlist();
};
hotkey_actions["toggle_embed_original"] = function() {
if (typeof embedOriginalArticle != "undefined") {
if (getActiveArticleId())
embedOriginalArticle(getActiveArticleId());
} else {
alert(__("Please enable embed_original plugin first."));
} }
}; }
hotkey_actions["toggle_widescreen"] = function() { }
if (!isCdmMode()) { };
_widescreen_mode = !_widescreen_mode; hotkey_actions["create_label"] = function() {
addLabel();
};
hotkey_actions["create_filter"] = function() {
quickAddFilter();
};
hotkey_actions["collapse_sidebar"] = function() {
collapse_feedlist();
};
hotkey_actions["toggle_embed_original"] = function() {
if (typeof embedOriginalArticle != "undefined") {
if (getActiveArticleId())
embedOriginalArticle(getActiveArticleId());
} else {
alert(__("Please enable embed_original plugin first."));
}
};
hotkey_actions["toggle_widescreen"] = function() {
if (!isCdmMode()) {
_widescreen_mode = !_widescreen_mode;
// reset stored sizes because geometry changed // reset stored sizes because geometry changed
setCookie("ttrss_ci_width", 0); setCookie("ttrss_ci_width", 0);
setCookie("ttrss_ci_height", 0); setCookie("ttrss_ci_height", 0);
switchPanelMode(_widescreen_mode); switchPanelMode(_widescreen_mode);
} else { } else {
alert(__("Widescreen is not available in combined mode.")); alert(__("Widescreen is not available in combined mode."));
} }
}; };
hotkey_actions["help_dialog"] = function() { hotkey_actions["help_dialog"] = function() {
helpDialog("main"); helpDialog("main");
}; };
hotkey_actions["toggle_combined_mode"] = function() { hotkey_actions["toggle_combined_mode"] = function() {
notify_progress("Loading, please wait..."); notify_progress("Loading, please wait...");
var value = isCdmMode() ? "false" : "true"; var value = isCdmMode() ? "false" : "true";
var query = "?op=rpc&method=setpref&key=COMBINED_DISPLAY_MODE&value=" + value; var query = "?op=rpc&method=setpref&key=COMBINED_DISPLAY_MODE&value=" + value;
new Ajax.Request("backend.php", { new Ajax.Request("backend.php", {
parameters: query, parameters: query,
onComplete: function(transport) { onComplete: function(transport) {
setInitParam("combined_display_mode", setInitParam("combined_display_mode",
!getInitParam("combined_display_mode")); !getInitParam("combined_display_mode"));
closeArticlePanel(); closeArticlePanel();
viewCurrentFeed(); viewCurrentFeed();
} }); } });
}; };
hotkey_actions["toggle_cdm_expanded"] = function() { hotkey_actions["toggle_cdm_expanded"] = function() {
notify_progress("Loading, please wait..."); notify_progress("Loading, please wait...");
var value = getInitParam("cdm_expanded") ? "false" : "true";
var query = "?op=rpc&method=setpref&key=CDM_EXPANDED&value=" + value;
new Ajax.Request("backend.php", {
parameters: query,
onComplete: function(transport) {
setInitParam("cdm_expanded", !getInitParam("cdm_expanded"));
viewCurrentFeed();
} });
};
var value = getInitParam("cdm_expanded") ? "false" : "true";
var query = "?op=rpc&method=setpref&key=CDM_EXPANDED&value=" + value;
} catch (e) { new Ajax.Request("backend.php", {
exception_error("init", e); parameters: query,
} onComplete: function(transport) {
setInitParam("cdm_expanded", !getInitParam("cdm_expanded"));
viewCurrentFeed();
} });
};
} }
function init_second_stage() { function init_second_stage() {
try { try {
dojo.addOnLoad(function() { updateFeedList();
updateFeedList(); closeArticlePanel();
closeArticlePanel();
if (parseInt(getCookie("ttrss_fh_width")) > 0) {
dijit.byId("feeds-holder").domNode.setStyle(
{width: getCookie("ttrss_fh_width") + "px" });
}
dijit.byId("main").resize(); if (parseInt(getCookie("ttrss_fh_width")) > 0) {
dijit.byId("feeds-holder").domNode.setStyle(
{width: getCookie("ttrss_fh_width") + "px" });
}
var tmph = dojo.connect(dijit.byId('feeds-holder'), 'resize', dijit.byId("main").resize();
function (args) {
if (args && args.w >= 0) {
setCookie("ttrss_fh_width", args.w, getInitParam("cookie_lifetime"));
}
});
var tmph = dojo.connect(dijit.byId('content-insert'), 'resize', var tmph = dojo.connect(dijit.byId('feeds-holder'), 'resize',
function (args) { function (args) {
if (args && args.w >= 0 && args.h >= 0) { if (args && args.w >= 0) {
setCookie("ttrss_ci_width", args.w, getInitParam("cookie_lifetime")); setCookie("ttrss_fh_width", args.w, getInitParam("cookie_lifetime"));
setCookie("ttrss_ci_height", args.h, getInitParam("cookie_lifetime")); }
} });
});
var tmph = dojo.connect(dijit.byId('content-insert'), 'resize',
function (args) {
if (args && args.w >= 0 && args.h >= 0) {
setCookie("ttrss_ci_width", args.w, getInitParam("cookie_lifetime"));
setCookie("ttrss_ci_height", args.h, getInitParam("cookie_lifetime"));
}
}); });
delCookie("ttrss_test"); delCookie("ttrss_test");

@ -1,474 +1,486 @@
dojo.provide("lib.CheckBoxTree"); //dojo.provide("lib.CheckBoxTree");
dojo.provide("lib.CheckBoxStoreModel"); //dojo.provide("lib.CheckBoxStoreModel");
// THIS WIDGET IS BASED ON DOJO/DIJIT 1.4.0 AND WILL NOT WORK WITH PREVIOUS VERSIONS // THIS WIDGET IS BASED ON DOJO/DIJIT 1.4.0 AND WILL NOT WORK WITH PREVIOUS VERSIONS
// //
// Release date: 02/05/2010 // Release date: 02/05/2010
// //
dojo.require("dijit.Tree"); //dojo.require("dijit.Tree");
dojo.require("dijit.form.CheckBox"); //dojo.require("dijit.form.CheckBox");
dojo.declare( "lib.CheckBoxStoreModel", dijit.tree.TreeStoreModel, require(["dojo/_base/declare", "dijit/tree/TreeStoreModel"], function (declare) {
{
// checkboxAll: Boolean return declare( "lib.CheckBoxStoreModel", dijit.tree.TreeStoreModel,
// If true, every node in the tree will receive a checkbox regardless if the 'checkbox' attribute {
// is specified in the dojo.data. // checkboxAll: Boolean
checkboxAll: true, // If true, every node in the tree will receive a checkbox regardless if the 'checkbox' attribute
// is specified in the dojo.data.
// checkboxState: Boolean checkboxAll: true,
// The default state applied to every checkbox unless otherwise specified in the dojo.data.
// (see also: checkboxIdent) // checkboxState: Boolean
checkboxState: false, // The default state applied to every checkbox unless otherwise specified in the dojo.data.
// (see also: checkboxIdent)
// checkboxRoot: Boolean checkboxState: false,
// If true, the root node will receive a checkbox eventhough it's not a true entry in the store.
// This attribute is independent of the showRoot attribute of the tree itself. If the tree // checkboxRoot: Boolean
// attribute 'showRoot' is set to false to checkbox for the root will not show either. // If true, the root node will receive a checkbox eventhough it's not a true entry in the store.
checkboxRoot: false, // This attribute is independent of the showRoot attribute of the tree itself. If the tree
// attribute 'showRoot' is set to false to checkbox for the root will not show either.
// checkboxStrict: Boolean checkboxRoot: false,
// If true, a strict parent-child checkbox relation is maintained. For example, if all children
// are checked the parent will automatically be checked or if any of the children are unchecked // checkboxStrict: Boolean
// the parent will be unchecked. // If true, a strict parent-child checkbox relation is maintained. For example, if all children
checkboxStrict: true, // are checked the parent will automatically be checked or if any of the children are unchecked
// the parent will be unchecked.
// checkboxIdent: String checkboxStrict: true,
// The attribute name (attribute of the dojo.data.item) that specifies that items checkbox initial
// state. Example: { name:'Egypt', type:'country', checkbox: true } // checkboxIdent: String
// If a dojo.data.item has no 'checkbox' attribute specified it will depend on the attribute // The attribute name (attribute of the dojo.data.item) that specifies that items checkbox initial
// 'checkboxAll' if one will be created automatically and if so what the initial state will be as // state. Example: { name:'Egypt', type:'country', checkbox: true }
// specified by 'checkboxState'. // If a dojo.data.item has no 'checkbox' attribute specified it will depend on the attribute
checkboxIdent: "checkbox", // 'checkboxAll' if one will be created automatically and if so what the initial state will be as
// specified by 'checkboxState'.
updateCheckbox: function(/*dojo.data.Item*/ storeItem, /*Boolean*/ newState ) { checkboxIdent: "checkbox",
// summary:
// Update the checkbox state (true/false) for the item and the associated parent and updateCheckbox: function(/*dojo.data.Item*/ storeItem, /*Boolean*/ newState ) {
// child checkboxes if any. // summary:
// description: // Update the checkbox state (true/false) for the item and the associated parent and
// Update a single checkbox state (true/false) for the item and the associated parent // child checkboxes if any.
// and child checkboxes if any. This function is called from the tree if a user checked // description:
// or unchecked a checkbox on the tree. The parent and child tree nodes are updated to // Update a single checkbox state (true/false) for the item and the associated parent
// maintain consistency if 'checkboxStrict' is set to true. // and child checkboxes if any. This function is called from the tree if a user checked
// storeItem: // or unchecked a checkbox on the tree. The parent and child tree nodes are updated to
// The item in the dojo.data.store whos checkbox state needs updating. // maintain consistency if 'checkboxStrict' is set to true.
// newState: // storeItem:
// The new state of the checkbox: true or false // The item in the dojo.data.store whos checkbox state needs updating.
// example: // newState:
// | model.updateCheckboxState(item, true); // The new state of the checkbox: true or false
// // example:
// | model.updateCheckboxState(item, true);
this._setCheckboxState( storeItem, newState ); //
//if( this.checkboxStrict ) { I don't need all this 1-1 stuff, only parent -> child (fox)
this._updateChildCheckbox( storeItem, newState ); this._setCheckboxState( storeItem, newState );
//this._updateParentCheckbox( storeItem, newState ); //if( this.checkboxStrict ) { I don't need all this 1-1 stuff, only parent -> child (fox)
//} this._updateChildCheckbox( storeItem, newState );
}, //this._updateParentCheckbox( storeItem, newState );
setAllChecked: function(checked) { //}
var items = this.store._arrayOfAllItems; },
this.setCheckboxState(items, checked); setAllChecked: function(checked) {
}, var items = this.store._arrayOfAllItems;
setCheckboxState: function(items, checked) { this.setCheckboxState(items, checked);
for (var i = 0; i < items.length; i++) { },
this._setCheckboxState(items[i], checked); setCheckboxState: function(items, checked) {
} for (var i = 0; i < items.length; i++) {
}, this._setCheckboxState(items[i], checked);
getCheckedItems: function() {
var items = this.store._arrayOfAllItems;
var result = [];
for (var i = 0; i < items.length; i++) {
if (this.store.getValue(items[i], 'checkbox'))
result.push(items[i]);
}
return result;
},
getCheckboxState: function(/*dojo.data.Item*/ storeItem) {
// summary:
// Get the current checkbox state from the dojo.data.store.
// description:
// Get the current checkbox state from the dojo.data store. A checkbox can have three
// different states: true, false or undefined. Undefined in this context means no
// checkbox identifier (checkboxIdent) was found in the dojo.data store. Depending on
// the checkbox attributes as specified above the following will take place:
// a) If the current checkbox state is undefined and the checkbox attribute 'checkboxAll' or
// 'checkboxRoot' is true one will be created and the default state 'checkboxState' will
// be applied.
// b) If the current state is undefined and 'checkboxAll' is false the state undefined remains
// unchanged and is returned. This will prevent any tree node from creating a checkbox.
//
// storeItem:
// The item in the dojo.data.store whos checkbox state is returned.
// example:
// | var currState = model.getCheckboxState(item);
//
var currState = undefined;
// Special handling required for the 'fake' root entry (the root is NOT a dojo.data.item).
// this stuff is only relevant for Forest store -fox
/* if ( storeItem == this.root ) {
if( typeof(storeItem.checkbox) == "undefined" ) {
this.root.checkbox = undefined; // create a new checbox reference as undefined.
if( this.checkboxRoot ) {
currState = this.root.checkbox = this.checkboxState;
} }
} else { },
currState = this.root.checkbox; getCheckedItems: function() {
} var items = this.store._arrayOfAllItems;
} else { // a valid dojo.store.item var result = [];
currState = this.store.getValue(storeItem, this.checkboxIdent);
if( currState == undefined && this.checkboxAll) { for (var i = 0; i < items.length; i++) {
this._setCheckboxState( storeItem, this.checkboxState ); if (this.store.getValue(items[i], 'checkbox'))
currState = this.checkboxState; result.push(items[i]);
} }
} */
return result;
currState = this.store.getValue(storeItem, this.checkboxIdent); },
if( currState == undefined && this.checkboxAll) {
this._setCheckboxState( storeItem, this.checkboxState ); getCheckboxState: function(/*dojo.data.Item*/ storeItem) {
currState = this.checkboxState; // summary:
} // Get the current checkbox state from the dojo.data.store.
// description:
return currState; // the current state of the checkbox (true/false or undefined) // Get the current checkbox state from the dojo.data store. A checkbox can have three
}, // different states: true, false or undefined. Undefined in this context means no
// checkbox identifier (checkboxIdent) was found in the dojo.data store. Depending on
_setCheckboxState: function(/*dojo.data.Item*/ storeItem, /*Boolean*/ newState ) { // the checkbox attributes as specified above the following will take place:
// summary: // a) If the current checkbox state is undefined and the checkbox attribute 'checkboxAll' or
// Set/update the checkbox state on the dojo.data store. // 'checkboxRoot' is true one will be created and the default state 'checkboxState' will
// description: // be applied.
// Set/update the checkbox state on the dojo.data.store. Retreive the current // b) If the current state is undefined and 'checkboxAll' is false the state undefined remains
// state of the checkbox and validate if an update is required, this will keep // unchanged and is returned. This will prevent any tree node from creating a checkbox.
// update events to a minimum. On completion a 'onCheckboxChange' event is //
// triggered. // storeItem:
// If the current state is undefined (ie: no checkbox attribute specified for // The item in the dojo.data.store whos checkbox state is returned.
// this dojo.data.item) the 'checkboxAll' attribute is checked to see if one // example:
// needs to be created. In case of the root the 'checkboxRoot' attribute is checked. // | var currState = model.getCheckboxState(item);
// NOTE: the store.setValue function will create the 'checkbox' attribute for the //
// item if none exists. var currState = undefined;
// storeItem:
// The item in the dojo.data.store whos checkbox state is updated. // Special handling required for the 'fake' root entry (the root is NOT a dojo.data.item).
// newState: // this stuff is only relevant for Forest store -fox
// The new state of the checkbox: true or false /* if ( storeItem == this.root ) {
// example: if( typeof(storeItem.checkbox) == "undefined" ) {
// | model.setCheckboxState(item, true); this.root.checkbox = undefined; // create a new checbox reference as undefined.
// if( this.checkboxRoot ) {
var stateChanged = true; currState = this.root.checkbox = this.checkboxState;
}
if( storeItem != this.root ) { } else {
var currState = this.store.getValue(storeItem, this.checkboxIdent); currState = this.root.checkbox;
if( currState != newState && ( currState !== undefined || this.checkboxAll ) ) { }
this.store.setValue(storeItem, this.checkboxIdent, newState); } else { // a valid dojo.store.item
} else { currState = this.store.getValue(storeItem, this.checkboxIdent);
stateChanged = false; // No changes to the checkbox if( currState == undefined && this.checkboxAll) {
} this._setCheckboxState( storeItem, this.checkboxState );
} else { // Tree root instance currState = this.checkboxState;
if( this.root.checkbox != newState && ( this.root.checkbox !== undefined || this.checkboxRoot ) ) { }
this.root.checkbox = newState; } */
} else {
stateChanged = false; currState = this.store.getValue(storeItem, this.checkboxIdent);
} if( currState == undefined && this.checkboxAll) {
} this._setCheckboxState( storeItem, this.checkboxState );
if( stateChanged ) { // In case of any changes trigger the update event. currState = this.checkboxState;
this.onCheckboxChange(storeItem); }
}
return stateChanged; return currState; // the current state of the checkbox (true/false or undefined)
}, },
_updateChildCheckbox: function(/*dojo.data.Item*/ parentItem, /*Boolean*/ newState ) { _setCheckboxState: function(/*dojo.data.Item*/ storeItem, /*Boolean*/ newState ) {
// summary: // summary:
// Set all child checkboxes to true/false depending on the parent checkbox state. // Set/update the checkbox state on the dojo.data store.
// description: // description:
// If a parent checkbox changes state, all child and grandchild checkboxes will be // Set/update the checkbox state on the dojo.data.store. Retreive the current
// updated to reflect the change. For example, if the parent state is set to true, // state of the checkbox and validate if an update is required, this will keep
// all child and grandchild checkboxes will receive that same 'true' state. // update events to a minimum. On completion a 'onCheckboxChange' event is
// If a child checkbox changes state and has multiple parent, all of its parents // triggered.
// need to be re-evaluated. // If the current state is undefined (ie: no checkbox attribute specified for
// parentItem: // this dojo.data.item) the 'checkboxAll' attribute is checked to see if one
// The parent dojo.data.item whos child/grandchild checkboxes require updating. // needs to be created. In case of the root the 'checkboxRoot' attribute is checked.
// newState: // NOTE: the store.setValue function will create the 'checkbox' attribute for the
// The new state of the checkbox: true or false // item if none exists.
// // storeItem:
// The item in the dojo.data.store whos checkbox state is updated.
if( this.mayHaveChildren( parentItem )) { // newState:
this.getChildren( parentItem, dojo.hitch( this, // The new state of the checkbox: true or false
function( children ) { // example:
dojo.forEach( children, function(child) { // | model.setCheckboxState(item, true);
if( this._setCheckboxState(child, newState) ) { //
var parents = this._getParentsItem(child); var stateChanged = true;
if( parents.length > 1 ) {
this._updateParentCheckbox( child, newState ); if( storeItem != this.root ) {
} var currState = this.store.getValue(storeItem, this.checkboxIdent);
if( currState != newState && ( currState !== undefined || this.checkboxAll ) ) {
this.store.setValue(storeItem, this.checkboxIdent, newState);
} else {
stateChanged = false; // No changes to the checkbox
}
} else { // Tree root instance
if( this.root.checkbox != newState && ( this.root.checkbox !== undefined || this.checkboxRoot ) ) {
this.root.checkbox = newState;
} else {
stateChanged = false;
}
}
if( stateChanged ) { // In case of any changes trigger the update event.
this.onCheckboxChange(storeItem);
}
return stateChanged;
},
_updateChildCheckbox: function(/*dojo.data.Item*/ parentItem, /*Boolean*/ newState ) {
// summary:
// Set all child checkboxes to true/false depending on the parent checkbox state.
// description:
// If a parent checkbox changes state, all child and grandchild checkboxes will be
// updated to reflect the change. For example, if the parent state is set to true,
// all child and grandchild checkboxes will receive that same 'true' state.
// If a child checkbox changes state and has multiple parent, all of its parents
// need to be re-evaluated.
// parentItem:
// The parent dojo.data.item whos child/grandchild checkboxes require updating.
// newState:
// The new state of the checkbox: true or false
//
if( this.mayHaveChildren( parentItem )) {
this.getChildren( parentItem, dojo.hitch( this,
function( children ) {
dojo.forEach( children, function(child) {
if( this._setCheckboxState(child, newState) ) {
var parents = this._getParentsItem(child);
if( parents.length > 1 ) {
this._updateParentCheckbox( child, newState );
}
}
if( this.mayHaveChildren( child )) {
this._updateChildCheckbox( child, newState );
}
}, this );
}),
function(err) {
console.error(this, ": updating child checkboxes: ", err);
} }
if( this.mayHaveChildren( child )) { );
this._updateChildCheckbox( child, newState ); }
},
_updateParentCheckbox: function(/*dojo.data.Item*/ storeItem, /*Boolean*/ newState ) {
// summary:
// Update the parent checkbox state depending on the state of all child checkboxes.
// description:
// Update the parent checkbox state depending on the state of all child checkboxes.
// The parent checkbox automatically changes state if ALL child checkboxes are true
// or false. If, as a result, the parent checkbox changes state, we will check if
// its parent needs to be updated as well all the way upto the root.
// storeItem:
// The dojo.data.item whos parent checkboxes require updating.
// newState:
// The new state of the checkbox: true or false
//
var parents = this._getParentsItem(storeItem);
dojo.forEach( parents, function( parentItem ) {
if( newState ) { // new state = true (checked)
this.getChildren( parentItem, dojo.hitch( this,
function(siblings) {
var allChecked = true;
dojo.some( siblings, function(sibling) {
siblState = this.getCheckboxState(sibling);
if( siblState !== undefined && allChecked )
allChecked = siblState;
return !(allChecked);
}, this );
if( allChecked ) {
this._setCheckboxState( parentItem, true );
this._updateParentCheckbox( parentItem, true );
}
}),
function(err) {
console.error(this, ": updating parent checkboxes: ", err);
}
);
} else { // new state = false (unchecked)
if( this._setCheckboxState( parentItem, false ) ) {
this._updateParentCheckbox( parentItem, false );
} }
}, this ); }
}), }, this );
function(err) { },
console.error(this, ": updating child checkboxes: ", err);
_getParentsItem: function(/*dojo.data.Item*/ storeItem ) {
// summary:
// Get the parent(s) of a dojo.data item.
// description:
// Get the parent(s) of a dojo.data item. The '_reverseRefMap' entry of the item is
// used to identify the parent(s). A child will have a parent reference if the parent
// specified the '_reference' attribute.
// For example: children:[{_reference:'Mexico'}, {_reference:'Canada'}, ...
// storeItem:
// The dojo.data.item whos parent(s) will be returned.
//
var parents = [];
if( storeItem != this.root ) {
var references = storeItem[this.store._reverseRefMap];
for(itemId in references ) {
parents.push(this.store._itemsByIdentity[itemId]);
}
if (!parents.length) {
parents.push(this.root);
}
} }
); return parents; // parent(s) of a dojo.data.item (Array of dojo.data.items)
} },
},
validateData: function(/*dojo.data.Item*/ storeItem, /*thisObject*/ scope ) {
_updateParentCheckbox: function(/*dojo.data.Item*/ storeItem, /*Boolean*/ newState ) { // summary:
// summary: // Validate/normalize the parent(s) checkbox data in the dojo.data store.
// Update the parent checkbox state depending on the state of all child checkboxes. // description:
// description: // Validate/normalize the parent-child checkbox relationship if the attribute
// Update the parent checkbox state depending on the state of all child checkboxes. // 'checkboxStrict' is set to true. This function is called as part of the post
// The parent checkbox automatically changes state if ALL child checkboxes are true // creation of the Tree instance. All parent checkboxes are set to the appropriate
// or false. If, as a result, the parent checkbox changes state, we will check if // state according to the actual state(s) of their children.
// its parent needs to be updated as well all the way upto the root. // This will potentionally overwrite whatever was specified for the parent in the
// storeItem: // dojo.data store. This will garantee the tree is in a consistent state after startup.
// The dojo.data.item whos parent checkboxes require updating. // storeItem:
// newState: // The element to start traversing the dojo.data.store, typically model.root
// The new state of the checkbox: true or false // scope:
// // The scope to use when this method executes.
var parents = this._getParentsItem(storeItem); // example:
dojo.forEach( parents, function( parentItem ) { // | this.model.validateData(this.model.root, this.model);
if( newState ) { // new state = true (checked) //
this.getChildren( parentItem, dojo.hitch( this, if( !scope.checkboxStrict ) {
function(siblings) { return;
}
scope.getChildren( storeItem, dojo.hitch( scope,
function(children) {
var allChecked = true; var allChecked = true;
dojo.some( siblings, function(sibling) { var childState;
siblState = this.getCheckboxState(sibling); dojo.forEach( children, function( child ) {
if( siblState !== undefined && allChecked ) if( this.mayHaveChildren( child )) {
allChecked = siblState; this.validateData( child, this );
return !(allChecked); }
}, this ); childState = this.getCheckboxState( child );
if( allChecked ) { if( childState !== undefined && allChecked )
this._setCheckboxState( parentItem, true ); allChecked = childState;
this._updateParentCheckbox( parentItem, true ); }, this);
if ( this._setCheckboxState( storeItem, allChecked) ) {
this._updateParentCheckbox( storeItem, allChecked);
} }
}), }),
function(err) { function(err) {
console.error(this, ": updating parent checkboxes: ", err); console.error(this, ": validating checkbox data: ", err);
} }
); );
} else { // new state = false (unchecked) },
if( this._setCheckboxState( parentItem, false ) ) {
this._updateParentCheckbox( parentItem, false ); onCheckboxChange: function(/*dojo.data.Item*/ storeItem ) {
} // summary:
} // Callback whenever a checkbox state has changed state, so that
}, this ); // the Tree can update the checkbox. This callback is generally
}, // triggered by the '_setCheckboxState' function.
// tags:
_getParentsItem: function(/*dojo.data.Item*/ storeItem ) { // callback
// summary:
// Get the parent(s) of a dojo.data item.
// description:
// Get the parent(s) of a dojo.data item. The '_reverseRefMap' entry of the item is
// used to identify the parent(s). A child will have a parent reference if the parent
// specified the '_reference' attribute.
// For example: children:[{_reference:'Mexico'}, {_reference:'Canada'}, ...
// storeItem:
// The dojo.data.item whos parent(s) will be returned.
//
var parents = [];
if( storeItem != this.root ) {
var references = storeItem[this.store._reverseRefMap];
for(itemId in references ) {
parents.push(this.store._itemsByIdentity[itemId]);
} }
if (!parents.length) {
parents.push(this.root);
}
}
return parents; // parent(s) of a dojo.data.item (Array of dojo.data.items)
},
validateData: function(/*dojo.data.Item*/ storeItem, /*thisObject*/ scope ) {
// summary:
// Validate/normalize the parent(s) checkbox data in the dojo.data store.
// description:
// Validate/normalize the parent-child checkbox relationship if the attribute
// 'checkboxStrict' is set to true. This function is called as part of the post
// creation of the Tree instance. All parent checkboxes are set to the appropriate
// state according to the actual state(s) of their children.
// This will potentionally overwrite whatever was specified for the parent in the
// dojo.data store. This will garantee the tree is in a consistent state after startup.
// storeItem:
// The element to start traversing the dojo.data.store, typically model.root
// scope:
// The scope to use when this method executes.
// example:
// | this.model.validateData(this.model.root, this.model);
//
if( !scope.checkboxStrict ) {
return;
}
scope.getChildren( storeItem, dojo.hitch( scope,
function(children) {
var allChecked = true;
var childState;
dojo.forEach( children, function( child ) {
if( this.mayHaveChildren( child )) {
this.validateData( child, this );
}
childState = this.getCheckboxState( child );
if( childState !== undefined && allChecked )
allChecked = childState;
}, this);
if ( this._setCheckboxState( storeItem, allChecked) ) { });
this._updateParentCheckbox( storeItem, allChecked);
});
require(["dojo/_base/declare", "dijit/Tree"], function (declare) {
return declare("lib._CheckBoxTreeNode", dijit._TreeNode,
{
// _checkbox: [protected] dojo.doc.element
// Local reference to the dojo.doc.element of type 'checkbox'
_checkbox: null,
_createCheckbox: function () {
// summary:
// Create a checkbox on the CheckBoxTreeNode
// description:
// Create a checkbox on the CheckBoxTreeNode. The checkbox is ONLY created if a
// valid reference was found in the dojo.data store or the attribute 'checkboxAll'
// is set to true. If the current state is 'undefined' no reference was found and
// 'checkboxAll' is set to false.
// Note: the attribute 'checkboxAll' is validated by the getCheckboxState function
// therefore no need to do that here. (see getCheckboxState for details).
//
var currState = this.tree.model.getCheckboxState(this.item);
if (currState !== undefined) {
this._checkbox = new dijit.form.CheckBox();
//this._checkbox = dojo.doc.createElement('input');
this._checkbox.type = 'checkbox';
this._checkbox.attr('checked', currState);
dojo.place(this._checkbox.domNode, this.expandoNode, 'after');
} }
}), },
function(err) {
console.error(this, ": validating checkbox data: ", err); postCreate: function () {
// summary:
// Handle the creation of the checkbox after the CheckBoxTreeNode has been instanciated.
// description:
// Handle the creation of the checkbox after the CheckBoxTreeNode has been instanciated.
this._createCheckbox();
this.inherited(arguments);
} }
);
},
onCheckboxChange: function(/*dojo.data.Item*/ storeItem ) {
// summary:
// Callback whenever a checkbox state has changed state, so that
// the Tree can update the checkbox. This callback is generally
// triggered by the '_setCheckboxState' function.
// tags:
// callback
}
});
}); });
dojo.declare( "lib._CheckBoxTreeNode", dijit._TreeNode, require(["dojo/_base/declare", "dijit/Tree"], function (declare) {
{
// _checkbox: [protected] dojo.doc.element return declare( "lib.CheckBoxTree", dijit.Tree,
// Local reference to the dojo.doc.element of type 'checkbox' {
_checkbox: null,
onNodeChecked: function(/*dojo.data.Item*/ storeItem, /*treeNode*/ treeNode) {
_createCheckbox: function() { // summary:
// summary: // Callback when a checkbox tree node is checked
// Create a checkbox on the CheckBoxTreeNode // tags:
// description: // callback
// Create a checkbox on the CheckBoxTreeNode. The checkbox is ONLY created if a },
// valid reference was found in the dojo.data store or the attribute 'checkboxAll'
// is set to true. If the current state is 'undefined' no reference was found and onNodeUnchecked: function(/*dojo.data.Item*/ storeItem, /* treeNode */ treeNode) {
// 'checkboxAll' is set to false. // summary:
// Note: the attribute 'checkboxAll' is validated by the getCheckboxState function // Callback when a checkbox tree node is unchecked
// therefore no need to do that here. (see getCheckboxState for details). // tags:
// // callback
var currState = this.tree.model.getCheckboxState( this.item ); },
if( currState !== undefined ) {
this._checkbox = new dijit.form.CheckBox(); _onClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e) {
//this._checkbox = dojo.doc.createElement('input'); // summary:
this._checkbox.type = 'checkbox'; // Translates click events into commands for the controller to process
this._checkbox.attr('checked', currState); // description:
dojo.place(this._checkbox.domNode, this.expandoNode, 'after'); // the _onClick function is called whenever a 'click' is detected. This
} // instance of _onClick only handles the click events associated with
}, // the checkbox whos DOM name is INPUT.
//
postCreate: function() { var domElement = e.target;
// summary:
// Handle the creation of the checkbox after the CheckBoxTreeNode has been instanciated. // Only handle checkbox clicks here
// description: if(domElement.type != 'checkbox') {
// Handle the creation of the checkbox after the CheckBoxTreeNode has been instanciated. return this.inherited( arguments );
this._createCheckbox(); }
this.inherited( arguments );
}
}); this._publish("execute", { item: nodeWidget.item, node: nodeWidget} );
// Go tell the model to update the checkbox state
dojo.declare( "lib.CheckBoxTree", dijit.Tree, this.model.updateCheckbox( nodeWidget.item, nodeWidget._checkbox.checked );
{ // Generate some additional events
//this.onClick( nodeWidget.item, nodeWidget, e );
onNodeChecked: function(/*dojo.data.Item*/ storeItem, /*treeNode*/ treeNode) { if(nodeWidget._checkbox.checked) {
// summary: this.onNodeChecked( nodeWidget.item, nodeWidget);
// Callback when a checkbox tree node is checked } else {
// tags: this.onNodeUnchecked( nodeWidget.item, nodeWidget);
// callback }
}, this.focusNode(nodeWidget);
},
onNodeUnchecked: function(/*dojo.data.Item*/ storeItem, /* treeNode */ treeNode) {
// summary: _onCheckboxChange: function(/*dojo.data.Item*/ storeItem ) {
// Callback when a checkbox tree node is unchecked // summary:
// tags: // Processes notification of a change to a checkbox state (triggered by the model).
// callback // description:
}, // Whenever the model changes the state of a checkbox in the dojo.data.store it will
// trigger the 'onCheckboxChange' event allowing the Tree to make the same changes
_onClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e) { // on the tree Node. There are several conditions why a tree node or checkbox does not
// summary: // exists:
// Translates click events into commands for the controller to process // a) The node has not been created yet (the user has not expanded the tree node yet).
// description: // b) The checkbox may be null if condition (a) exists or no 'checkbox' attribute was
// the _onClick function is called whenever a 'click' is detected. This // specified for the associated dojo.data.item and the attribute 'checkboxAll' is
// instance of _onClick only handles the click events associated with // set to false.
// the checkbox whos DOM name is INPUT. // tags:
// // callback
var domElement = e.target; var model = this.model,
identity = model.getIdentity(storeItem),
// Only handle checkbox clicks here nodes = this._itemNodesMap[identity];
if(domElement.type != 'checkbox') {
return this.inherited( arguments ); // As of dijit.Tree 1.4 multiple references (parents) are supported, therefore we may have
} // to update multiple nodes which are all associated with the same dojo.data.item.
if( nodes ) {
this._publish("execute", { item: nodeWidget.item, node: nodeWidget} ); dojo.forEach( nodes, function(node) {
// Go tell the model to update the checkbox state if( node._checkbox != null ) {
node._checkbox.attr('checked', this.model.getCheckboxState( storeItem ));
this.model.updateCheckbox( nodeWidget.item, nodeWidget._checkbox.checked ); }
// Generate some additional events }, this );
//this.onClick( nodeWidget.item, nodeWidget, e );
if(nodeWidget._checkbox.checked) {
this.onNodeChecked( nodeWidget.item, nodeWidget);
} else {
this.onNodeUnchecked( nodeWidget.item, nodeWidget);
}
this.focusNode(nodeWidget);
},
_onCheckboxChange: function(/*dojo.data.Item*/ storeItem ) {
// summary:
// Processes notification of a change to a checkbox state (triggered by the model).
// description:
// Whenever the model changes the state of a checkbox in the dojo.data.store it will
// trigger the 'onCheckboxChange' event allowing the Tree to make the same changes
// on the tree Node. There are several conditions why a tree node or checkbox does not
// exists:
// a) The node has not been created yet (the user has not expanded the tree node yet).
// b) The checkbox may be null if condition (a) exists or no 'checkbox' attribute was
// specified for the associated dojo.data.item and the attribute 'checkboxAll' is
// set to false.
// tags:
// callback
var model = this.model,
identity = model.getIdentity(storeItem),
nodes = this._itemNodesMap[identity];
// As of dijit.Tree 1.4 multiple references (parents) are supported, therefore we may have
// to update multiple nodes which are all associated with the same dojo.data.item.
if( nodes ) {
dojo.forEach( nodes, function(node) {
if( node._checkbox != null ) {
node._checkbox.attr('checked', this.model.getCheckboxState( storeItem ));
} }
}, this ); },
}
}, postCreate: function() {
// summary:
postCreate: function() { // Handle any specifics related to the tree and model after the instanciation of the Tree.
// summary: // description:
// Handle any specifics related to the tree and model after the instanciation of the Tree. // Validate if we have a 'write' store first. Subscribe to the 'onCheckboxChange' event
// description: // (triggered by the model) and kickoff the initial checkbox data validation.
// Validate if we have a 'write' store first. Subscribe to the 'onCheckboxChange' event //
// (triggered by the model) and kickoff the initial checkbox data validation. var store = this.model.store;
// if(!store.getFeatures()['dojo.data.api.Write']){
var store = this.model.store; throw new Error("lib.CheckboxTree: store must support dojo.data.Write");
if(!store.getFeatures()['dojo.data.api.Write']){ }
throw new Error("lib.CheckboxTree: store must support dojo.data.Write"); this.connect(this.model, "onCheckboxChange", "_onCheckboxChange");
} this.model.validateData( this.model.root, this.model );
this.connect(this.model, "onCheckboxChange", "_onCheckboxChange"); this.inherited(arguments);
this.model.validateData( this.model.root, this.model ); },
this.inherited(arguments);
}, _createTreeNode: function( args ) {
// summary:
_createTreeNode: function( args ) { // Create a new CheckboxTreeNode instance.
// summary: // description:
// Create a new CheckboxTreeNode instance. // Create a new CheckboxTreeNode instance.
// description: return new lib._CheckBoxTreeNode(args);
// Create a new CheckboxTreeNode instance. }
return new lib._CheckBoxTreeNode(args);
} });
}); });

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -274,7 +274,7 @@ class Af_Psql_Trgm extends Plugin {
$result = db_query("SELECT COUNT(id) AS nequal $result = db_query("SELECT COUNT(id) AS nequal
FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id AND FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id AND
date_entered >= NOW() - interval '1 day' AND date_entered >= NOW() - interval '3 days' AND
title = '$title_escaped' AND title = '$title_escaped' AND
guid != '$entry_guid' AND guid != '$entry_guid' AND
owner_uid = $owner_uid"); owner_uid = $owner_uid");

@ -159,7 +159,7 @@ class Af_Readability extends Plugin {
$tmp = fetch_file_contents($url); $tmp = fetch_file_contents($url);
if ($tmp && mb_strlen($tmp) < 65535 * 4) { if ($tmp && mb_strlen($tmp) < 1024 * 500) {
$tmpdoc = new DOMDocument("1.0", "UTF-8"); $tmpdoc = new DOMDocument("1.0", "UTF-8");
if (!$tmpdoc->loadHTML($tmp)) if (!$tmpdoc->loadHTML($tmp))

@ -29,7 +29,7 @@ class Af_RedditImgur extends Plugin {
$enable_content_dupcheck = $this->host->get($this, "enable_content_dupcheck"); $enable_content_dupcheck = $this->host->get($this, "enable_content_dupcheck");
$enable_content_dupcheck_checked = $enable_content_dupcheck ? "checked" : ""; $enable_content_dupcheck_checked = $enable_content_dupcheck ? "checked" : "";
print "<form dojoType=\"dijit.form.Form\">"; print "<form dojoType=\"dijit.form.Form\">";
print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\"> print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\">
@ -59,7 +59,7 @@ class Af_RedditImgur extends Plugin {
print "<label for=\"enable_readability\">" . __("Extract missing content using Readability") . "</label>"; print "<label for=\"enable_readability\">" . __("Extract missing content using Readability") . "</label>";
print "<br/>"; print "<br/>";
print "<input dojoType=\"dijit.form.CheckBox\" id=\"enable_content_dupcheck\" print "<input dojoType=\"dijit.form.CheckBox\" id=\"enable_content_dupcheck\"
$enable_content_dupcheck_checked name=\"enable_content_dupcheck\">&nbsp;"; $enable_content_dupcheck_checked name=\"enable_content_dupcheck\">&nbsp;";
@ -95,11 +95,38 @@ class Af_RedditImgur extends Plugin {
$matches = array(); $matches = array();
if (preg_match("/\.gfycat.com\/([a-z]+)?(\.[a-z]+)$/i", $entry->getAttribute("href"), $matches)) { if (preg_match("/^https?:\/\/twitter.com\/(.*?)\/status\/(.*)/", $entry->getAttribute("href"), $matches)) {
_debug("handling as twitter: " . $matches[1] . " " . $matches[2], $debug);
$oembed_result = fetch_file_contents("https://publish.twitter.com/oembed?url=" . urlencode($entry->getAttribute("href")));
if ($oembed_result) {
$oembed_result = json_decode($oembed_result, true);
if ($oembed_result && isset($oembed_result["html"])) {
$tmp = new DOMDocument();
if ($tmp->loadHTML('<?xml encoding="utf-8" ?>' . $oembed_result["html"])) {
$p = $doc->createElement("p");
$p->appendChild($doc->importNode(
$tmp->getElementsByTagName("blockquote")->item(0), TRUE));
$br = $doc->createElement('br');
$entry->parentNode->insertBefore($p, $entry);
$entry->parentNode->insertBefore($br, $entry);
$found = 1;
}
}
}
}
if (!$found && preg_match("/\.gfycat.com\/([a-z]+)?(\.[a-z]+)$/i", $entry->getAttribute("href"), $matches)) {
$entry->setAttribute("href", "http://www.gfycat.com/".$matches[1]); $entry->setAttribute("href", "http://www.gfycat.com/".$matches[1]);
} }
if (preg_match("/https?:\/\/(www\.)?gfycat.com\/([a-z]+)$/i", $entry->getAttribute("href"), $matches)) { if (!$found && preg_match("/https?:\/\/(www\.)?gfycat.com\/([a-z]+)$/i", $entry->getAttribute("href"), $matches)) {
_debug("Handling as Gfycat", $debug); _debug("Handling as Gfycat", $debug);
@ -111,41 +138,34 @@ class Af_RedditImgur extends Plugin {
if (@$tmpdoc->loadHTML($tmp)) { if (@$tmpdoc->loadHTML($tmp)) {
$tmpxpath = new DOMXPath($tmpdoc); $tmpxpath = new DOMXPath($tmpdoc);
$source_meta = $tmpxpath->query("//meta[@name='twitter:player:stream' and contains(@content, '.mp4')]")->item(0); $source_node = $tmpxpath->query("//video[contains(@class,'share-video')]//source[contains(@src, '.mp4')]")->item(0);
$poster_meta = $tmpxpath->query("//meta[@property='og:image' and contains(@content,'thumbs.gfycat.com')]")->item(0); $poster_node = $tmpxpath->query("//video[contains(@class,'share-video') and @poster]")->item(0);
if ($source_meta) { if ($source_node && $poster_node) {
$source_stream = $source_meta->getAttribute("content"); $source_stream = $source_node->getAttribute("src");
$poster_url = false; $poster_url = $poster_node->getAttribute("poster");
if ($source_stream) { $this->handle_as_video($doc, $entry, $source_stream, $poster_url);
$found = 1;
if ($poster_meta)
$poster_url = $poster_meta->getAttribute("content");
$this->handle_as_video($doc, $entry, $source_stream, $poster_url);
$found = 1;
}
} }
} }
} }
} }
// imgur .gif -> .gifv // imgur .gif -> .gifv
if (preg_match("/i\.imgur\.com\/(.*?)\.gif$/i", $entry->getAttribute("href"))) { if (!$found && preg_match("/i\.imgur\.com\/(.*?)\.gif$/i", $entry->getAttribute("href"))) {
_debug("Handling as imgur gif (->gifv)", $debug); _debug("Handling as imgur gif (->gifv)", $debug);
$entry->setAttribute("href", $entry->setAttribute("href",
str_replace(".gif", ".gifv", $entry->getAttribute("href"))); str_replace(".gif", ".gifv", $entry->getAttribute("href")));
} }
if (preg_match("/\.(gifv)$/i", $entry->getAttribute("href"))) { if (!$found && preg_match("/\.(gifv|mp4)$/i", $entry->getAttribute("href"))) {
_debug("Handling as imgur gifv", $debug); _debug("Handling as imgur gifv", $debug);
$source_stream = str_replace(".gifv", ".mp4", $entry->getAttribute("href")); $source_stream = str_replace(".gifv", ".mp4", $entry->getAttribute("href"));
if (strpos($source_stream, "i.imgur.com") !== FALSE) if (strpos($source_stream, "imgur.com") !== FALSE)
$poster_url = str_replace(".mp4", "h.jpg", $source_stream); $poster_url = str_replace(".mp4", "h.jpg", $source_stream);
$this->handle_as_video($doc, $entry, $source_stream, $poster_url, $debug); $this->handle_as_video($doc, $entry, $source_stream, $poster_url, $debug);
@ -154,7 +174,7 @@ class Af_RedditImgur extends Plugin {
} }
$matches = array(); $matches = array();
if (preg_match("/youtube\.com\/v\/([\w-]+)/", $entry->getAttribute("href"), $matches) || if (!$found && preg_match("/youtube\.com\/v\/([\w-]+)/", $entry->getAttribute("href"), $matches) ||
preg_match("/youtube\.com\/.*?[\&\?]v=([\w-]+)/", $entry->getAttribute("href"), $matches) || preg_match("/youtube\.com\/.*?[\&\?]v=([\w-]+)/", $entry->getAttribute("href"), $matches) ||
preg_match("/youtube\.com\/watch\?v=([\w-]+)/", $entry->getAttribute("href"), $matches) || preg_match("/youtube\.com\/watch\?v=([\w-]+)/", $entry->getAttribute("href"), $matches) ||
preg_match("/\/\/youtu.be\/([\w-]+)/", $entry->getAttribute("href"), $matches)) { preg_match("/\/\/youtu.be\/([\w-]+)/", $entry->getAttribute("href"), $matches)) {
@ -179,7 +199,10 @@ class Af_RedditImgur extends Plugin {
$found = true; $found = true;
} }
if (preg_match("/\.(jpg|jpeg|gif|png)(\?[0-9][0-9]*)?$/i", $entry->getAttribute("href"))) { if (!$found && preg_match("/\.(jpg|jpeg|gif|png)(\?[0-9][0-9]*)?$/i", $entry->getAttribute("href")) ||
mb_strpos($entry->getAttribute("href"), "i.reddituploads.com") !== FALSE ||
mb_strpos($this->get_content_type($entry->getAttribute("href")), "image/") !== FALSE) {
_debug("Handling as a picture", $debug); _debug("Handling as a picture", $debug);
$img = $doc->createElement('img'); $img = $doc->createElement('img');
@ -194,7 +217,7 @@ class Af_RedditImgur extends Plugin {
// linked albums & pages // linked albums & pages
if (preg_match("/^https?:\/\/(m\.)?imgur.com\/([^\.\/]+$)/", $entry->getAttribute("href"), $matches) || if (!$found && preg_match("/^https?:\/\/(m\.)?imgur.com\/([^\.\/]+$)/", $entry->getAttribute("href"), $matches) ||
preg_match("/^https?:\/\/(m\.)?imgur.com\/(a|album|gallery)\/[^\.]+$/", $entry->getAttribute("href"), $matches)) { preg_match("/^https?:\/\/(m\.)?imgur.com\/(a|album|gallery)\/[^\.]+$/", $entry->getAttribute("href"), $matches)) {
_debug("Handling as an imgur page/album/gallery", $debug); _debug("Handling as an imgur page/album/gallery", $debug);
@ -208,51 +231,35 @@ class Af_RedditImgur extends Plugin {
if (@$adoc->loadHTML($album_content)) { if (@$adoc->loadHTML($album_content)) {
$axpath = new DOMXPath($adoc); $axpath = new DOMXPath($adoc);
/*$aentries = $axpath->query("//meta[@property='og:image']"); $aentries = $axpath->query("(//div[@class='post-image']/img[@src] | //a[@class='zoom']/img[@src] | //div[@class='video-elements']/source)");
$urls = array(); $urls = [];
foreach ($aentries as $aentry) { foreach ($aentries as $aentry) {
_debug("og:image content=" . $aentry->getAttribute("content"), $debug); $url = $aentry->getAttribute("src");
$url = str_replace("?fb", "", $aentry->getAttribute("content"));
$check_url = basename($url);
$check_url = mb_substr($check_url, 0, strrpos($check_url, "."));
if (!in_array($check_url, $urls)) {
$img = $doc->createElement('img');
$img->setAttribute("src", $url);
$entry->parentNode->insertBefore($doc->createElement('br'), $entry);
$br = $doc->createElement('br');
$entry->parentNode->insertBefore($img, $entry);
$entry->parentNode->insertBefore($br, $entry);
array_push($urls, $check_url);
$found = true; if (!in_array($url, $urls)) {
}
} */
//if ($debug) print_r($album_content); if ($aentry->tagName == "img") {
$aentries = $axpath->query("(//div[@class='post-image']/img[@src] | //a[@class='zoom']/img[@src])"); $img = $doc->createElement('img');
$urls = []; $img->setAttribute("src", $url);
$entry->parentNode->insertBefore($doc->createElement('br'), $entry);
foreach ($aentries as $aentry) { $br = $doc->createElement('br');
$url = $aentry->getAttribute("src"); $entry->parentNode->insertBefore($img, $entry);
$entry->parentNode->insertBefore($br, $entry);
} else if ($aentry->tagName == "source") {
if (!in_array($url, $urls)) { if (strpos($url, "imgur.com") !== FALSE)
$img = $doc->createElement('img'); $poster_url = str_replace(".mp4", "h.jpg", $url);
$img->setAttribute("src", $url); else
$entry->parentNode->insertBefore($doc->createElement('br'), $entry); $poster_url = "";
$br = $doc->createElement('br'); $this->handle_as_video($doc, $entry, $url, $poster_url);
$entry->parentNode->insertBefore($img, $entry); }
$entry->parentNode->insertBefore($br, $entry);
array_push($urls, $url); array_push($urls, $url);
@ -267,7 +274,7 @@ class Af_RedditImgur extends Plugin {
} }
// wtf is this even // wtf is this even
if (preg_match("/^https?:\/\/gyazo\.com\/([^\.\/]+$)/", $entry->getAttribute("href"), $matches)) { if (!$found && preg_match("/^https?:\/\/gyazo\.com\/([^\.\/]+$)/", $entry->getAttribute("href"), $matches)) {
$img_id = $matches[1]; $img_id = $matches[1];
_debug("handling as gyazo: $img_id", $debug); _debug("handling as gyazo: $img_id", $debug);
@ -301,8 +308,9 @@ class Af_RedditImgur extends Plugin {
@$doc->loadHTML($article["content"]); @$doc->loadHTML($article["content"]);
$xpath = new DOMXPath($doc); $xpath = new DOMXPath($doc);
$content_link = $xpath->query("(//a[contains(., '[link]')])")->item(0);
if ($this->host->get($this, "enable_content_dupcheck")) { if ($this->host->get($this, "enable_content_dupcheck")) {
$content_link = $xpath->query("(//a[contains(., '[link]')])")->item(0);
if ($content_link) { if ($content_link) {
$content_href = db_escape_string($content_link->getAttribute("href")); $content_href = db_escape_string($content_link->getAttribute("href"));
@ -333,81 +341,12 @@ class Af_RedditImgur extends Plugin {
$found = $this->inline_stuff($article, $doc, $xpath); $found = $this->inline_stuff($article, $doc, $xpath);
if (!defined('NO_CURL') && function_exists("curl_init") && !$found && $this->host->get($this, "enable_readability") &&
mb_strlen(strip_tags($article["content"])) <= 150) {
if (!class_exists("Readability")) require_once(dirname(dirname(__DIR__)). "/lib/readability/Readability.php");
if ($content_link &&
strpos($content_link->getAttribute("href"), "twitter.com") === FALSE &&
strpos($content_link->getAttribute("href"), "youtube.com") === FALSE &&
strpos($content_link->getAttribute("href"), "reddit.com") === FALSE) {
/* link may lead to a huge video file or whatever, we need to check content type before trying to
parse it which p much requires curl */
$ch = curl_init($content_link->getAttribute("href"));
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, !ini_get("open_basedir"));
curl_setopt($ch, CURLOPT_USERAGENT, SELF_USER_AGENT);
@$result = curl_exec($ch);
$content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
if ($content_type && strpos($content_type, "text/html") !== FALSE) {
$tmp = fetch_file_contents($content_link->getAttribute("href"));
//_debug("tmplen: " . mb_strlen($tmp));
if ($tmp && mb_strlen($tmp) < 65535 * 4) {
$r = new Readability($tmp, $content_link->getAttribute("href"));
if ($r->init()) {
$tmpxpath = new DOMXPath($r->dom);
$entries = $tmpxpath->query('(//a[@href]|//img[@src])');
foreach ($entries as $entry) {
if ($entry->hasAttribute("href")) {
$entry->setAttribute("href",
rewrite_relative_url($content_link->getAttribute("href"), $entry->getAttribute("href")));
}
if ($entry->hasAttribute("src")) {
$entry->setAttribute("src",
rewrite_relative_url($content_link->getAttribute("href"), $entry->getAttribute("src")));
}
}
$article["content"] = $r->articleContent->innerHTML . "<hr/>" . $article["content"];
// prob not a very good idea (breaks wikipedia pages, etc) -
// inliner currently is not really fit for any random web content
//$doc = new DOMDocument();
//@$doc->loadHTML($article["content"]);
//$xpath = new DOMXPath($doc);
//$found = $this->inline_stuff($article, $doc, $xpath);
}
}
}
}
}
$node = $doc->getElementsByTagName('body')->item(0); $node = $doc->getElementsByTagName('body')->item(0);
if ($node && $found) { if ($node && $found) {
$article["content"] = $doc->saveXML($node); $article["content"] = $doc->saveXML($node);
} else if ($content_link) {
$article = $this->readability($article, $content_link->getAttribute("href"), $doc, $xpath);
} }
} }
@ -457,12 +396,101 @@ class Af_RedditImgur extends Plugin {
@$doc->loadHTML("<html><body><a href=\"$url\">[link]</a></body>"); @$doc->loadHTML("<html><body><a href=\"$url\">[link]</a></body>");
$xpath = new DOMXPath($doc); $xpath = new DOMXPath($doc);
print "Inline result: " . $this->inline_stuff([], $doc, $xpath, true) . "\n"; $found = $this->inline_stuff([], $doc, $xpath, true);
print "Inline result: $found\n";
if (!$found) {
print "\nReadability result:\n";
$article = $this->readability([], $url, $doc, $xpath, true);
print_r($article);
} else {
print "\nResulting HTML:\n";
print $doc->saveHTML();
}
}
private function get_content_type($url, $useragent = SELF_USER_AGENT) {
$content_type = false;
if (function_exists("curl_init") && !defined("NO_CURL")) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, !ini_get("open_basedir"));
curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
@$result = curl_exec($ch);
$content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
}
return $content_type;
}
private function readability($article, $url, $doc, $xpath, $debug = false) {
if (!defined('NO_CURL') && function_exists("curl_init") && $this->host->get($this, "enable_readability") &&
mb_strlen(strip_tags($article["content"])) <= 150) {
print "\nResulting HTML:\n"; if (!class_exists("Readability")) require_once(dirname(dirname(__DIR__)). "/lib/readability/Readability.php");
print $doc->saveHTML(); if ($url &&
strpos($url, "twitter.com") === FALSE &&
strpos($url, "youtube.com") === FALSE &&
strpos($url, "reddit.com") === FALSE) {
/* link may lead to a huge video file or whatever, we need to check content type before trying to
parse it which p much requires curl */
$useragent_compat = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)";
$content_type = $this->get_content_type($url, $useragent_compat);
if ($content_type && strpos($content_type, "text/html") !== FALSE) {
$tmp = fetch_file_contents(array("url" => $url,
"useragent" => $useragent_compat));
if ($debug) _debug("tmplen: " . mb_strlen($tmp));
if ($tmp && mb_strlen($tmp) < 1024 * 500) {
$r = new Readability($tmp, $url);
if ($r->init()) {
$tmpxpath = new DOMXPath($r->dom);
$entries = $tmpxpath->query('(//a[@href]|//img[@src])');
foreach ($entries as $entry) {
if ($entry->hasAttribute("href")) {
$entry->setAttribute("href",
rewrite_relative_url($url, $entry->getAttribute("href")));
}
if ($entry->hasAttribute("src")) {
$entry->setAttribute("src",
rewrite_relative_url($url, $entry->getAttribute("src")));
}
}
$article["content"] = $r->articleContent->innerHTML . "<hr/>" . $article["content"];
}
}
}
}
}
return $article;
} }
} }
?> ?>

@ -1,40 +1,41 @@
dojo.addOnLoad(function() { require(['dojo/_base/kernel', 'dojo/ready'], function (dojo, ready) {
PluginHost.register(PluginHost.HOOK_ARTICLE_RENDERED_CDM, function(row) { ready(function () {
if (row) { PluginHost.register(PluginHost.HOOK_ARTICLE_RENDERED_CDM, function (row) {
console.log("af_zz_noautoplay!"); if (row) {
console.log(row); console.log("af_zz_noautoplay!");
console.log(row);
var videos = row.getElementsByTagName("video");
console.log(row.innerHTML); var videos = row.getElementsByTagName("video");
console.log(row.innerHTML);
for (i = 0; i < videos.length; i++) {
for (i = 0; i < videos.length; i++) {
videos[i].removeAttribute("autoplay");
videos[i].pause(); videos[i].removeAttribute("autoplay");
videos[i].onclick = function() { videos[i].pause();
this.paused ? this.play() : this.pause(); videos[i].onclick = function () {
this.paused ? this.play() : this.pause();
}
} }
} }
}
return true; return true;
}); });
PluginHost.register(PluginHost.HOOK_ARTICLE_RENDERED, function(row) { PluginHost.register(PluginHost.HOOK_ARTICLE_RENDERED, function (row) {
if (row) { if (row) {
var videos = row.getElementsByTagName("video"); var videos = row.getElementsByTagName("video");
for (i = 0; i < videos.length; i++) { for (i = 0; i < videos.length; i++) {
videos[i].removeAttribute("autoplay"); videos[i].removeAttribute("autoplay");
videos[i].pause(); videos[i].pause();
videos[i].onclick = function() { videos[i].onclick = function () {
this.paused ? this.play() : this.pause(); this.paused ? this.play() : this.pause();
}
} }
}
} }
return true; return true;
});
}); });
}); });

@ -425,34 +425,35 @@ class Import_Export extends Plugin implements IHandler {
print "<div style='text-align : center'>"; print "<div style='text-align : center'>";
if ($_FILES['export_file']['error'] != 0) { if ($_FILES['export_file']['error'] != 0) {
print_error(T_sprintf("Upload failed with error code %d", print_error(T_sprintf("Upload failed with error code %d (%s)",
$_FILES['export_file']['error'])); $_FILES['export_file']['error'],
return; get_upload_error_message($_FILES['export_file']['error'])));
} } else {
$tmp_file = false; $tmp_file = false;
if (is_uploaded_file($_FILES['export_file']['tmp_name'])) { if (is_uploaded_file($_FILES['export_file']['tmp_name'])) {
$tmp_file = tempnam(CACHE_DIR . '/upload', 'export'); $tmp_file = tempnam(CACHE_DIR . '/upload', 'export');
$result = move_uploaded_file($_FILES['export_file']['tmp_name'], $result = move_uploaded_file($_FILES['export_file']['tmp_name'],
$tmp_file); $tmp_file);
if (!$result) { if (!$result) {
print_error(__("Unable to move uploaded file.")); print_error(__("Unable to move uploaded file."));
return;
}
} else {
print_error(__('Error: please upload OPML file.'));
return; return;
} }
} else {
print_error(__('Error: please upload OPML file.'));
return;
}
if (is_file($tmp_file)) { if (is_file($tmp_file)) {
$this->perform_data_import($tmp_file, $_SESSION['uid']); $this->perform_data_import($tmp_file, $_SESSION['uid']);
unlink($tmp_file); unlink($tmp_file);
} else { } else {
print_error(__('No file uploaded.')); print_error(__('No file uploaded.'));
return; return;
}
} }
print "<button dojoType=\"dijit.form.Button\" print "<button dojoType=\"dijit.form.Button\"

@ -1,5 +1,7 @@
dojo.addOnLoad(function() { require(['dojo/_base/kernel', 'dojo/ready'], function (dojo, ready) {
updateTitle = function() { ready(function () {
document.title = "Tiny Tiny RSS"; updateTitle = function () {
}; document.title = "Tiny Tiny RSS";
};
});
}); });

@ -1,4 +1,9 @@
dojo.addOnLoad(function() { require(['dojo/_base/kernel', 'dojo/ready'], function (dojo, ready) {
hash_set = function() { }; ready(function () {
hash_get = function() { }; hash_set = function () {
};
hash_get = function () {
};
});
}); });

@ -22,26 +22,30 @@ function expandSizeWrapper(id) {
} }
dojo.addOnLoad(function() { require(['dojo/_base/kernel', 'dojo/ready'], function (dojo, ready) {
PluginHost.register(PluginHost.HOOK_ARTICLE_RENDERED_CDM, function(row) {
if (getInitParam('cdm_expanded')) {
window.setTimeout(function() { ready(function() {
if (row) { PluginHost.register(PluginHost.HOOK_ARTICLE_RENDERED_CDM, function(row) {
if (row.offsetHeight >= _shorten_expanded_threshold * window.innerHeight) { if (getInitParam('cdm_expanded')) {
var content = row.select(".cdmContentInner")[0];
if (content) { window.setTimeout(function() {
content.innerHTML = "<div class='contentSizeWrapper'>" + if (row) {
content.innerHTML + "</div><button class='expandPrompt' onclick='return expandSizeWrapper(\""+row.id+"\")' "+ if (row.offsetHeight >= _shorten_expanded_threshold * window.innerHeight) {
"href='#'>" + __("Click to expand article") + "</button>"; var content = row.select(".cdmContentInner")[0];
if (content) {
content.innerHTML = "<div class='contentSizeWrapper'>" +
content.innerHTML + "</div><button class='expandPrompt' onclick='return expandSizeWrapper(\""+row.id+"\")' "+
"href='#'>" + __("Click to expand article") + "</button>";
}
} }
} }
} }, 150);
}, 150); }
}
return true; return true;
});
}); });
}); });

@ -54,6 +54,16 @@
<link rel="shortcut icon" type="image/png" href="images/favicon.png"/> <link rel="shortcut icon" type="image/png" href="images/favicon.png"/>
<link rel="icon" type="image/png" sizes="72x72" href="images/favicon-72px.png" /> <link rel="icon" type="image/png" sizes="72x72" href="images/favicon-72px.png" />
<script>
dojoConfig = {
async: true,
packages: [
{ name: "lib", location: "../" },
{ name: "fox", location: "../../js" },
]
};
</script>
<?php <?php
foreach (array("lib/prototype.js", foreach (array("lib/prototype.js",
"lib/scriptaculous/scriptaculous.js?load=effects,controls", "lib/scriptaculous/scriptaculous.js?load=effects,controls",
@ -72,11 +82,16 @@
foreach (PluginHost::getInstance()->get_plugins() as $n => $p) { foreach (PluginHost::getInstance()->get_plugins() as $n => $p) {
if (method_exists($p, "get_prefs_js")) { if (method_exists($p, "get_prefs_js")) {
echo "try {";
echo JShrink\Minifier::minify($p->get_prefs_js()); echo JShrink\Minifier::minify($p->get_prefs_js());
echo "} catch (e) {
console.warn('failed to initialize plugin JS: $n');
console.warn(e);
}";
} }
} }
print get_minified_js(array("../lib/CheckBoxTree","functions", "deprecated", "prefs", "PrefFeedTree", "PrefFilterTree", "PrefLabelTree")); print get_minified_js(array("functions", "deprecated", "prefs"));
init_js_translations(); init_js_translations();
?> ?>

@ -1,5 +1,8 @@
BEGIN; BEGIN;
update ttrss_feeds set last_updated = NULL;
alter table ttrss_feeds modify column last_updated datetime DEFAULT NULL;
alter table ttrss_feeds add column feed_language varchar(100); alter table ttrss_feeds add column feed_language varchar(100);
update ttrss_feeds set feed_language = ''; update ttrss_feeds set feed_language = '';
alter table ttrss_feeds change feed_language feed_language varchar(100) not null; alter table ttrss_feeds change feed_language feed_language varchar(100) not null;

Loading…
Cancel
Save