summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/App.js110
-rw-r--r--js/CommonDialogs.js30
-rw-r--r--js/CommonFilters.js5
-rwxr-xr-xjs/FeedTree.js9
-rw-r--r--js/Feeds.js19
-rwxr-xr-xjs/Headlines.js152
-rw-r--r--js/PluginHost.js12
-rw-r--r--js/PrefHelpers.js9
-rw-r--r--js/PrefUsers.js2
-rwxr-xr-xjs/common.js3
-rwxr-xr-xjs/form/Select.js64
11 files changed, 324 insertions, 91 deletions
diff --git a/js/App.js b/js/App.js
index 0afcb0b77..262988135 100644
--- a/js/App.js
+++ b/js/App.js
@@ -17,6 +17,9 @@ const App = {
hotkey_actions: {},
is_prefs: false,
LABEL_BASE_INDEX: -1024,
+ UserAccessLevels: {
+ ACCESS_LEVEL_READONLY: -1
+ },
_translations: {},
Hash: {
get: function() {
@@ -76,10 +79,15 @@ const App = {
</select>
`
},
- select_hash: function(name, value, values = {}, attributes = {}, id = "") {
+ select_hash: function(name, value, values = {}, attributes = {}, id = "", params = {}) {
+ let keys = Object.keys(values);
+
+ if (params.numeric_sort)
+ keys = keys.sort((a,b) => a - b);
+
return `
<select name="${name}" dojoType="fox.form.Select" id="${App.escapeHtml(id)}" ${this.attributes_to_string(attributes)}>
- ${Object.keys(values).map((vk) =>
+ ${keys.map((vk) =>
`<option ${vk == value ? 'selected="selected"' : ''} value="${App.escapeHtml(vk)}">${App.escapeHtml(values[vk])}</option>`
).join("")}
</select>
@@ -278,7 +286,33 @@ const App = {
},
isCombinedMode: function() {
- return this.getInitParam("combined_display_mode");
+ return !!this.getInitParam("combined_display_mode");
+ },
+ setCombinedMode: function(combined) {
+ const value = combined ? "true" : "false";
+
+ xhr.post("backend.php", {op: "rpc", method: "setpref", key: "COMBINED_DISPLAY_MODE", value: value}, () => {
+ this.setInitParam("combined_display_mode",
+ !this.getInitParam("combined_display_mode"));
+
+ Article.close();
+ Headlines.renderAgain();
+ })
+ },
+ isExpandedMode: function() {
+ return !!this.getInitParam("cdm_expanded");
+ },
+ setExpandedMode: function(expand) {
+ if (App.isCombinedMode()) {
+ const value = expand ? "true" : "false";
+
+ xhr.post("backend.php", {op: "rpc", method: "setpref", key: "CDM_EXPANDED", value: value}, () => {
+ this.setInitParam("cdm_expanded", !this.getInitParam("cdm_expanded"));
+ Headlines.renderAgain();
+ });
+ } else {
+ alert(__("This function is only available in combined mode."));
+ }
},
getActionByHotkeySequence: function(sequence) {
const hotkeys_map = this.getInitParam("hotkeys");
@@ -789,7 +823,7 @@ const App = {
this.setLoadingProgress(50);
this._widescreen_mode = this.getInitParam("widescreen");
- this.setWidescreen(this._widescreen_mode);
+ this.setWideScreenMode(this.isWideScreenMode(), true);
Headlines.initScrollHandler();
@@ -812,6 +846,10 @@ const App = {
App.updateRuntimeInfo();
}, 60 * 1000)
+ if (App.getInitParam("safe_mode") && this.isPrefs()) {
+ CommonDialogs.safeModeWarning();
+ }
+
console.log("second stage ok");
},
@@ -870,7 +908,22 @@ const App = {
}
}
},
- setWidescreen: function(wide) {
+ isWideScreenMode: function() {
+ return !!this._widescreen_mode;
+ },
+ setWideScreenMode: function(wide, quiet = false) {
+
+ if (this.isCombinedMode() && !quiet) {
+ alert(__("Widescreen is not available in combined mode."));
+ return;
+ }
+
+ // reset stored sizes because geometry changed
+ Cookie.set("ttrss_ci_width", 0);
+ Cookie.set("ttrss_ci_height", 0);
+
+ this._widescreen_mode = wide;
+
const article_id = Article.getActive();
const headlines_frame = App.byId("headlines-frame");
const content_insert = dijit.byId("content-insert");
@@ -1189,39 +1242,16 @@ const App = {
}
};
this.hotkey_actions["toggle_widescreen"] = () => {
- if (!this.isCombinedMode()) {
- this._widescreen_mode = !this._widescreen_mode;
-
- // reset stored sizes because geometry changed
- Cookie.set("ttrss_ci_width", 0);
- Cookie.set("ttrss_ci_height", 0);
-
- this.setWidescreen(this._widescreen_mode);
- } else {
- alert(__("Widescreen is not available in combined mode."));
- }
+ this.setWideScreenMode(!this.isWideScreenMode());
};
this.hotkey_actions["help_dialog"] = () => {
this.hotkeyHelp();
};
this.hotkey_actions["toggle_combined_mode"] = () => {
- const value = this.isCombinedMode() ? "false" : "true";
-
- xhr.post("backend.php", {op: "rpc", method: "setpref", key: "COMBINED_DISPLAY_MODE", value: value}, () => {
- this.setInitParam("combined_display_mode",
- !this.getInitParam("combined_display_mode"));
-
- Article.close();
- Headlines.renderAgain();
- })
+ App.setCombinedMode(!App.isCombinedMode());
};
this.hotkey_actions["toggle_cdm_expanded"] = () => {
- const value = this.getInitParam("cdm_expanded") ? "false" : "true";
-
- xhr.post("backend.php", {op: "rpc", method: "setpref", key: "CDM_EXPANDED", value: value}, () => {
- this.setInitParam("cdm_expanded", !this.getInitParam("cdm_expanded"));
- Headlines.renderAgain();
- });
+ App.setExpandedMode(!App.isExpandedMode());
};
this.hotkey_actions["article_span_grid"] = () => {
Article.cdmToggleGridSpan(Article.getActive());
@@ -1282,18 +1312,14 @@ const App = {
Feeds.toggleUnread();
break;
case "qmcToggleWidescreen":
- if (!this.isCombinedMode()) {
- this._widescreen_mode = !this._widescreen_mode;
-
- // reset stored sizes because geometry changed
- Cookie.set("ttrss_ci_width", 0);
- Cookie.set("ttrss_ci_height", 0);
-
- this.setWidescreen(this._widescreen_mode);
- } else {
- alert(__("Widescreen is not available in combined mode."));
- }
+ App.setWideScreenMode(!App.isWideScreenMode());
break;
+ case "qmcToggleCombined":
+ App.setCombinedMode(!App.isCombinedMode());
+ break;
+ case "qmcToggleExpanded":
+ App.setExpandedMode(!App.isExpandedMode());
+ break;
case "qmcHKhelp":
this.hotkeyHelp()
break;
diff --git a/js/CommonDialogs.js b/js/CommonDialogs.js
index a68dc8068..4cfc6ec70 100644
--- a/js/CommonDialogs.js
+++ b/js/CommonDialogs.js
@@ -11,6 +11,21 @@ const CommonDialogs = {
const dialog = dijit.byId("infoBox");
if (dialog) dialog.hide();
},
+ safeModeWarning: function() {
+ const dialog = new fox.SingleUseDialog({
+ title: __("Safe mode"),
+ content: `<div class='alert alert-info'>
+ ${__('Tiny Tiny RSS is running in safe mode. All themes and plugins are disabled. You will need to log out and back in to disable it.')}
+ </div>
+ <footer class='text-center'>
+ <button dojoType='dijit.form.Button' type='submit' class='alt-primary'>
+ ${__('Close this window')}
+ </button>
+ </footer>`
+ });
+
+ dialog.show();
+ },
subscribeToFeed: function() {
xhr.json("backend.php",
{op: "feeds", method: "subscribeToFeed"},
@@ -131,6 +146,9 @@ const CommonDialogs = {
console.log(rc);
switch (parseInt(rc['code'])) {
+ case 0:
+ dialog.show_error(__("You are already subscribed to this feed."));
+ break;
case 1:
dialog.hide();
Notify.info(__("Subscribed to %s").replace("%s", feed_url));
@@ -175,8 +193,11 @@ const CommonDialogs = {
case 6:
dialog.show_error(__("XML validation failed: %s").replace("%s", rc['message']));
break;
- case 0:
- dialog.show_error(__("You are already subscribed to this feed."));
+ case 7:
+ dialog.show_error(__("Error while creating feed database entry."));
+ break;
+ case 8:
+ dialog.show_error(__("You are not allowed to perform this operation."));
break;
}
@@ -451,6 +472,7 @@ const CommonDialogs = {
xhr.json("backend.php", {op: "pref-feeds", method: "editfeed", id: feed_id}, (reply) => {
const feed = reply.feed;
+ const is_readonly = reply.user.access_level == App.UserAccessLevels.ACCESS_LEVEL_READONLY;
// for unsub prompt
dialog.feed_title = feed.title;
@@ -524,7 +546,9 @@ const CommonDialogs = {
<fieldset>
<label>${__("Update interval:")}</label>
- ${App.FormFields.select_hash("update_interval", feed.update_interval, reply.intervals.update)}
+ ${App.FormFields.select_hash("update_interval", is_readonly ? -1 : feed.update_interval,
+ reply.intervals.update,
+ {disabled: is_readonly})}
</fieldset>
<fieldset>
<label>${__('Article purging:')}</label>
diff --git a/js/CommonFilters.js b/js/CommonFilters.js
index 8a20480f0..1a0ce1606 100644
--- a/js/CommonFilters.js
+++ b/js/CommonFilters.js
@@ -16,7 +16,8 @@ const Filters = {
ACTION_SCORE: 6,
ACTION_LABEL: 7,
ACTION_PLUGIN: 9,
- PARAM_ACTIONS: [4, 6, 7, 9],
+ ACTION_REMOVE_TAG: 10,
+ PARAM_ACTIONS: [4, 6, 7, 9, 10],
filter_info: {},
test: function() {
const test_dialog = new fox.SingleUseDialog({
@@ -397,6 +398,8 @@ const Filters = {
xhr.post("backend.php", this.attr('value'), () => {
dialog.hide();
+ Notify.close();
+
const tree = dijit.byId("filterTree");
if (tree) tree.reload();
});
diff --git a/js/FeedTree.js b/js/FeedTree.js
index e0c44e2b5..f6c44a71e 100755
--- a/js/FeedTree.js
+++ b/js/FeedTree.js
@@ -102,6 +102,15 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dojo/_base/array", "dojo/co
}}));
menu.addChild(new dijit.MenuItem({
+ label: __("Open site"),
+ onClick: function() {
+ App.postOpenWindow("backend.php", {op: "feeds", method: "opensite",
+ feed_id: this.getParent().row_id, csrf_token: __csrf_token});
+ }}));
+
+ menu.addChild(new dijit.MenuSeparator());
+
+ menu.addChild(new dijit.MenuItem({
label: __("Debug feed"),
onClick: function() {
/* global __csrf_token */
diff --git a/js/Feeds.js b/js/Feeds.js
index 27586ab13..714eb77d2 100644
--- a/js/Feeds.js
+++ b/js/Feeds.js
@@ -278,21 +278,14 @@ const Feeds = {
}
if (App.getInitParam("safe_mode")) {
- const dialog = new fox.SingleUseDialog({
- title: __("Safe mode"),
- content: `<div class='alert alert-info'>
- ${__('Tiny Tiny RSS is running in safe mode. All themes and plugins are disabled. You will need to log out and back in to disable it.')}
- </div>
- <footer class='text-center'>
- <button dojoType='dijit.form.Button' type='submit' class='alt-primary'>
- ${__('Close this window')}
- </button>
- </footer>`
- });
-
- dialog.show();
+ /* global CommonDialogs */
+ CommonDialogs.safeModeWarning();
}
+ dojo.connect(dijit.byId("main-catchup-dropdown"), 'onItemClick',
+ (item) => Feeds.catchupCurrent(item.option.value)
+ );
+
// bw_limit disables timeout() so we request initial counters separately
if (App.getInitParam("bw_limit")) {
this.requestCounters();
diff --git a/js/Headlines.js b/js/Headlines.js
index d01993838..2be3cd697 100755
--- a/js/Headlines.js
+++ b/js/Headlines.js
@@ -178,7 +178,7 @@ const Headlines = {
if (scores.length != 0) {
scores.forEach((score) => {
promises.push(xhr.post("backend.php",
- {op: "article", method: "setScore", "ids[]": ops.rescore[score].toString(), score: score}));
+ {op: "article", method: "setScore", "ids[]": ops.rescore[score], score: score}));
});
}
@@ -490,6 +490,7 @@ const Headlines = {
id="RROW-${hl.id}"
data-article-id="${hl.id}"
data-orig-feed-id="${hl.feed_id}"
+ data-orig-feed-title="${App.escapeHtml(hl.feed_title)}"
data-is-packed="1"
data-content="${App.escapeHtml(hl.content)}"
data-rendered-enclosures="${App.escapeHtml(Article.renderEnclosures(hl.enclosures))}"
@@ -514,10 +515,8 @@ const Headlines = {
${hl.cdm_excerpt ? hl.cdm_excerpt : ""}
</span>
- <div class="feed">
- <a href="#" style="background-color: ${hl.feed_bg_color}"
- onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title}</a>
- </div>
+ <a href="#" class="feed vfeedMenuAttach" style="background-color: ${hl.feed_bg_color}" data-feed-id="${hl.feed_id}"
+ onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title}</a>
<span class="updated" title="${hl.imported}">${hl.updated}</span>
@@ -566,6 +565,7 @@ const Headlines = {
row = `<div class="hl ${row_class} ${Article.getScoreClass(hl.score)}"
id="RROW-${hl.id}"
data-orig-feed-id="${hl.feed_id}"
+ data-orig-feed-title="${App.escapeHtml(hl.feed_title)}"
data-article-id="${hl.id}"
data-score="${hl.score}"
data-article-title="${App.escapeHtml(hl.title)}"
@@ -584,9 +584,9 @@ const Headlines = {
${Article.renderLabels(hl.id, hl.labels)}
</span>
</div>
- <span class="feed">
- <a style="background : ${hl.feed_bg_color}" href="#" onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title}</a>
- </span>
+ <span class="feed vfeedMenuAttach" data-feed-id="${hl.feed_id}">
+ <a style="background : ${hl.feed_bg_color}" href="#" onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title}</a>
+ </span>
<div title="${hl.imported}">
<span class="updated">${hl.updated}</span>
</div>
@@ -626,6 +626,12 @@ const Headlines = {
const search_query = Feeds._search_query ? Feeds._search_query.query : "";
const target = dijit.byId('toolbar-headlines');
+ // TODO: is this needed? destroyDescendants() below might take care of it (?)
+ if (this._headlinesSelectClickHandle)
+ dojo.disconnect(this._headlinesSelectClickHandle);
+
+ target.destroyDescendants();
+
if (tb && typeof tb == 'object') {
target.attr('innerHTML',
`
@@ -646,27 +652,37 @@ const Headlines = {
</span>
<span class='right'>
<span id='selected_prompt'></span>
- <div class='select-articles-dropdown' dojoType='fox.form.DropDownButton' title='"${__('Select articles')}'>
- <span>${__("Select...")}</span>
- <div dojoType='dijit.Menu' style='display: none;'>
- <div dojoType='dijit.MenuItem' onclick='Headlines.select("all")'>${__('All')}</div>
- <div dojoType='dijit.MenuItem' onclick='Headlines.select("unread")'>${__('Unread')}</div>
- <div dojoType='dijit.MenuItem' onclick='Headlines.select("invert")'>${__('Invert')}</div>
- <div dojoType='dijit.MenuItem' onclick='Headlines.select("none")'>${__('None')}</div>
- <div dojoType='dijit.MenuSeparator'></div>
- <div dojoType='dijit.MenuItem' onclick='Headlines.selectionToggleUnread()'>${__('Toggle unread')}</div>
- <div dojoType='dijit.MenuItem' onclick='Headlines.selectionToggleMarked()'>${__('Toggle starred')}</div>
- <div dojoType='dijit.MenuItem' onclick='Headlines.selectionTogglePublished()'>${__('Toggle published')}</div>
- <div dojoType='dijit.MenuSeparator'></div>
- <div dojoType='dijit.MenuItem' onclick='Headlines.catchupSelection()'>${__('Mark as read')}</div>
- <div dojoType='dijit.MenuItem' onclick='Article.selectionSetScore()'>${__('Set score')}</div>
- ${tb.plugin_menu_items}
+
+ <select class='select-articles-dropdown'
+ id='headlines-select-articles-dropdown'
+ data-prevent-value-change="true"
+ data-dropdown-skip-first="true"
+ dojoType="fox.form.Select"
+ title="${__('Show articles')}">
+ <option value='' selected="selected">${__("Select...")}</option>
+ <option value='headlines_select_all'>${__('All')}</option>
+ <option value='headlines_select_unread'>${__('Unread')}</option>
+ <option value='headlines_select_invert'>${__('Invert')}</option>
+ <option value='headlines_select_none'>${__('None')}</option>
+ <option></option>
+ <option value='headlines_selectionToggleUnread'>${__('Toggle unread')}</option>
+ <option value='headlines_selectionToggleMarked'>${__('Toggle starred')}</option>
+ <option value='headlines_selectionTogglePublished'>${__('Toggle published')}</option>
+ <option></option>
+ <option value='headlines_catchupSelection'>${__('Mark as read')}</option>
+ <option value='article_selectionSetScore'>${__('Set score')}</option>
+ ${tb.plugin_menu_items != '' ?
+ `
+ <option></option>
+ ${tb.plugin_menu_items}
+ ` : ''}
${headlines.id === 0 && !headlines.is_cat ?
`
- <div dojoType='dijit.MenuSeparator'></div>
- <div dojoType='dijit.MenuItem' class='text-error' onclick='Headlines.deleteSelection()'>${__('Delete permanently')}</div>
+ <option></option>
+ <option class='text-error' value='headlines_deleteSelection'>${__('Delete permanently')}</option>
` : ''}
- </div>
+ </select>
+
${tb.plugin_buttons}
</span>
`);
@@ -675,6 +691,48 @@ const Headlines = {
}
dojo.parser.parse(target.domNode);
+
+ this._headlinesSelectClickHandle = dojo.connect(dijit.byId("headlines-select-articles-dropdown"), 'onItemClick',
+ (item) => {
+ const action = item.option.value;
+
+ switch (action) {
+ case 'headlines_select_all':
+ Headlines.select('all');
+ break;
+ case 'headlines_select_unread':
+ Headlines.select('unread');
+ break;
+ case 'headlines_select_invert':
+ Headlines.select('invert');
+ break;
+ case 'headlines_select_none':
+ Headlines.select('none');
+ break;
+ case 'headlines_selectionToggleUnread':
+ Headlines.selectionToggleUnread();
+ break;
+ case 'headlines_selectionToggleMarked':
+ Headlines.selectionToggleMarked();
+ break;
+ case 'headlines_selectionTogglePublished':
+ Headlines.selectionTogglePublished();
+ break;
+ case 'headlines_catchupSelection':
+ Headlines.catchupSelection();
+ break;
+ case 'article_selectionSetScore':
+ Article.selectionSetScore();
+ break;
+ case 'headlines_deleteSelection':
+ Headlines.deleteSelection();
+ break;
+ default:
+ if (!PluginHost.run_until(PluginHost.HOOK_HEADLINE_TOOLBAR_SELECT_MENU_ITEM2, true, action))
+ console.warn('unknown headlines action', action);
+ }
+ }
+ );
},
onLoaded: function (reply, offset, append) {
console.log("Headlines.onLoaded: offset=", offset, "append=", append);
@@ -1501,6 +1559,48 @@ const Headlines = {
menu.startup();
}
+ /* vfeed menu */
+
+ if (!dijit.byId("vfeedMenu")) {
+
+ const menu = new dijit.Menu({
+ id: "vfeedMenu",
+ targetNodeIds: ["headlines-frame"],
+ selector: ".vfeedMenuAttach"
+ });
+
+ menu.addChild(new dijit.MenuItem({
+ label: __("Mark as read"),
+ onClick: function() {
+ Feeds.catchupFeed(this.getParent().currentTarget.getAttribute("data-feed-id"));
+ }}));
+
+ menu.addChild(new dijit.MenuItem({
+ label: __("Edit feed"),
+ onClick: function() {
+ CommonDialogs.editFeed(this.getParent().currentTarget.getAttribute("data-feed-id"), false);
+ }}));
+
+ menu.addChild(new dijit.MenuItem({
+ label: __("Open site"),
+ onClick: function() {
+ App.postOpenWindow("backend.php", {op: "feeds", method: "opensite",
+ feed_id: this.getParent().currentTarget.getAttribute("data-feed-id"), csrf_token: __csrf_token});
+ }}));
+
+ menu.addChild(new dijit.MenuSeparator());
+
+ menu.addChild(new dijit.MenuItem({
+ label: __("Debug feed"),
+ onClick: function() {
+ /* global __csrf_token */
+ App.postOpenWindow("backend.php", {op: "feeds", method: "updatedebugger",
+ feed_id: this.getParent().currentTarget.getAttribute("data-feed-id"), csrf_token: __csrf_token});
+ }}));
+
+ menu.startup();
+ }
+
/* vgroup feed title menu */
if (!dijit.byId("headlinesFeedTitleMenu")) {
diff --git a/js/PluginHost.js b/js/PluginHost.js
index deb7c0645..513429e4a 100644
--- a/js/PluginHost.js
+++ b/js/PluginHost.js
@@ -21,6 +21,7 @@ const PluginHost = {
HOOK_HEADLINE_MUTATIONS_SYNCED: 16,
HOOK_HEADLINES_RENDERED: 17,
HOOK_HEADLINES_SCROLL_HANDLER: 18,
+ HOOK_HEADLINE_TOOLBAR_SELECT_MENU_ITEM2: 19,
hooks: [],
register: function (name, callback) {
if (typeof(this.hooks[name]) == 'undefined')
@@ -36,6 +37,17 @@ const PluginHost = {
this.hooks[name][i](args);
}
},
+ run_until: function (name, check, ...args) {
+ //console.warn('PluginHost.run_until', name, check, args);
+
+ if (typeof(this.hooks[name]) != 'undefined')
+ for (let i = 0; i < this.hooks[name].length; i++) {
+ if (this.hooks[name][i](args) == check)
+ return true;
+ }
+
+ return false;
+ },
unregister: function (name, callback) {
for (let i = 0; i < this.hooks[name].length; i++)
if (this.hooks[name][i] == callback)
diff --git a/js/PrefHelpers.js b/js/PrefHelpers.js
index 30a4544fe..c0fff66c9 100644
--- a/js/PrefHelpers.js
+++ b/js/PrefHelpers.js
@@ -363,8 +363,15 @@ const Helpers = {
xhr.json("backend.php", {op: "pref-prefs", method: "getPluginsList"}, (reply) => {
this._list_of_plugins = reply;
this.render_contents();
+ }, (e) => {
+ this.render_error(e);
});
},
+ render_error: function(e) {
+ const container = document.querySelector(".prefs-plugin-list");
+
+ container.innerHTML = `<li class='text-error'>${__("Error while loading plugins list: %s.").replace("%s", e)}</li>`;
+ },
render_contents: function() {
const container = document.querySelector(".prefs-plugin-list");
@@ -450,7 +457,7 @@ const Helpers = {
xhr.json("backend.php", {op: "pref-prefs", method: "uninstallPlugin", plugin: plugin}, (reply) => {
if (reply && reply.status == 1)
- Helpers.Prefs.refresh();
+ Helpers.Plugins.reload();
else {
Notify.error("Plugin uninstallation failed.");
}
diff --git a/js/PrefUsers.js b/js/PrefUsers.js
index 7ce3cae94..a6081f35f 100644
--- a/js/PrefUsers.js
+++ b/js/PrefUsers.js
@@ -75,7 +75,7 @@ const Users = {
<fieldset>
<label>${__('Access level: ')}</label>
${App.FormFields.select_hash("access_level",
- user.access_level, reply.access_level_names, {disabled: admin_disabled.toString()})}
+ user.access_level, reply.access_level_names, {disabled: admin_disabled.toString()}, "", {numeric_sort: true})}
${admin_disabled ? App.FormFields.hidden_tag("access_level",
user.access_level.toString()) : ''}
diff --git a/js/common.js b/js/common.js
index 1299a0c64..b972f2376 100755
--- a/js/common.js
+++ b/js/common.js
@@ -186,7 +186,7 @@ const xhr = {
},
json: function(url, params = {}, complete = undefined, failed = undefined) {
return new Promise((resolve, reject) =>
- this.post(url, params).then((data) => {
+ this.post(url, params, null, failed).then((data) => {
let obj = null;
try {
@@ -198,6 +198,7 @@ const xhr = {
failed(e);
reject(e);
+ return;
}
console.log('xhr.json', '<<<', obj, (new Date().getTime() - xhr._ts) + " ms");
diff --git a/js/form/Select.js b/js/form/Select.js
index 530880e2d..0c73cd52c 100755
--- a/js/form/Select.js
+++ b/js/form/Select.js
@@ -1,8 +1,66 @@
-/* global dijit, define */
-define(["dojo/_base/declare", "dijit/form/Select"], function (declare) {
- return declare("fox.form.Select", dijit.form.Select, {
+/* eslint-disable prefer-rest-params */
+/* global define */
+// FIXME: there probably is a better, more dojo-like notation for custom data- properties
+define(["dojo/_base/declare",
+ "dijit/form/Select",
+ "dojo/_base/lang", // lang.hitch
+ "dijit/MenuItem",
+ "dijit/MenuSeparator",
+ "dojo/aspect",
+ ], function (declare, select, lang, MenuItem, MenuSeparator, aspect) {
+ return declare("fox.form.Select", select, {
focus: function() {
return; // Stop dijit.form.Select from keeping focus after closing the menu
},
+ startup: function() {
+ this.inherited(arguments);
+
+ if (this.attr('data-dropdown-skip-first') == 'true') {
+ aspect.before(this, "_loadChildren", () => {
+ this.options = this.options.splice(1);
+ });
+ }
+ },
+ // hook invoked when dropdown MenuItem is clicked
+ onItemClick: function(/*item, menu*/) {
+ //
+ },
+ _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){
+ if (this.attr('data-prevent-value-change') == 'true' && newValue != '')
+ return;
+
+ this.inherited(arguments);
+ },
+ // the only difference from dijit/form/Select is _onItemClicked() handler
+ _getMenuItemForOption: function(/*_FormSelectWidget.__SelectOption*/ option){
+ // summary:
+ // For the given option, return the menu item that should be
+ // used to display it. This can be overridden as needed
+ if (!option.value && !option.label){
+ // We are a separator (no label set for it)
+ return new MenuSeparator({ownerDocument: this.ownerDocument});
+ } else {
+ // Just a regular menu option
+ const click = lang.hitch(this, "_setValueAttr", option);
+ const item = new MenuItem({
+ option: option,
+ label: (this.labelType === 'text' ? (option.label || '').toString()
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;') :
+ option.label) || this.emptyLabel,
+ onClick: () => {
+ this.onItemClick(item, this.dropDown);
+
+ click();
+ },
+ ownerDocument: this.ownerDocument,
+ dir: this.dir,
+ textDir: this.textDir,
+ disabled: option.disabled || false
+ });
+ item.focusNode.setAttribute("role", "option");
+
+ return item;
+ }
+ },
});
});