UNPKG

zotero-web-library

Version:

Web library from zotero.org

537 lines (474 loc) 16.5 kB
'use strict'; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; var log = require('libzotero/lib/Log').Logger('zotero-web-library:State'); var State = function State() { this.q = {}; this.f = {}; this.pathVars = {}; this.startUrl = ''; this.replacePush = false; //keep track of where we came from //update prevHref before history.pushState so we know what changed //update curHref when we get a popState event //difference between those two is what we need to look at to know what we should update this.curHref = ''; this.prevHref = ''; //state holder that will keep "current" state separate from History so we can check for updates on popstate //"current" here amounts to the state actually represented in the loaded page, so it will be updated after //events fire for a browser state change this.curState = {}; this.prevState = {}; //setting to determine if we modify the url or not this.useLocation = true; this.filter = null; this.selectedItemKeys = []; }; //rewrite old style urls to current urls State.prototype.rewriteAltUrl = function () { log.debug('rewriteAltUrl', 3); var state = this; var matches = false; var itemKey = false; var collectionKey = false; var replace = false; var basePath = Zotero.config.nonparsedBaseUrl; var pathname = window.location.pathname; var baseRE = new RegExp('.*' + basePath + '\/?'); var oldCollectionRE = /^.*\/items\/collections?\/([A-Z0-9]{8})(?:\/[A-Z0-9]{8})?$/; var oldItemRE = /^.*\/items\/([A-Z0-9]{8})$/; switch (true) { case oldCollectionRE.test(pathname): matches = oldCollectionRE.exec(pathname); collectionKey = matches[1]; itemKey = matches[2]; replace = true; break; case oldItemRE.test(pathname): matches = oldItemRE.exec(pathname); itemKey = matches[1]; replace = true; break; } if (collectionKey) { state.setUrlVar('collectionKey', collectionKey); } if (itemKey) { state.setUrlVar('itemKey', itemKey); } if (replace) { state.replaceState(); } }; State.prototype.updateCurState = function () { var state = this; state.curState = Z.extend({}, state.q, state.pathVars); return; }; State.prototype.savePrevState = function () { var state = this; state.prevState = state.curState; return; }; State.prototype.getSelectedItemKeys = function () { var state = this; //filter actual selected itemKeys so we only return unique list //necessary because of duplicate item checkboxes, some of which //may be hidden var uniqueKeys = {}; var returnKeys = []; state.selectedItemKeys.forEach(function (val) { uniqueKeys[val] = true; }); for (var key in uniqueKeys) { returnKeys.push(key); } if (returnKeys.length === 0 && state.getUrlVar('itemKey')) { returnKeys.push(state.getUrlVar('itemKey')); } return returnKeys; }; //toggle the selected state of the passed item key State.prototype.toggleItemSelected = function (itemKey) { var state = this; var newselected = []; var alreadySelected = false; var selectedItemKeys = state.getSelectedItemKeys(); selectedItemKeys.forEach(function (val) { if (val == itemKey) { alreadySelected = true; } else { newselected.push(val); } }); if (!alreadySelected) { newselected.push(itemKey); } state.selectedItemKeys = newselected; }; State.prototype.pushTag = function (newtag) { log.debug('State.pushTag', 3); var state = this; if (state.pathVars['tag']) { if (state.pathVars['tag'] instanceof Array) { state.pathVars['tag'].push(newtag); } else { var currentTag = state.pathVars['tag']; state.pathVars['tag'] = [currentTag, newtag]; } } else { state.pathVars['tag'] = [newtag]; } return; }; State.prototype.popTag = function (oldtag) { log.debug('State.popTag', 3); var state = this; if (!state.pathVars['tag']) { return; } else if (state.pathVars['tag'] instanceof Array) { var newTagArray = state.pathVars['tag'].filter(function (element, index, array) { return element != oldtag; }); state.pathVars['tag'] = newTagArray; return; } else if (state.pathVars['tag'] == oldtag) { state.pathVars['tag'] = []; return; } }; State.prototype.toggleTag = function (tagtitle) { log.debug('toggleTag', 3); var state = this; if (!state.pathVars['tag']) { state.pathVars['tag'] = [tagtitle]; return; } else if (Array.isArray(state.pathVars['tag'])) { if (state.pathVars['tag'].indexOf(tagtitle) != -1) { var newTagArray = state.pathVars['tag'].filter(function (element, index, array) { return element != tagtitle; }); state.pathVars['tag'] = newTagArray; return; } else { state.pathVars['tag'].push(tagtitle); return; } } else if (state.pathVars['tag'] == tagtitle) { state.pathVars['tag'] = []; return; } else if (typeof state.pathVars['tag'] == 'string') { var oldValue = state.pathVars['tag']; state.pathVars['tag'] = [oldValue, tagtitle]; return; } }; State.prototype.unsetUrlVar = function (unset) { log.debug('State.unsetUrlVar', 3); var state = this; if (state.pathVars[unset]) { delete state.pathVars[unset]; } }; State.prototype.clearUrlVars = function (except) { log.debug('State.clearUrlVars', 3); var state = this; if (!except) { except = []; } var pathVars = state.pathVars; for (var key in pathVars) { if (except.indexOf(key) == -1) { delete pathVars[key]; } } }; State.prototype.parseUrlVars = function () { log.debug('State.parseUrlVars', 3); var state = this; if (!state.useLocation) return; state.q = Zotero.utils.parseQuery(Zotero.utils.querystring(window.location.href)); state.pathVars = state.parsePathVars(); }; State.prototype.parsePathVars = function (pathname) { log.debug('State.parsePathVars', 3); var state = this; var history = window.history; //parse variables out of library urls //:userslug/items/:itemKey/* //:userslug/items/collection/:collectionKey //groups/:groupidentifier/items/:itemKey/* //groups/:groupidentifier/items/collection/:collectionKey/* if (!pathname) { //var hstate = history.state;// History.getState(); pathname = window.location.pathname; } var basePath = Zotero.config.nonparsedBaseUrl; basePath = basePath.replace(window.location.origin, ''); var split_replaced = []; var re = new RegExp('.*' + basePath + '\/?'); var replaced = pathname.replace(re, ''); split_replaced = replaced.split('/'); var pathVars = {}; for (var i = 0; i < split_replaced.length - 1; i = i + 2) { var pathVar = pathVars[split_replaced[i]]; //if var already present change to array and/or push if (pathVar) { if (pathVar instanceof Array) { pathVar.push(decodeURIComponent(split_replaced[i + 1])); } else { var ar = [pathVar]; ar.push(decodeURIComponent(split_replaced[i + 1])); pathVar = ar; } } //otherwise just set the value in the object else { pathVar = decodeURIComponent(split_replaced[i + 1]); } pathVars[split_replaced[i]] = pathVar; } if (pathVars['itemkey']) { var itemKey = pathVars['itemkey']; pathVars['itemKey'] = itemKey; delete pathVars['itemkey']; } return pathVars; }; State.prototype.buildUrl = function (urlvars, queryVars) { var state = this; log.debug('State.buildUrl', 3); log.debug(urlvars); log.debug(queryVars); if (typeof queryVars === 'undefined') { queryVars = false; } var basePath = Zotero.config.nonparsedBaseUrl + '/'; var urlVarsArray = []; var _loop = function _loop(index) { var value = urlvars[index]; if (!value) { return { v: void 0 }; } else if (Array.isArray(value)) { value.forEach(function (v) { urlVarsArray.push(index + '/' + encodeURIComponent(v)); }); } else { urlVarsArray.push(index + '/' + encodeURIComponent(value)); } }; for (var index in urlvars) { var _ret = _loop(index); if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; } urlVarsArray.sort(); var queryVarsArray = []; var _loop2 = function _loop2(_index) { var value = queryVars[_index]; if (!value) { return { v: void 0 }; } else if (Array.isArray(value)) { value.forEach(function (v) { queryVarsArray.push(_index + '=' + encodeURIComponent(v)); }); } else { queryVarsArray.push(_index + '=' + encodeURIComponent(value)); } }; for (var _index in queryVars) { var _ret2 = _loop2(_index); if ((typeof _ret2 === 'undefined' ? 'undefined' : _typeof(_ret2)) === "object") return _ret2.v; } queryVarsArray.sort(); var pathVarsString = urlVarsArray.join('/'); var queryString = ''; if (queryVarsArray.length) { queryString = '?' + queryVarsArray.join('&'); } var url = basePath + pathVarsString + queryString; log.debug(basePath); log.debug(pathVarsString); log.debug(queryString); return url; }; //addvars is an object mapping keys and values to add //removevars is an array of var keys to remove State.prototype.mutateUrl = function (addvars, removevars) { var state = this; //log.debug("State.mutateUrl", 3); if (!addvars) { addvars = {}; } if (!removevars) { removevars = []; } var urlvars = Z.extend({}, state.pathVars); for (var key in addvars) { urlvars[key] = addvars[key]; } if (!Array.isArray(removevars)) { log.error('removevars is not an array'); } removevars.forEach(function (val) { delete urlvars[val]; }); var url = state.buildUrl(urlvars, false); return url; }; State.prototype.pushState = function () { log.debug('State.pushState', 3); var state = this; var history = window.history; //Zotero.ui.saveFormData(); //make prevHref the current location before we change it state.prevHref = state.curHref || window.location.href; //selectively add state to hint where to go var s = Z.extend({}, state.q, state.pathVars); var urlvars = state.pathVars; var queryVars = state.q; var url = state.buildUrl(urlvars, queryVars, false); state.curHref = url; log.debug('about to push url: ' + url, 3); //actually push state and manually call urlChangeCallback if specified if (state.useLocation) { if (state.replacePush === true) { log.debug('State.pushState - replacePush', 3); state.replacePush = false; history.replaceState(s, document.title, url); } else { log.debug('State.pushState - pushState', 3); history.pushState(s, document.title, url); state.stateChanged(); } } else { state.stateChanged(); } log.debug('leaving pushstate', 3); }; State.prototype.replaceState = function () { log.debug('State.replaceState', 3); var state = this; var history = window.history; //update current and leave prev alone. //Zotero.ui.saveFormData(); state.updateCurState(); //selectively add state to hint where to go var s = Z.extend({}, state.curState); var urlvars = state.pathVars; var url = state.buildUrl(urlvars, false); if (state.useLocation) { history.replaceState(s, null, url); state.curHref = url; } else { state.curHref = url; } }; State.prototype.updateStateTitle = function (title) { log.debug('State.updateStateTitle', 3); var state = this; document.title = title; }; State.prototype.getUrlVar = function (key) { var state = this; if (state.pathVars.hasOwnProperty(key) && state.pathVars[key] !== '') { return state.pathVars[key]; } else if (state.q.hasOwnProperty(key)) { return state.q[key]; } return undefined; }; State.prototype.setUrlVar = function (key, val) { var state = this; state.pathVars[key] = val; }; State.prototype.getUrlVars = function () { var state = this; var params = Zotero.utils.parseQuery(Zotero.utils.querystring(window.location.href)); return Z.extend(true, {}, state.pathVars, params); }; State.prototype.setQueryVar = function (key, val) { var state = this; if (val === '') { delete state.q[key]; } else { state.q[key] = val; } }; State.prototype.addQueryVar = function (key, val) { var state = this; if (state.q.hasOwnProperty(key)) { //property exists if (Array.isArray(state.q[key])) { state.q[key].push(val); } else { var newArray = [state.q[key], val]; state.q[key] = newArray; } } else { //no value for that key yet state.q[key] = val; } return state.q[key]; }; State.prototype.popstateCallback = function (evt) { var state = this; log.debug('===== popstateCallback =====', 3); var history = window.history; state.prevHref = state.curHref; log.debug('new href, updating href and processing urlchange', 3); state.curHref = window.location.href; // History.getState().cleanUrl; //reparse url to set vars in Z.ajax state.parseUrlVars(); state.stateChanged(evt); }; State.prototype.stateChanged = function (event) { var state = this; log.debug('stateChanged', 3); state.savePrevState(); state.updateCurState(); //check for changed variables in the url and fire events for them log.debug('Checking changed variables', 3); var changedVars = state.diffState(state.prevHref, state.curHref); var widgetEvents = {}; changedVars.forEach(function (val) { var eventString = val + 'Changed'; log.debug(eventString, 3); //map var events to widget events if (Zotero.eventful.eventMap.hasOwnProperty(eventString)) { Zotero.eventful.eventMap[eventString].forEach(function (val) { if (!widgetEvents.hasOwnProperty(val)) { widgetEvents[val] = 1; } }); } log.debug('State Filter: ' + state.filter, 3); Zotero.trigger(eventString, {}, state.filter); }); //TODO: is this eventMap triggering necessary? for (var ind in widgetEvents) { log.debug('State Filter: ' + state.filter, 3); Zotero.trigger(ind, {}, state.filter); } log.debug('===== stateChanged Done =====', 3); }; State.prototype.diffState = function (prevHref, curHref) { log.debug('State.diffState', 3); var state = this; //check what has changed when a new state is pushed var prevVars = Z.extend({}, state.parsePathVars(prevHref)); var curVars = Z.extend({}, state.parsePathVars(curHref)); var monitoredVars = ['start', 'limit', 'order', 'sort', 'content', 'format', 'q', 'fq', 'itemType', 'itemKey', 'collectionKey', 'searchKey', 'locale', 'tag', 'tagType', 'key', 'style', 'session', 'newer', 'since', 'itemPage', 'mode']; var changedVars = []; monitoredVars.forEach(function (val) { if (prevVars.hasOwnProperty(val) || curVars.hasOwnProperty(val)) { if (prevVars[val] != curVars[val]) { changedVars.push(val); } } }); return changedVars; }; module.exports = State;