diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/AppBase.js | 60 | ||||
-rw-r--r-- | js/Article.js | 103 | ||||
-rw-r--r-- | js/CommonFilters.js | 55 | ||||
-rwxr-xr-x | js/Headlines.js | 327 | ||||
-rw-r--r-- | js/PrefFilterTree.js | 4 | ||||
-rw-r--r-- | js/form/ValidationTextArea.js | 33 | ||||
-rwxr-xr-x | js/prefs.js | 1 | ||||
-rw-r--r-- | js/tt-rss.js | 64 |
8 files changed, 318 insertions, 329 deletions
diff --git a/js/AppBase.js b/js/AppBase.js index 86cc44e8a..8f9d71653 100644 --- a/js/AppBase.js +++ b/js/AppBase.js @@ -7,6 +7,39 @@ define(["dojo/_base/declare"], function (declare) { hotkey_prefix: 0, hotkey_prefix_pressed: false, hotkey_prefix_timeout: 0, + Scrollable: { + scrollByPages: function (elem, page_offset) { + if (!elem) return; + + /* keep a line or so from the previous page */ + const offset = (elem.offsetHeight - (page_offset > 0 ? 50 : -50)) * page_offset; + + this.scroll(elem, offset); + }, + scroll: function(elem, offset) { + if (!elem) return; + + elem.scrollTop += offset; + }, + isChildVisible: function(elem, ctr) { + if (!elem) return; + + const ctop = ctr.scrollTop; + const cbottom = ctop + ctr.offsetHeight; + + const etop = elem.offsetTop; + const ebottom = etop + elem.offsetHeight; + + return etop >= ctop && ebottom <= cbottom || + etop < ctop && ebottom > ctop || ebottom > cbottom && etop < cbottom; + }, + fitsInContainer: function (elem, ctr) { + if (!elem) return; + + return elem.offsetTop + elem.offsetHeight <= ctr.scrollTop + ctr.offsetHeight && + elem.offsetTop >= ctr.scrollTop; + } + }, constructor: function() { window.onerror = this.Error.onWindowError; }, @@ -101,6 +134,17 @@ define(["dojo/_base/declare"], function (declare) { } }, + getActionByHotkeySequence: function (sequence) { + const hotkeys_map = App.getInitParam("hotkeys"); + + for (const seq in hotkeys_map[1]) { + if (hotkeys_map[1].hasOwnProperty(seq)) { + if (seq == sequence) { + return hotkeys_map[1][seq]; + } + } + } + }, keyeventToAction: function(event) { const hotkeys_map = App.getInitParam("hotkeys"); @@ -144,18 +188,16 @@ define(["dojo/_base/declare"], function (declare) { hotkey_name = keychar ? keychar : "(" + keycode + ")"; } - const hotkey_full = this.hotkey_prefix ? this.hotkey_prefix + " " + hotkey_name : hotkey_name; + let hotkey_full = this.hotkey_prefix ? this.hotkey_prefix + " " + hotkey_name : hotkey_name; this.hotkey_prefix = false; - let action_name = false; + let action_name = this.getActionByHotkeySequence(hotkey_full); - for (const sequence in hotkeys_map[1]) { - if (hotkeys_map[1].hasOwnProperty(sequence)) { - if (sequence == hotkey_full) { - action_name = hotkeys_map[1][sequence]; - break; - } - } + // check for mode-specific hotkey + if (!action_name) { + hotkey_full = (App.isCombinedMode() ? "{C}" : "{3}") + hotkey_full; + + action_name = this.getActionByHotkeySequence(hotkey_full); } console.log('keyeventToAction', hotkey_full, '=>', action_name); diff --git a/js/Article.js b/js/Article.js index 50447c2a1..3056dd666 100644 --- a/js/Article.js +++ b/js/Article.js @@ -139,7 +139,7 @@ define(["dojo/_base/declare"], function (declare) { c.attr('content', article); PluginHost.run(PluginHost.HOOK_ARTICLE_RENDERED, c.domNode); - Headlines.correctHeadlinesOffset(Article.getActive()); + //Headlines.correctHeadlinesOffset(Article.getActive()); try { c.focus(); @@ -149,14 +149,14 @@ define(["dojo/_base/declare"], function (declare) { formatComments: function(hl) { let comments = ""; - if (hl.comments) { + if (hl.comments || hl.num_comments > 0) { let comments_msg = __("comments"); if (hl.num_comments > 0) { comments_msg = hl.num_comments + " " + ngettext("comment", "comments", hl.num_comments) } - comments = `<a href="${escapeHtml(hl.comments)}">(${comments_msg})</a>`; + comments = `<a href="${escapeHtml(hl.comments ? hl.comments : hl.link)}">(${comments_msg})</a>`; } return comments; @@ -178,15 +178,29 @@ define(["dojo/_base/declare"], function (declare) { if (container.textContent.length == 0) container.innerHTML += " "; + // in expandable mode, save content for later, so that we can pack unfocused rows back + if (App.isCombinedMode() && $("main").hasClassName("expandable")) + row.setAttribute("data-content-original", row.getAttribute("data-content")); + row.removeAttribute("data-content"); PluginHost.run(PluginHost.HOOK_ARTICLE_RENDERED_CDM, row); } }, - view: function (id, noexpand) { + pack: function(row) { + if (row.hasAttribute("data-content-original")) { + console.log("packing", row.id); + row.setAttribute("data-content", row.getAttribute("data-content-original")); + row.removeAttribute("data-content-original"); + + row.querySelector(".content-inner").innerHTML = " "; + } + }, + view: function (id, no_expand) { this.setActive(id); + Headlines.scrollToArticleId(id); - if (!noexpand) { + if (!no_expand) { const hl = Headlines.objectById(id); if (hl) { @@ -277,49 +291,39 @@ define(["dojo/_base/declare"], function (declare) { dialog.show(); }, - cdmScrollToId: function (id, force, event) { - const ctr = $("headlines-frame"); - const e = $("RROW-" + id); - const is_expanded = App.getInitParam("cdm_expanded"); - - if (!e || !ctr) return; + cdmMoveToId: function (id, params) { + params = params || {}; - if (force || is_expanded || e.offsetTop + e.offsetHeight > (ctr.scrollTop + ctr.offsetHeight) || - e.offsetTop < ctr.scrollTop) { + const force_to_top = params.force_to_top || false; - if (event && event.repeat || !is_expanded) { - ctr.addClassName("forbid-smooth-scroll"); - window.clearTimeout(this._scroll_reset_timeout); - - this._scroll_reset_timeout = window.setTimeout(() => { - if (ctr) ctr.removeClassName("forbid-smooth-scroll"); - }, 250) - - } else { - ctr.removeClassName("forbid-smooth-scroll"); - } + const ctr = $("headlines-frame"); + const row = $("RROW-" + id); - ctr.scrollTop = e.offsetTop; + if (!row || !ctr) return; - Element.hide("floatingTitle"); + if (force_to_top || !App.Scrollable.fitsInContainer(row, ctr)) { + ctr.scrollTop = row.offsetTop; } }, setActive: function (id) { - console.log("setActive", id); + if (id != Article.getActive()) { + console.log("setActive", id, "was", Article.getActive()); - $$("div[id*=RROW][class*=active]").each((e) => { - e.removeClassName("active"); - }); + $$("div[id*=RROW][class*=active]").each((row) => { + row.removeClassName("active"); + Article.pack(row); + }); - const row = $("RROW-" + id); + const row = $("RROW-" + id); - if (row) { - Article.unpack(row); + if (row) { + Article.unpack(row); - row.removeClassName("Unread"); - row.addClassName("active"); + row.removeClassName("Unread"); + row.addClassName("active"); - PluginHost.run(PluginHost.HOOK_ARTICLE_SET_ACTIVE, row.getAttribute("data-article-id")); + PluginHost.run(PluginHost.HOOK_ARTICLE_SET_ACTIVE, row.getAttribute("data-article-id")); + } } }, getActive: function () { @@ -330,30 +334,11 @@ define(["dojo/_base/declare"], function (declare) { else return 0; }, - scrollByPages: function (page_offset, event) { - const elem = App.isCombinedMode() ? $("headlines-frame") : $("content-insert"); - - const offset = elem.offsetHeight * page_offset * 0.99; - - this.scroll(offset, event); + scrollByPages: function (page_offset) { + App.Scrollable.scrollByPages($("content-insert"), page_offset); }, - scroll: function (offset, event) { - - const elem = App.isCombinedMode() ? $("headlines-frame") : $("content-insert"); - - if (event && event.repeat) { - elem.addClassName("forbid-smooth-scroll"); - window.clearTimeout(this._scroll_reset_timeout); - - this._scroll_reset_timeout = window.setTimeout(() => { - if (elem) elem.removeClassName("forbid-smooth-scroll"); - }, 250) - - } else { - elem.removeClassName("forbid-smooth-scroll"); - } - - elem.scrollTop += offset; + scroll: function (offset) { + App.Scrollable.scroll($("content-insert"), offset); }, mouseIn: function (id) { this.post_under_pointer = id; diff --git a/js/CommonFilters.js b/js/CommonFilters.js index 1538a3fb3..53573b365 100644 --- a/js/CommonFilters.js +++ b/js/CommonFilters.js @@ -2,24 +2,6 @@ /* global __, ngettext */ define(["dojo/_base/declare"], function (declare) { Filters = { - filterDlgCheckRegExp: function(sender) { - const tooltip = dijit.byId("filterDlg_regExp_tip").domNode; - - try { - sender.domNode.removeClassName("invalid"); - sender.domNode.removeClassName("valid"); - - new RegExp("/" + sender.value + "/"); - - sender.domNode.addClassName("valid"); - tooltip.innerText = __("Regular expression, without outer delimiters (i.e. slashes)"); - - } catch (e) { - sender.domNode.addClassName("invalid"); - - tooltip.innerText = e.message; - } - }, filterDlgCheckAction: function(sender) { const action = sender.value; @@ -143,9 +125,6 @@ define(["dojo/_base/declare"], function (declare) { if (dijit.byId("filterNewRuleDlg")) dijit.byId("filterNewRuleDlg").destroyRecursive(); - const query = "backend.php?op=pref-filters&method=newrule&rule=" + - encodeURIComponent(ruleStr); - const rule_dlg = new dijit.Dialog({ id: "filterNewRuleDlg", title: ruleStr ? __("Edit rule") : __("Add rule"), @@ -156,7 +135,15 @@ define(["dojo/_base/declare"], function (declare) { this.hide(); } }, - href: query + content: __('Loading, please wait...'), + }); + + const tmph = dojo.connect(rule_dlg, "onShow", null, function (e) { + dojo.disconnect(tmph); + + xhrPost("backend.php", {op: 'pref-filters', method: 'newrule', rule: ruleStr}, (transport) => { + rule_dlg.attr('content', transport.responseText); + }); }); rule_dlg.show(); @@ -183,7 +170,7 @@ define(["dojo/_base/declare"], function (declare) { rule_dlg.show(); }, - editFilterTest: function(query) { + editFilterTest: function(params) { if (dijit.byId("filterTestDlg")) dijit.byId("filterTestDlg").destroyRecursive(); @@ -195,12 +182,14 @@ define(["dojo/_base/declare"], function (declare) { results: 0, limit: 100, max_offset: 10000, - getTestResults: function (query, offset) { - const updquery = query + "&offset=" + offset + "&limit=" + test_dlg.limit; + getTestResults: function (params, offset) { + params.method = 'testFilterDo'; + params.offset = offset; + params.limit = test_dlg.limit; console.log("getTestResults:" + offset); - xhrPost("backend.php", updquery, (transport) => { + xhrPost("backend.php", params, (transport) => { try { const result = JSON.parse(transport.responseText); @@ -216,9 +205,7 @@ define(["dojo/_base/declare"], function (declare) { console.log(offset + " " + test_dlg.max_offset); for (let i = 0; i < result.length; i++) { - const tmp = new Element("table"); - tmp.innerHTML = result[i]; - dojo.parser.parse(tmp); + const tmp = dojo.create("table", { innerHTML: result[i]}); $("prefFilterTestResultList").innerHTML += tmp.innerHTML; } @@ -227,7 +214,7 @@ define(["dojo/_base/declare"], function (declare) { // get the next batch window.setTimeout(function () { - test_dlg.getTestResults(query, offset + test_dlg.limit); + test_dlg.getTestResults(params, offset + test_dlg.limit); }, 0); } else { @@ -262,11 +249,11 @@ define(["dojo/_base/declare"], function (declare) { }); }, - href: query + href: "backend.php?op=pref-filters&method=testFilterDlg" }); dojo.connect(test_dlg, "onLoad", null, function (e) { - test_dlg.getTestResults(query, 0); + test_dlg.getTestResults(params, 0); }); test_dlg.show(); @@ -296,9 +283,7 @@ define(["dojo/_base/declare"], function (declare) { title: __("Create Filter"), style: "width: 600px", test: function () { - const query = "backend.php?" + dojo.formToQuery("filter_new_form") + "&savemode=test"; - - Filters.editFilterTest(query); + Filters.editFilterTest(dojo.formToObject("filter_new_form")); }, selectRules: function (select) { Lists.select("filterDlg_Matches", select); diff --git a/js/Headlines.js b/js/Headlines.js index 540c400d3..8425dc980 100755 --- a/js/Headlines.js +++ b/js/Headlines.js @@ -8,6 +8,36 @@ define(["dojo/_base/declare"], function (declare) { headlines: [], current_first_id: 0, _scroll_reset_timeout: false, + default_force_previous: false, + default_force_to_top: false, + line_scroll_offset: 120, /* px */ + sticky_header_observer: new IntersectionObserver( + (entries, observer) => { + entries.forEach((entry) => { + const header = entry.target.nextElementSibling; + + if (entry.intersectionRatio == 0) { + header.setAttribute("stuck", "1"); + + } else if (entry.intersectionRatio == 1) { + header.removeAttribute("stuck"); + } + + //console.log(entry.target, header, entry.intersectionRatio); + + }); + }, + {threshold: [0, 1], root: document.querySelector("#headlines-frame")} + ), + unpack_observer: new IntersectionObserver( + (entries, observer) => { + entries.forEach((entry) => { + if (entry.intersectionRatio > 0) + Article.unpack(entry.target); + }); + }, + {threshold: [0], root: document.querySelector("#headlines-frame")} + ), row_observer: new MutationObserver((mutations) => { const modified = []; @@ -40,7 +70,6 @@ define(["dojo/_base/declare"], function (declare) { }); Headlines.updateSelectedPrompt(); - Headlines.updateFloatingTitle(true); if ('requestIdleCallback' in window) window.requestIdleCallback(() => { @@ -49,7 +78,7 @@ define(["dojo/_base/declare"], function (declare) { else Headlines.syncModified(modified); }), - syncModified: function(modified) { + syncModified: function (modified) { const ops = { tmark: [], tpub: [], @@ -62,7 +91,7 @@ define(["dojo/_base/declare"], function (declare) { rescore: {}, }; - modified.each(function(m) { + modified.each(function (m) { if (m.old.marked != m.new.marked) ops.tmark.push(m.id); @@ -118,26 +147,26 @@ define(["dojo/_base/declare"], function (declare) { if (ops.tmark.length != 0) promises.push(xhrPost("backend.php", - { op: "rpc", method: "markSelected", ids: ops.tmark.toString(), cmode: 2})); + {op: "rpc", method: "markSelected", ids: ops.tmark.toString(), cmode: 2})); if (ops.tpub.length != 0) promises.push(xhrPost("backend.php", - { op: "rpc", method: "publishSelected", ids: ops.tpub.toString(), cmode: 2})); + {op: "rpc", method: "publishSelected", ids: ops.tpub.toString(), cmode: 2})); if (ops.read.length != 0) promises.push(xhrPost("backend.php", - { op: "rpc", method: "catchupSelected", ids: ops.read.toString(), cmode: 0})); + {op: "rpc", method: "catchupSelected", ids: ops.read.toString(), cmode: 0})); if (ops.unread.length != 0) promises.push(xhrPost("backend.php", - { op: "rpc", method: "catchupSelected", ids: ops.unread.toString(), cmode: 1})); + {op: "rpc", method: "catchupSelected", ids: ops.unread.toString(), cmode: 1})); const scores = Object.keys(ops.rescore); if (scores.length != 0) { scores.each((score) => { promises.push(xhrPost("backend.php", - { op: "article", method: "setScore", id: ops.rescore[score].toString(), score: score })); + {op: "article", method: "setScore", id: ops.rescore[score].toString(), score: score})); }); } @@ -177,15 +206,24 @@ define(["dojo/_base/declare"], function (declare) { } else if (Article.getActive() != id) { Headlines.select('none'); + + const scroll_position_A = $("RROW-" + id).offsetTop - $("headlines-frame").scrollTop; + Article.setActive(id); if (App.getInitParam("cdm_expanded")) { + if (!in_body) Article.openInNewWindow(id); Headlines.toggleUnread(id, 0); } else { - Article.cdmScrollToId(id); + const scroll_position_B = $("RROW-" + id).offsetTop - $("headlines-frame").scrollTop; + + // this would only work if there's enough space + $("headlines-frame").scrollTop -= scroll_position_A-scroll_position_B; + + Article.cdmMoveToId(id); } } else if (in_body) { @@ -245,36 +283,22 @@ define(["dojo/_base/declare"], function (declare) { Feeds.open({feed: Feeds.getActive(), is_cat: Feeds.activeIsCat(), offset: offset, append: true}); }, - isChildVisible: function (elem, ctr) { - const ctop = ctr.scrollTop; - const cbottom = ctop + ctr.offsetHeight; - - const etop = elem.offsetTop; - const ebottom = etop + elem.offsetHeight; - - return etop >= ctop && ebottom <= cbottom || - etop < ctop && ebottom > ctop || ebottom > cbottom && etop < cbottom - + isChildVisible: function (elem) { + return App.Scrollable.isChildVisible(elem, $("headlines-frame")); }, - firstVisible: function() { + firstVisible: function () { const rows = $$("#headlines-frame > div[id*=RROW]"); - const ctr = $("headlines-frame"); for (let i = 0; i < rows.length; i++) { const row = rows[i]; - if (this.isChildVisible(row, ctr)) { + if (this.isChildVisible(row)) { return row.getAttribute("data-article-id"); } } }, scrollHandler: function (/*event*/) { try { - Headlines.unpackVisible(); - - if (App.isCombinedMode()) - Headlines.updateFloatingTitle(); - if (!Feeds.infscroll_disabled && !Feeds.infscroll_in_progress) { const hsp = $("headlines-spacer"); const container = $("headlines-frame"); @@ -311,82 +335,25 @@ define(["dojo/_base/declare"], function (declare) { console.warn("scrollHandler", e); } }, - updateFloatingTitle: function (status_only) { - if (!App.isCombinedMode()/* || !App.getInitParam("cdm_expanded")*/) return; - - const safety_offset = 120; /* px, needed for firefox */ - const hf = $("headlines-frame"); - const elems = $$("#headlines-frame > div[id*=RROW]"); - const ft = $("floatingTitle"); - - for (let i = 0; i < elems.length; i++) { - const row = elems[i]; - - if (row && row.offsetTop + row.offsetHeight > hf.scrollTop + safety_offset) { - - const header = row.select(".header")[0]; - const id = row.getAttribute("data-article-id"); - - if (status_only || id != ft.getAttribute("data-article-id")) { - if (id != ft.getAttribute("data-article-id")) { - - ft.setAttribute("data-article-id", id); - ft.innerHTML = header.innerHTML; - - ft.select(".dijitCheckBox")[0].outerHTML = "<i class=\"material-icons icon-anchor\" onclick=\"Article.cdmScrollToId(" + id + ", true)\">expand_more</i>"; - - this.initFloatingMenu(); - - } - - if (row.hasClassName("Unread")) - ft.addClassName("Unread"); - else - ft.removeClassName("Unread"); - - if (row.hasClassName("marked")) - ft.addClassName("marked"); - else - ft.removeClassName("marked"); - - if (row.hasClassName("published")) - ft.addClassName("published"); - else - ft.removeClassName("published"); - - PluginHost.run(PluginHost.HOOK_FLOATING_TITLE, row); - } - - if (hf.scrollTop - row.offsetTop <= header.offsetHeight + safety_offset) - ft.fade({duration: 0.2}); - else - ft.appear({duration: 0.2}); - - return; - } - } + objectById: function (id) { + return this.headlines[id]; }, - unpackVisible: function () { - if (!App.isCombinedMode() || !App.getInitParam("cdm_expanded")) return; + setCommonClasses: function () { + $("headlines-frame").removeClassName("cdm"); + $("headlines-frame").removeClassName("normal"); - const rows = $$("#headlines-frame div[id*=RROW][data-content]"); - const threshold = $("headlines-frame").scrollTop + $("headlines-frame").offsetHeight + 600; + $("headlines-frame").addClassName(App.isCombinedMode() ? "cdm" : "normal"); - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; + // for floating title because it's placed outside of headlines-frame + $("main").removeClassName("expandable"); + $("main").removeClassName("expanded"); - if (row.offsetTop <= threshold) { - Article.unpack(row); - } else { - break; - } - } - }, - objectById: function (id){ - return this.headlines[id]; + if (App.isCombinedMode()) + $("main").addClassName(App.getInitParam("cdm_expanded") ? " expanded" : " expandable"); }, - renderAgain: function() { + renderAgain: function () { // TODO: wrap headline elements into a knockoutjs model to prevent all this stuff + Headlines.setCommonClasses(); $$("#headlines-frame > div[id*=RROW]").each((row) => { const id = row.getAttribute("data-article-id"); @@ -399,19 +366,27 @@ define(["dojo/_base/declare"], function (declare) { if (hl.active) { new_row.addClassName("active"); + Article.unpack(new_row); if (App.isCombinedMode()) - Article.cdmScrollToId(id); + Article.cdmMoveToId(id, {noscroll: true}); else Article.view(id); } if (hl.selected) this.select("all", id); - - Article.unpack(new_row); - } }); + + $$(".cdm .header-sticky-guard").each((e) => { + this.sticky_header_observer.observe(e) + }); + + if (App.getInitParam("cdm_expanded")) + $$("#headlines-frame > div[id*=RROW].cdm").each((e) => { + this.unpack_observer.observe(e) + }); + }, render: function (headlines, hl) { let row = null; @@ -453,7 +428,7 @@ define(["dojo/_base/declare"], function (declare) { data-article-title="${escapeHtml(hl.title)}" onmouseover="Article.mouseIn(${hl.id})" onmouseout="Article.mouseOut(${hl.id})"> - + <div class="header-sticky-guard"></div> <div class="header"> <div class="left"> <input dojoType="dijit.form.CheckBox" type="checkbox" onclick="Headlines.onRowChecked(this)" class='rchk'> @@ -558,7 +533,7 @@ define(["dojo/_base/declare"], function (declare) { return tmp.firstChild; }, - updateCurrentUnread: function() { + updateCurrentUnread: function () { if ($("feed_current_unread")) { const feed_unread = Feeds.getUnread(Feeds.getActive(), Feeds.activeIsCat()); @@ -598,34 +573,18 @@ define(["dojo/_base/declare"], function (declare) { Feeds.infscroll_disabled = parseInt(headlines_count) != 30; console.log('infscroll_disabled=', Feeds.infscroll_disabled); - // TODO: the below needs to be applied again when switching expanded/expandable on the fly - // via hotkeys, not just on feed load - - $("headlines-frame").removeClassName("cdm"); - $("headlines-frame").removeClassName("normal"); - - $("headlines-frame").addClassName(App.isCombinedMode() ? "cdm" : "normal"); + // also called in renderAgain() after view mode switch + Headlines.setCommonClasses(); $("headlines-frame").setAttribute("is-vfeed", reply['headlines']['is_vfeed'] ? 1 : 0); - // for floating title because it's placed outside of headlines-frame - $("main").removeClassName("expandable"); - $("main").removeClassName("expanded"); - - if (App.isCombinedMode()) - $("main").addClassName(App.getInitParam("cdm_expanded") ? " expanded" : " expandable"); - Article.setActive(0); try { $("headlines-frame").removeClassName("smooth-scroll"); $("headlines-frame").scrollTop = 0; $("headlines-frame").addClassName("smooth-scroll"); - - Element.hide("floatingTitle"); - $("floatingTitle").setAttribute("data-article-id", 0); - $("floatingTitle").innerHTML = ""; } catch (e) { console.warn(e); } @@ -736,6 +695,15 @@ define(["dojo/_base/declare"], function (declare) { } } + $$(".cdm .header-sticky-guard").each((e) => { + this.sticky_header_observer.observe(e) + }); + + if (App.getInitParam("cdm_expanded")) + $$("#headlines-frame > div[id*=RROW].cdm").each((e) => { + this.unpack_observer.observe(e) + }); + } else { console.error("Invalid object received: " + transport.responseText); dijit.byId("headlines-frame").attr('content', "<div class='whiteBox'>" + @@ -837,33 +805,30 @@ define(["dojo/_base/declare"], function (declare) { move: function (mode, params) { params = params || {}; - const noscroll = params.noscroll || false; - const noexpand = params.noexpand || false; - const event = params.event; - - const rows = Headlines.getLoaded(); + const no_expand = params.no_expand || false; + const force_previous = params.force_previous || this.default_force_previous; + const force_to_top = params.force_to_top || this.default_force_to_top; let prev_id = false; let next_id = false; + let current_id = Article.getActive(); - const active_row = $("RROW-" + Article.getActive()); - - if (!active_row) { - Article.setActive(0); - } - - if (!Article.getActive() || (active_row && !Headlines.isChildVisible(active_row, $("headlines-frame")))) { - next_id = Headlines.firstVisible(); - prev_id = next_id; + if (!Headlines.isChildVisible($("RROW-" + current_id))) { + console.log('active article is obscured, resetting to first visible...'); + current_id = Headlines.firstVisible(); + prev_id = current_id; + next_id = current_id; } else { + const rows = Headlines.getLoaded(); + for (let i = 0; i < rows.length; i++) { - if (rows[i] == Article.getActive()) { + if (rows[i] == current_id) { // Account for adjacent identical article ids. if (i > 0) prev_id = rows[i - 1]; for (let j = i + 1; j < rows.length; j++) { - if (rows[j] != Article.getActive()) { + if (rows[j] != current_id) { next_id = rows[j]; break; } @@ -873,51 +838,39 @@ define(["dojo/_base/declare"], function (declare) { } } - console.log("cur: " + Article.getActive() + " next: " + next_id); + console.log("cur: " + current_id + " next: " + next_id + " prev:" + prev_id); if (mode === "next") { - if (next_id || Article.getActive()) { + if (next_id) { if (App.isCombinedMode()) { - - //const row = $("RROW-" + Article.getActive()); - const ctr = $("headlines-frame"); - - if (!noscroll) { - Article.scroll(ctr.offsetHeight / 2, event); - } else if (next_id) { + window.requestAnimationFrame(() => { Article.setActive(next_id); - Article.cdmScrollToId(next_id, true, event); - } - - } else if (next_id) { - Headlines.correctHeadlinesOffset(next_id); - Article.view(next_id, noexpand); + Article.cdmMoveToId(next_id, {force_to_top: force_to_top}); + }); + } else { + Article.view(next_id, no_expand); } } - } - - if (mode === "prev") { - if (prev_id || Article.getActive()) { + } else if (mode === "prev") { + if (prev_id || current_id) { if (App.isCombinedMode()) { + window.requestAnimationFrame(() => { + const row = $("RROW-" + current_id); + const ctr = $("headlines-frame"); + const delta_px = Math.round(row.offsetTop) - Math.round(ctr.scrollTop); - const row = $("RROW-" + Article.getActive()); - //const prev_row = $("RROW-" + prev_id); - const ctr = $("headlines-frame"); + console.log('moving back, delta_px', delta_px); - if (!noscroll) { - Article.scroll(-ctr.offsetHeight / 2, event); - } else { - if (row && Math.round(row.offsetTop) < Math.round(ctr.scrollTop)) { - Article.cdmScrollToId(Article.getActive(), noscroll, event); + if (!force_previous && row && delta_px < -8) { + Article.setActive(current_id); + Article.cdmMoveToId(current_id, {force_to_top: force_to_top}); } else if (prev_id) { Article.setActive(prev_id); - Article.cdmScrollToId(prev_id, noscroll, event); + Article.cdmMoveToId(prev_id, {force_to_top: force_to_top}); } - } - + }); } else if (prev_id) { - Headlines.correctHeadlinesOffset(prev_id); - Article.view(prev_id, noexpand); + Article.view(prev_id, no_expand); } } } @@ -1254,7 +1207,7 @@ define(["dojo/_base/declare"], function (declare) { eval(elem.value); elem.attr('value', 'false'); }, - correctHeadlinesOffset: function (id) { + scrollToArticleId: function (id) { const container = $("headlines-frame"); const row = $("RROW-" + id); @@ -1274,20 +1227,6 @@ define(["dojo/_base/declare"], function (declare) { container.scrollTop = row.offsetTop + row.offsetHeight - viewport; } }, - initFloatingMenu: function () { - if (!dijit.byId("floatingMenu")) { - - const menu = new dijit.Menu({ - id: "floatingMenu", - selector: ".hlMenuAttach", - targetNodeIds: ["floatingTitle"] - }); - - this.headlinesMenuCommon(menu); - - menu.startup(); - } - }, headlinesMenuCommon: function (menu) { menu.addChild(new dijit.MenuItem({ @@ -1416,21 +1355,11 @@ define(["dojo/_base/declare"], function (declare) { } }, - scrollByPages: function (offset, event) { - const elem = $("headlines-frame"); - - if (event && event.repeat) { - elem.addClassName("forbid-smooth-scroll"); - window.clearTimeout(this._scroll_reset_timeout); - - this._scroll_reset_timeout = window.setTimeout(() => { - if (elem) elem.removeClassName("forbid-smooth-scroll"); - }, 250) - } else { - elem.removeClassName("forbid-smooth-scroll"); - } - - elem.scrollTop += elem.offsetHeight * offset * 0.99; + scrollByPages: function (page_offset) { + App.Scrollable.scrollByPages($("headlines-frame"), page_offset); + }, + scroll: function (offset) { + App.Scrollable.scroll($("headlines-frame"), offset); }, initHeadlinesMenu: function () { if (!dijit.byId("headlinesMenu")) { diff --git a/js/PrefFilterTree.js b/js/PrefFilterTree.js index a7c7464fb..5fe0714c0 100644 --- a/js/PrefFilterTree.js +++ b/js/PrefFilterTree.js @@ -149,9 +149,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], functio style: "width: 600px", test: function () { - const query = "backend.php?" + dojo.formToQuery("filter_edit_form") + "&savemode=test"; - - Filters.editFilterTest(query); + Filters.editFilterTest(dojo.formToObject("filter_edit_form")); }, selectRules: function (select) { Lists.select("filterDlg_Matches", select); diff --git a/js/form/ValidationTextArea.js b/js/form/ValidationTextArea.js new file mode 100644 index 000000000..a7993f716 --- /dev/null +++ b/js/form/ValidationTextArea.js @@ -0,0 +1,33 @@ +// https://stackoverflow.com/questions/19317258/how-to-use-dijit-textarea-validation-dojo-1-9 + +define(["dojo/_base/declare", "dojo/_base/lang", "dijit/form/SimpleTextarea", "dijit/form/ValidationTextBox"], + function(declare, lang, SimpleTextarea, ValidationTextBox) { + + return declare('fox.form.ValidationTextArea', [SimpleTextarea, ValidationTextBox], { + constructor: function(params){ + this.constraints = {}; + this.baseClass += ' dijitValidationTextArea'; + }, + templateString: "<textarea ${!nameAttrSetting} data-dojo-attach-point='focusNode,containerNode,textbox' autocomplete='off'></textarea>", + validator: function(value, constraints) { + //console.log(this, value, constraints); + + if (this.required && this._isEmpty(value)) + return false; + + if (this.validregexp) { + try { + new RegExp("/" + value + "/"); + } catch (e) { + return false; + } + } + + return value.match(new RegExp(this._computeRegexp(constraints))); + + /*return (new RegExp("^(?:" + this._computeRegexp(constraints) + ")"+(this.required?"":"?")+"$",["m"])).test(value) && + (!this.required || !this._isEmpty(value)) && + (this._isEmpty(value) || this.parse(value, constraints) !== undefined); // Boolean*/ + } + }) + }); diff --git a/js/prefs.js b/js/prefs.js index 944e49258..96fcbd948 100755 --- a/js/prefs.js +++ b/js/prefs.js @@ -55,6 +55,7 @@ require(["dojo/_base/kernel", "fox/PrefFilterTree", "fox/PrefLabelTree", "fox/Toolbar", + "fox/form/ValidationTextArea", "fox/form/Select", "fox/form/ComboButton", "fox/form/DropDownButton"], function (dojo, declare, ready, parser, AppBase) { diff --git a/js/tt-rss.js b/js/tt-rss.js index d45dd5748..d8818420c 100644 --- a/js/tt-rss.js +++ b/js/tt-rss.js @@ -56,6 +56,7 @@ require(["dojo/_base/kernel", "fox/FeedStoreModel", "fox/FeedTree", "fox/Toolbar", + "fox/form/ValidationTextArea", "fox/form/Select", "fox/form/ComboButton", "fox/form/DropDownButton"], function (dojo, declare, ready, parser, AppBase) { @@ -210,8 +211,8 @@ require(["dojo/_base/kernel", if (event.target.nodeName == "INPUT" || event.target.nodeName == "TEXTAREA") return; // Arrow buttons and escape are not reported via keypress, handle them via keydown. - // escape = 27, left = 37, up = 38, right = 39, down = 40, pgup = 33, pgdn = 34 - if (event.type == "keydown" && event.which != 27 && (event.which < 33 || event.which > 40)) return; + // escape = 27, left = 37, up = 38, right = 39, down = 40, pgup = 33, pgdn = 34, insert = 45, delete = 46 + if (event.type == "keydown" && event.which != 27 && (event.which < 33 || event.which > 46)) return; const action_name = App.keyeventToAction(event); @@ -284,26 +285,35 @@ require(["dojo/_base/kernel", if (rv) Feeds.open({feed: rv[0], is_cat: rv[1], delayed: true}) }; this.hotkey_actions["next_article_or_scroll"] = function (event) { - Headlines.move('next', {event: event}); + if (App.isCombinedMode()) + Headlines.scroll(Headlines.line_scroll_offset, event); + else + Headlines.move('next'); }; this.hotkey_actions["prev_article_or_scroll"] = function (event) { - Headlines.move('prev', {event: event}); + if (App.isCombinedMode()) + Headlines.scroll(-Headlines.line_scroll_offset, event); + else + Headlines.move('prev'); }; - this.hotkey_actions["next_article_noscroll"] = function (event) { - Headlines.move('next', {noscroll: true, event: event}); + this.hotkey_actions["next_article_noscroll"] = function () { + Headlines.move('next'); }; - this.hotkey_actions["prev_article_noscroll"] = function (event) { - Headlines.move('prev', {noscroll: true, event: event}); + this.hotkey_actions["prev_article_noscroll"] = function () { + Headlines.move('prev'); }; - this.hotkey_actions["next_article_noexpand"] = function (event) { - Headlines.move('next', {noscroll: true, noexpand: true, event: event}); + this.hotkey_actions["next_article_noexpand"] = function () { + Headlines.move('next', {no_expand: true}); }; - this.hotkey_actions["prev_article_noexpand"] = function (event) { - Headlines.move('prev', {noscroll: true, noexpand: true, event: event}); + this.hotkey_actions["prev_article_noexpand"] = function () { + Headlines.move('prev', {no_expand: true}); }; this.hotkey_actions["search_dialog"] = function () { Feeds.search(); }; + this.hotkey_actions["cancel_search"] = function () { + Feeds.cancelSearch(); + }; this.hotkey_actions["toggle_mark"] = function () { Headlines.selectionToggleMarked(); }; @@ -331,28 +341,34 @@ require(["dojo/_base/kernel", Headlines.catchupRelativeTo(0); }; this.hotkey_actions["article_scroll_down"] = function (event) { - const ctr = App.isCombinedMode() ? $("headlines-frame") : $("content-insert"); - - if (ctr) - Article.scroll(ctr.offsetHeight / 2, event); + if (App.isCombinedMode()) + Headlines.scroll(Headlines.line_scroll_offset, event); + else + Article.scroll(Headlines.line_scroll_offset, event); }; this.hotkey_actions["article_scroll_up"] = function (event) { - const ctr = App.isCombinedMode() ? $("headlines-frame") : $("content-insert"); - - if (ctr) - Article.scroll(-ctr.offsetHeight / 2, event); + if (App.isCombinedMode()) + Headlines.scroll(-Headlines.line_scroll_offset, event); + else + Article.scroll(-Headlines.line_scroll_offset, event); }; - this.hotkey_actions["next_article_page"] = function (event) { + this.hotkey_actions["next_headlines_page"] = function (event) { Headlines.scrollByPages(1, event); }; - this.hotkey_actions["prev_article_page"] = function (event) { + this.hotkey_actions["prev_headlines_page"] = function (event) { Headlines.scrollByPages(-1, event); }; this.hotkey_actions["article_page_down"] = function (event) { - Article.scrollByPages(1, event); + if (App.isCombinedMode()) + Headlines.scrollByPages(1, event); + else + Article.scrollByPages(1, event); }; this.hotkey_actions["article_page_up"] = function (event) { - Article.scrollByPages(-1, event); + if (App.isCombinedMode()) + Headlines.scrollByPages(-1, event); + else + Article.scrollByPages(-1, event); }; this.hotkey_actions["close_article"] = function () { if (App.isCombinedMode()) { |