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