digest.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924
  1. var last_feeds = [];
  2. var init_params = {};
  3. var hotkeys_map = false;
  4. var hotkey_prefix = false;
  5. var mobile_mode = false;
  6. var _active_feed_id = false;
  7. var _update_timeout = false;
  8. var _view_update_timeout = false;
  9. var _feedlist_expanded = false;
  10. var _update_seq = 1;
  11. function article_appear(article_id) {
  12. try {
  13. new Effect.Appear('A-' + article_id);
  14. } catch (e) {
  15. exception_error("article_appear", e);
  16. }
  17. }
  18. function catchup_feed(feed_id, callback) {
  19. try {
  20. var fn = find_feed(last_feeds, feed_id).title;
  21. if (confirm(__("Mark all articles in %s as read?").replace("%s", fn))) {
  22. var is_cat = "";
  23. if (feed_id < 0) is_cat = "true"; // KLUDGE
  24. var query = "op=rpc&method=catchupFeed&feed_id=" +
  25. feed_id + "&is_cat=" + is_cat;
  26. new Ajax.Request("backend.php", {
  27. parameters: query,
  28. onComplete: function(transport) {
  29. if (callback) callback(transport);
  30. update();
  31. } });
  32. }
  33. } catch (e) {
  34. exception_error("catchup_article", e);
  35. }
  36. }
  37. function get_visible_article_ids() {
  38. try {
  39. var elems = $("headlines-content").getElementsByTagName("LI");
  40. var ids = [];
  41. for (var i = 0; i < elems.length; i++) {
  42. if (elems[i].id && elems[i].id.match("A-")) {
  43. ids.push(elems[i].id.replace("A-", ""));
  44. }
  45. }
  46. return ids;
  47. } catch (e) {
  48. exception_error("get_visible_article_ids", e);
  49. }
  50. }
  51. function catchup_visible_articles(callback) {
  52. try {
  53. var ids = get_visible_article_ids();
  54. if (confirm(ngettext("Mark %d displayed article as read?", "Mark %d displayed articles as read?", ids.length).replace("%d", ids.length))) {
  55. var query = "op=rpc&method=catchupSelected" +
  56. "&cmode=0&ids=" + param_escape(ids);
  57. new Ajax.Request("backend.php", {
  58. parameters: query,
  59. onComplete: function(transport) {
  60. if (callback) callback(transport);
  61. viewfeed(_active_feed_id, 0);
  62. } });
  63. }
  64. } catch (e) {
  65. exception_error("catchup_visible_articles", e);
  66. }
  67. }
  68. function catchup_article(article_id, callback) {
  69. try {
  70. var query = "op=rpc&method=catchupSelected" +
  71. "&cmode=0&ids=" + article_id;
  72. new Ajax.Request("backend.php", {
  73. parameters: query,
  74. onComplete: function(transport) {
  75. if (callback) callback(transport);
  76. } });
  77. } catch (e) {
  78. exception_error("catchup_article", e);
  79. }
  80. }
  81. function set_selected_article(article_id) {
  82. try {
  83. $$("#headlines-content > li[id*=A-]").each(function(article) {
  84. var id = article.id.replace("A-", "");
  85. var cb = article.getElementsByTagName("INPUT")[0];
  86. if (id == article_id) {
  87. article.addClassName("selected");
  88. cb.checked = true;
  89. } else {
  90. article.removeClassName("selected");
  91. cb.checked = false;
  92. }
  93. });
  94. } catch (e) {
  95. exception_error("set_selected_article", e);
  96. }
  97. }
  98. function set_selected_feed(feed_id) {
  99. try {
  100. var feeds = $("feeds-content").getElementsByTagName("LI");
  101. for (var i = 0; i < feeds.length; i++) {
  102. if (feeds[i].id == "F-" + feed_id)
  103. feeds[i].className = "selected";
  104. else
  105. feeds[i].className = "";
  106. }
  107. _active_feed_id = feed_id;
  108. } catch (e) {
  109. exception_error("set_selected_feed", e);
  110. }
  111. }
  112. function load_more() {
  113. try {
  114. var pr = $("H-LOADING-IMG");
  115. if (pr) Element.show(pr);
  116. var offset = $$("#headlines-content > li[id*=A-][class*=fresh],li[id*=A-][class*=unread]").length;
  117. viewfeed(false, offset, false, false, true,
  118. function() {
  119. var pr = $("H-LOADING-IMG");
  120. if (pr) Element.hide(pr);
  121. });
  122. } catch (e) {
  123. exception_error("load_more", e);
  124. }
  125. }
  126. function update(callback) {
  127. try {
  128. console.log('updating feeds...');
  129. window.clearTimeout(_update_timeout);
  130. new Ajax.Request("backend.php", {
  131. parameters: "op=digest&method=digestinit",
  132. onComplete: function(transport) {
  133. fatal_error_check(transport);
  134. parse_feeds(transport);
  135. set_selected_feed(_active_feed_id);
  136. if (callback) callback(transport);
  137. } });
  138. _update_timeout = window.setTimeout('update()', 5*1000);
  139. } catch (e) {
  140. exception_error("update", e);
  141. }
  142. }
  143. function remove_headline_entry(article_id) {
  144. try {
  145. var elem = $('A-' + article_id);
  146. if (elem) {
  147. elem.parentNode.removeChild(elem);
  148. }
  149. } catch (e) {
  150. exception_error("remove_headline_entry", e);
  151. }
  152. }
  153. function view_update() {
  154. try {
  155. viewfeed(_active_feed_id, _active_feed_offset, false, true, true);
  156. update();
  157. } catch (e) {
  158. exception_error("view_update", e);
  159. }
  160. }
  161. function view(article_id) {
  162. try {
  163. $("content").addClassName("move");
  164. var a = $("A-" + article_id);
  165. var h = $("headlines");
  166. setTimeout(function() {
  167. // below or above viewport, reposition headline
  168. if (a.offsetTop > h.scrollTop + h.offsetHeight || a.offsetTop+a.offsetHeight < h.scrollTop+a.offsetHeight)
  169. h.scrollTop = a.offsetTop - (h.offsetHeight/2 - a.offsetHeight/2);
  170. }, 500);
  171. new Ajax.Request("backend.php", {
  172. parameters: "op=digest&method=digestgetcontents&article_id=" +
  173. article_id,
  174. onComplete: function(transport) {
  175. fatal_error_check(transport);
  176. var reply = JSON.parse(transport.responseText);
  177. if (reply) {
  178. var article = reply['article'];
  179. var mark_part = "";
  180. var publ_part = "";
  181. var tags_part = "";
  182. if (article.tags.length > 0) {
  183. tags_part = " " + __("in") + " ";
  184. for (var i = 0; i < Math.min(5, article.tags.length); i++) {
  185. //tags_part += "<a href=\"#\" onclick=\"viewfeed('" +
  186. // article.tags[i] + "')\">" +
  187. // article.tags[i] + "</a>, ";
  188. tags_part += article.tags[i] + ", ";
  189. }
  190. tags_part = tags_part.replace(/, $/, "");
  191. tags_part = "<span class=\"tags\">" + tags_part + "</span>";
  192. }
  193. if (article.marked)
  194. mark_part = "<img title='"+ __("Unstar article")+"' onclick=\"toggle_mark(this, "+article.id+")\" src='images/mark_set.svg'>";
  195. else
  196. mark_part = "<img title='"+__("Star article")+"' onclick=\"toggle_mark(this, "+article.id+")\" src='images/mark_unset.svg'>";
  197. if (article.published)
  198. publ_part = "<img title='"+__("Unpublish article")+"' onclick=\"toggle_pub(this, "+article.id+")\" src='images/pub_set.svg'>";
  199. else
  200. publ_part = "<img title='"+__("Publish article")+"' onclick=\"toggle_pub(this, "+article.id+")\" src='images/pub_unset.svg'>";
  201. var tmp = "<div id=\"inner\">" +
  202. "<div id=\"ops\">" +
  203. mark_part +
  204. publ_part +
  205. "</div>" +
  206. "<h1>" + "<a target=\"_blank\" href=\""+article.url+"\">" +
  207. article.title + "</a>" + "</h1>" +
  208. "<div id=\"tags\">" +
  209. tags_part +
  210. "</div>" +
  211. article.content + "</div>";
  212. $("article-content").innerHTML = tmp;
  213. $("article").addClassName("visible");
  214. set_selected_article(article.id);
  215. catchup_article(article_id,
  216. function() {
  217. $("A-" + article_id).addClassName("read");
  218. });
  219. } else {
  220. elem.innerHTML = __("Error: unable to load article.");
  221. }
  222. }
  223. });
  224. return false;
  225. } catch (e) {
  226. exception_error("view", e);
  227. }
  228. }
  229. function close_feed() {
  230. $("headlines").removeClassName("move");
  231. if (mobile_mode) set_selected_feed(false);
  232. }
  233. function go_back() {
  234. if ($("article").hasClassName("visible")) {
  235. close_article();
  236. } else {
  237. close_feed();
  238. }
  239. }
  240. function close_article() {
  241. $("content").removeClassName("move");
  242. $("article").removeClassName("visible");
  243. }
  244. function viewfeed(feed_id, offset, replace, no_effects, no_indicator, callback) {
  245. try {
  246. $("headlines").addClassName("move");
  247. if (!feed_id) feed_id = _active_feed_id;
  248. if (offset == undefined) offset = 0;
  249. if (replace == undefined) replace = (offset == 0);
  250. _update_seq = _update_seq + 1;
  251. if (!offset) $("headlines").scrollTop = 0;
  252. var query = "op=digest&method=digestupdate&feed_id=" +
  253. param_escape(feed_id) + "&offset=" + offset +
  254. "&seq=" + _update_seq;
  255. console.log(query);
  256. var img = false;
  257. if ($("F-" + feed_id)) {
  258. img = $("F-" + feed_id).getElementsByTagName("IMG")[0];
  259. if (img && !no_indicator) {
  260. img.setAttribute("orig_src", img.src);
  261. img.src = 'images/indicator_tiny.gif';
  262. }
  263. }
  264. new Ajax.Request("backend.php", {
  265. parameters: query,
  266. onComplete: function(transport) {
  267. Element.hide("overlay");
  268. fatal_error_check(transport);
  269. parse_headlines(transport, replace, no_effects);
  270. set_selected_feed(feed_id);
  271. _active_feed_offset = offset;
  272. if (img && !no_indicator)
  273. img.src = img.getAttribute("orig_src");
  274. if (callback) callback(transport);
  275. } });
  276. } catch (e) {
  277. exception_error("view", e);
  278. }
  279. }
  280. function find_article(articles, article_id) {
  281. try {
  282. for (var i = 0; i < articles.length; i++) {
  283. if (articles[i].id == article_id)
  284. return articles[i];
  285. }
  286. return false;
  287. } catch (e) {
  288. exception_error("find_article", e);
  289. }
  290. }
  291. function find_feed(feeds, feed_id) {
  292. try {
  293. for (var i = 0; i < feeds.length; i++) {
  294. if (feeds[i].id == feed_id)
  295. return feeds[i];
  296. }
  297. return false;
  298. } catch (e) {
  299. exception_error("find_feed", e);
  300. }
  301. }
  302. function get_feed_icon(feed) {
  303. try {
  304. if (feed.has_icon)
  305. return getInitParam('icons_url') + "/" + feed.id + '.ico';
  306. if (feed.id == -1)
  307. return 'images/mark_set.svg';
  308. if (feed.id == -2)
  309. return 'images/pub_set.svg';
  310. if (feed.id == -3)
  311. return 'images/fresh.png';
  312. if (feed.id == -4)
  313. return 'images/tag.png';
  314. if (feed.id < -10)
  315. return 'images/label.png';
  316. return 'images/blank_icon.gif';
  317. } catch (e) {
  318. exception_error("get_feed_icon", e);
  319. }
  320. }
  321. function add_feed_entry(feed) {
  322. try {
  323. var icon_part = "";
  324. icon_part = "<img src='" + get_feed_icon(feed) + "'/>";
  325. var title = (feed.title.length > 30) ?
  326. feed.title.substring(0, 30) + "&hellip;" :
  327. feed.title;
  328. var tmp_html = "<li id=\"F-"+feed.id+"\" onclick=\"viewfeed("+feed.id+")\">" +
  329. "<div class='unread-ctr'>" + "<span class=\"unread\">" + feed.unread + "</span></div>" +
  330. icon_part + title +
  331. "</li>";
  332. $("feeds-content").innerHTML += tmp_html;
  333. } catch (e) {
  334. exception_error("add_feed_entry", e);
  335. }
  336. }
  337. function add_headline_entry(article, feed, no_effects) {
  338. try {
  339. var icon_part = "";
  340. icon_part = "<img class='icon' src='" + get_feed_icon(feed) + "'/>";
  341. var style = "";
  342. //if (!no_effects) style = "style=\"display : none\"";
  343. if (article.excerpt.trim() == "")
  344. article.excerpt = __("Click to expand article.");
  345. var li_class = "unread";
  346. var fresh_max = getInitParam("fresh_article_max_age") * 60 * 60;
  347. var d = new Date();
  348. if (d.getTime() / 1000 - article.updated < fresh_max)
  349. li_class = "fresh";
  350. var checkbox_part = "<input type=\"checkbox\" class=\"cb\" onclick=\"toggle_select_article(this)\"/>";
  351. var date = new Date(article.updated * 1000);
  352. var date_part = date.toString().substring(0,21);
  353. var tmp_html = "<li id=\"A-"+article.id+"\" "+style+" class=\""+li_class+"\">" +
  354. checkbox_part +
  355. icon_part +
  356. "<a target=\"_blank\" href=\""+article.link+"\""+
  357. "onclick=\"return view("+article.id+")\" class='title'>" +
  358. article.title + "</a>" +
  359. "<div class='body'>" +
  360. "<div onclick=\"view("+article.id+")\" class='excerpt'>" +
  361. article.excerpt + "</div>" +
  362. "<div onclick=\"view("+article.id+")\" class='info'>";
  363. /* tmp_html += "<a href=\#\" onclick=\"viewfeed("+feed.id+")\">" +
  364. feed.title + "</a> " + " @ "; */
  365. tmp_html += date_part + "</div>" +
  366. "</div></li>";
  367. $("headlines-content").innerHTML += tmp_html;
  368. if (!no_effects)
  369. window.setTimeout('article_appear(' + article.id + ')', 100);
  370. } catch (e) {
  371. exception_error("add_headline_entry", e);
  372. }
  373. }
  374. function expand_feeds() {
  375. try {
  376. _feedlist_expanded = true;
  377. redraw_feedlist(last_feeds);
  378. } catch (e) {
  379. exception_error("expand_feeds", e);
  380. }
  381. }
  382. function redraw_feedlist(feeds) {
  383. try {
  384. $('feeds-content').innerHTML = "";
  385. var limit = 10;
  386. if (_feedlist_expanded) limit = feeds.length;
  387. for (var i = 0; i < Math.min(limit, feeds.length); i++) {
  388. add_feed_entry(feeds[i]);
  389. }
  390. if (feeds.length > limit) {
  391. $('feeds-content').innerHTML += "<li id='F-MORE-PROMPT'>" +
  392. "<img src='images/blank_icon.gif'>" +
  393. "<a href=\"#\" onclick=\"expand_feeds()\">" +
  394. ngettext("%d more...", "%d more...", feeds.length-10).replace("%d", feeds.length-10) +
  395. "</a>" + "</li>";
  396. }
  397. if (feeds.length == 0) {
  398. $('feeds-content').innerHTML =
  399. "<div class='insensitive' style='text-align : center'>" +
  400. __("No unread feeds.") + "</div>";
  401. }
  402. if (_active_feed_id)
  403. set_selected_feed(_active_feed_id);
  404. } catch (e) {
  405. exception_error("redraw_feedlist", e);
  406. }
  407. }
  408. function parse_feeds(transport) {
  409. try {
  410. var reply = JSON.parse(transport.responseText);
  411. if (!reply) return;
  412. var feeds = reply['feeds'];
  413. if (feeds) {
  414. feeds.sort( function (a,b)
  415. {
  416. if (b.unread != a.unread)
  417. return (b.unread - a.unread);
  418. else
  419. if (a.title > b.title)
  420. return 1;
  421. else if (a.title < b.title)
  422. return -1;
  423. else
  424. return 0;
  425. });
  426. var all_articles = find_feed(feeds, -4);
  427. update_title(all_articles.unread);
  428. last_feeds = feeds;
  429. redraw_feedlist(feeds);
  430. }
  431. if (reply['hotkeys']) {
  432. hotkeys_map = reply['hotkeys'];
  433. }
  434. } catch (e) {
  435. console.log(e);
  436. //exception_error("parse_feeds", e);
  437. }
  438. }
  439. function parse_headlines(transport, replace, no_effects) {
  440. try {
  441. var reply = JSON.parse(transport.responseText);
  442. if (!reply) return;
  443. var seq = reply['seq'];
  444. if (seq) {
  445. if (seq != _update_seq) {
  446. console.log("parse_headlines: wrong sequence received.");
  447. return;
  448. }
  449. } else {
  450. return;
  451. }
  452. var headlines = reply['headlines']['content'];
  453. var headlines_title = reply['headlines']['title'];
  454. if (headlines && headlines_title) {
  455. if (replace) {
  456. $('headlines-content').innerHTML = '';
  457. }
  458. var pr = $('H-MORE-PROMPT');
  459. if (pr) pr.parentNode.removeChild(pr);
  460. var inserted = false;
  461. for (var i = 0; i < headlines.length; i++) {
  462. if (!$('A-' + headlines[i].id)) {
  463. add_headline_entry(headlines[i],
  464. find_feed(last_feeds, headlines[i].feed_id), !no_effects);
  465. }
  466. }
  467. console.log(inserted.id);
  468. var ids = get_visible_article_ids();
  469. if (ids.length > 0) {
  470. if (pr) {
  471. $('headlines-content').appendChild(pr);
  472. } else {
  473. $('headlines-content').innerHTML += "<li id='H-MORE-PROMPT'>" +
  474. "<div class='body'>" +
  475. "<a href=\"#\" onclick=\"catchup_visible_articles()\">" +
  476. __("Mark as read") + "</a> | " +
  477. "<a href=\"javascript:load_more()\">" +
  478. __("Load more...") + "</a>" +
  479. "<img style=\"display : none\" "+
  480. "id=\"H-LOADING-IMG\" src='images/indicator_tiny.gif'>" +
  481. "</div></li>";
  482. }
  483. } else {
  484. // FIXME : display some kind of "nothing to see here" prompt here
  485. }
  486. // if (replace && !no_effects)
  487. // new Effect.Appear('headlines-content', {duration : 0.3});
  488. //new Effect.Appear('headlines-content');
  489. }
  490. } catch (e) {
  491. exception_error("parse_headlines", e);
  492. }
  493. }
  494. function init_second_stage() {
  495. try {
  496. new Ajax.Request("backend.php", {
  497. parameters: "op=digest&method=digestinit&init=1",
  498. onComplete: function(transport) {
  499. parse_feeds(transport);
  500. Element.hide("overlay");
  501. document.onkeydown = hotkey_handler;
  502. if (!mobile_mode)
  503. window.setTimeout('viewfeed(-4)', 100);
  504. _update_timeout = window.setTimeout('update()', 5*1000);
  505. } });
  506. } catch (e) {
  507. exception_error("init_second_stage", e);
  508. }
  509. }
  510. function init(mobile) {
  511. try {
  512. mobile_mode = mobile;
  513. new Ajax.Request("backend.php", {
  514. parameters: {op: "rpc", method: "sanityCheck"},
  515. onComplete: function(transport) {
  516. backend_sanity_check_callback(transport);
  517. } });
  518. } catch (e) {
  519. exception_error("digest_init", e);
  520. }
  521. }
  522. function toggle_mark(img, id) {
  523. try {
  524. var query = "op=rpc&id=" + id + "&method=mark";
  525. if (!img) return;
  526. if (img.src.match("mark_unset")) {
  527. img.src = img.src.replace("mark_unset", "mark_set");
  528. img.alt = __("Unstar article");
  529. query = query + "&mark=1";
  530. } else {
  531. img.src = img.src.replace("mark_set", "mark_unset");
  532. img.alt = __("Star article");
  533. query = query + "&mark=0";
  534. }
  535. new Ajax.Request("backend.php", {
  536. parameters: query,
  537. onComplete: function(transport) {
  538. update();
  539. } });
  540. } catch (e) {
  541. exception_error("toggle_mark", e);
  542. }
  543. }
  544. function toggle_pub(img, id, note) {
  545. try {
  546. var query = "op=rpc&id=" + id + "&method=publ";
  547. if (note != undefined) {
  548. query = query + "&note=" + param_escape(note);
  549. } else {
  550. query = query + "&note=undefined";
  551. }
  552. if (!img) return;
  553. if (img.src.match("pub_unset") || note != undefined) {
  554. img.src = img.src.replace("pub_unset", "pub_set");
  555. img.alt = __("Unpublish article");
  556. query = query + "&pub=1";
  557. } else {
  558. img.src = img.src.replace("pub_set", "pub_unset");
  559. img.alt = __("Publish article");
  560. query = query + "&pub=0";
  561. }
  562. new Ajax.Request("backend.php", {
  563. parameters: query,
  564. onComplete: function(transport) {
  565. update();
  566. } });
  567. } catch (e) {
  568. exception_error("toggle_pub", e);
  569. }
  570. }
  571. function fatal_error(code, msg) {
  572. try {
  573. if (code == 6) {
  574. window.location.href = "digest.php";
  575. } else if (code == 5) {
  576. window.location.href = "db-updater.php";
  577. } else {
  578. if (msg == "") msg = "Unknown error";
  579. console.error("Fatal error: " + code + "\n" +
  580. msg);
  581. }
  582. } catch (e) {
  583. exception_error("fatalError", e);
  584. }
  585. }
  586. function fatal_error_check(transport) {
  587. try {
  588. if (transport.responseXML) {
  589. var error = transport.responseXML.getElementsByTagName("error")[0];
  590. if (error) {
  591. var code = error.getAttribute("error-code");
  592. var msg = error.getAttribute("error-msg");
  593. if (code != 0) {
  594. fatal_error(code, msg);
  595. return false;
  596. }
  597. }
  598. }
  599. } catch (e) {
  600. exception_error("fatal_error_check", e);
  601. }
  602. return true;
  603. }
  604. function update_title(unread) {
  605. try {
  606. document.title = "Tiny Tiny RSS";
  607. if (unread > 0)
  608. document.title += " (" + unread + ")";
  609. } catch (e) {
  610. exception_error("update_title", e);
  611. }
  612. }
  613. function toggle_select_article(elem) {
  614. try {
  615. var article = elem.parentNode;
  616. if (article.hasClassName("selected"))
  617. article.removeClassName("selected");
  618. else
  619. article.addClassName("selected");
  620. } catch (e) {
  621. exception_error("toggle_select_article", e);
  622. }
  623. }
  624. function hotkey_handler(e) {
  625. try {
  626. if (e.target.nodeName == "INPUT" || e.target.nodeName == "TEXTAREA") return;
  627. var keycode = false;
  628. var shift_key = false;
  629. var cmdline = $('cmdline');
  630. try {
  631. shift_key = e.shiftKey;
  632. } catch (e) {
  633. }
  634. if (window.event) {
  635. keycode = window.event.keyCode;
  636. } else if (e) {
  637. keycode = e.which;
  638. }
  639. var keychar = String.fromCharCode(keycode);
  640. if (!shift_key) keychar = keychar.toLowerCase();
  641. if (keycode == 16) return; // ignore lone shift
  642. if (keycode == 17) return; // ignore lone ctrl
  643. var hotkey = keychar.search(/[a-zA-Z0-9]/) != -1 ? keychar : "(" + keycode + ")";
  644. hotkey = hotkey_prefix ? hotkey_prefix + " " + hotkey : hotkey;
  645. hotkey_prefix = false;
  646. var hotkey_action = false;
  647. var hotkeys = getInitParam("hotkeys");
  648. for (sequence in hotkeys[1]) {
  649. if (sequence == hotkey) {
  650. hotkey_action = hotkeys[1][sequence];
  651. break;
  652. }
  653. }
  654. switch (keycode) {
  655. case 27: // esc
  656. go_back();
  657. return false;
  658. }
  659. switch (hotkey_action) {
  660. case "next_feed":
  661. var feeds = $$("#feeds li");
  662. for (var i = 0; i < feeds.length; i++) {
  663. var base_id = feeds[i].id.replace("F-", "");
  664. if (base_id == _active_feed_id) {
  665. if (feeds[i+1]) {
  666. viewfeed(feeds[i+1].id.replace("F-", ""));
  667. }
  668. break;
  669. }
  670. }
  671. return false;
  672. case "prev_feed":
  673. var feeds = $$("#feeds li");
  674. for (var i = 0; i < feeds.length; i++) {
  675. var base_id = feeds[i].id.replace("F-", "");
  676. if (base_id == _active_feed_id) {
  677. if (feeds[i-1]) {
  678. viewfeed(feeds[i-1].id.replace("F-", ""));
  679. }
  680. break;
  681. }
  682. }
  683. return false;
  684. case "next_article":
  685. return false;
  686. case "prev_article":
  687. return false;
  688. default:
  689. console.log("unhandled action: " + hotkey_action + "; hotkey: " + hotkey);
  690. }
  691. } catch (e) {
  692. exception_error("hotkey_handler", e);
  693. }
  694. }