1/** 2* Use HTML5 history.pushState to make the browser's back and forward buttons work as expected. 3*/ 4function CBrowserHistory() { 5 var m_inPopState = false; 6 var m_curBaseUrl = document.location.pathname; 7 var m_prevTitle = '__UNDEFINED__'; 8 var m_loadPageFunc = null; //TODO: Not great that this is a member var... 9 var self = this; 10 11 //TODO: Test with doku?id urls. 12 function base(url, withId) { 13 if (withId) { 14 var id = url.replace(/.*id=([^&]+).*/, '$1'); 15 if (id.match(/^[a-zA-Z0-9_\-:]+$/)) 16 return url.replace(/\?.*/, '') + '?id='+id; 17 } 18 return url.replace(/\?.*/, ''); 19 } 20 21 22 /** 23 * Get the expected title of the wiki page. 24 * 25 * @private 26 * @return {String} the title. 27 */ 28 function _getWikiTitle() { 29 var titleElt; 30 $('h1, h2, h3, h4, h5, h6', $('.content_initial')).each(function(idx, elt) { 31 if (elt.className.indexOf('sectionedit') >= 0) { 32 titleElt = elt; 33 return false; // Break out of each(). 34 } 35 }); 36 return titleElt ? $(titleElt).text() : ''; 37 } 38 39 40 /** 41 * Initialize this class 42 * 43 * @param {Function} loadFunc - The function to call after a new page is loaded. 44 */ 45 this.init = function(loadFunc) { 46 m_prevTitle = _getWikiTitle() || '__UNDEFINED__'; 47 m_loadPageFunc = loadFunc; 48 49 window.addEventListener('popstate', function(e) { 50 document.title = e.state.title; 51 m_inPopState = true; 52 self.switchBasePath(e.state.url); 53 //TODO: Set m_viewMode=null with a callback. Put current view mode in the state. Generalize with a getPageState() and pageStateCallback() 54 }); 55 }; 56 57 58 /** 59 * Get the id of the page, or null if switching to that page doesn't support fastshow. 60 * 61 * @param {String} newpage - The new page URL. 62 * @param {Boolean} force - Ignore fastshow rules. 63 * @return {Object} with two members: id (page id) and ns (namespace). 64 */ 65 this.getSwitchId = function(newpage, force) { 66 //TODO Bug: Doesn't work with httpd mode unless doku is in the base directory. Could fix by assuming same namespace. 67 var pageid = newpage.substr(1).replace(/.*doku.php(\?id=|\/)/, '').replace(/\//g, ':'); 68 var ns = pageid.replace(/:[^:]+$/, ''); 69 70 if (!force) { 71 if (JSINFO.fastwiki.fastshow_same_ns && ns != JSINFO.namespace) 72 return false; 73 var incl = JSINFO.fastwiki.fastshow_include, excl = JSINFO.fastwiki.fastshow_exclude; 74 // Include namespaces and pages 75 if (incl && !pageid.match('^(' + incl.split(/\s*,\s*/).join('|') + ')')) 76 return false; 77 // Exclude namespaces and pages 78 if (excl && pageid.match('^(' + excl.split(/\s*,\s*/).join('|') + ')')) 79 return false; 80 } 81 82 return {id:pageid, ns:ns}; 83 }; 84 85 86 /** 87 * Get a regex which matches the current page id in a url. 88 * 89 * @returns {RegExp} 90 */ 91 this.getSelfRefRegex = function() { 92 var path = document.location.pathname; 93 var idPath = JSINFO.id.replace(/:/g, '/'); 94 95 // The non-httpd version can be tested unambiguously. 96 if (path.indexOf('doku.php') >= 0) 97 return new RegExp('doku\\.php\\?id='+JSINFO.id+'$|doku\\.php/'+idPath+'$'); 98 99 // Absolute path with and without domain. 100 // TODO: While it won't apply to doku-generated links, for completeness we should also look for relative paths. This would require 101 // knowing the configured base path, and knowing about any <base> tags. 102 return new RegExp('^'+path+'$|://'+document.location.host+path+'$'); 103 }; 104 105 106 /** 107 * @return {String} the current base url. 108 */ 109 this.getBaseUrl = function() { 110 return m_curBaseUrl; 111 }; 112 113 114 /** 115 * Switch to a different page id (fastshow feature). 116 * 117 * @param {String} newpage - The URL of the new page. 118 */ 119 this.switchBasePath = function(newpage) { 120 //TODO Bug: Doesn't work with httpd mode unless doku is in the base directory. Could fix by assuming same namespace. 121 var pageinfo = this.getSwitchId(newpage); 122 if (!pageinfo) 123 return false; 124 125 // Update JSINFO 126 var oldid = JSINFO.id; 127 JSINFO.id = pageinfo.id; 128 JSINFO.namespace = pageinfo.ns; 129 130 // Replace 'id' fields. 131 $('form').each(function(idx, form) { 132 if ($(form).find('input[name="do"]').length > 0) { 133 var input = $('input[name="id"]', form); 134 if (input.val() == oldid) 135 input.val(pageinfo.id); 136 } 137 }); 138 139 140 // TODO: Need to have newpage in non-switch case to get history for other actions. 141 m_curBaseUrl = base(newpage); //TODO: Always? 142 var prevpage = document.location.href; 143 144 m_loadPageFunc('show', null, {fastwiki_compareid:oldid}, true, function() { 145 setTimeout(function() { 146 if (!m_inPopState) { 147 // If we do things like save and subscribe, we end up back on 'show'. 148 //if (m_viewMode == 'show' && newpage == prevpage) 149 // window.history.back(); 150 //else { 151 // When switching modes, just replace the url. When changing to a new page or in or out of show, push. 152 history.replaceState({url: prevpage, title: document.title}, "", prevpage); 153 // if (m_viewMode == 'show' || m_prevViewMode == 'show') { //TODO 154 history.pushState({url: newpage, title: document.title}, "", newpage); 155 $(window).trigger('fastwiki:afterIdChange', [prevpage, m_curBaseUrl]); 156 // } 157 //} 158 } 159 // Set this here instead of in the popstate listener, so that callbacks and setTimeout will work. 160 m_inPopState = false; 161 162 self.refreshPageTitle(true); 163 }, 1); // setTimeout so it happens after all other page manipulations. This won't be needed if I do history in _action(). 164 }); 165 166 return true; 167 }; 168 169 170 /** 171 * Refresh the page title based on the top heading. 172 * 173 * @param {Boolean} fromIdSwitch - Is this refresh triggered by an id switch? 174 */ 175 this.refreshPageTitle = function(fromIdSwitch) { 176 var title = _getWikiTitle(); 177 178 document.title = title; 179 180 //TODO: Close, but I need to get the prevTitle from h1,h2,etc like above. Or better, return it from the server. 181 if (!fromIdSwitch) { 182 $('a').each(function(idx, elt) { 183 var $this = $(this); 184 var href = $this.attr('href'); 185 if (href && href.match(self.getSelfRefRegex()) && $this.text() == m_prevTitle) 186 $this.text(document.title); 187 }); 188 } 189 190 m_prevTitle = title; 191 }; 192} 193