mirror of
				https://github.com/flynx/pWiki.git
				synced 2025-10-31 19:10:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			610 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			HTML
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			610 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			HTML
		
	
	
		
			Executable File
		
	
	
	
	
| <!DOCTYPE html>
 | |
| <html>
 | |
| <!--html manifest="pwiki.appcache"-->
 | |
| <head>
 | |
| <title>pWiki</title>
 | |
| 
 | |
| <meta charset="utf-8">
 | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
 | |
| 
 | |
| <link rel="manifest" href="manifest.json">
 | |
| 
 | |
| <link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-Bold.ttf">
 | |
| <link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-BoldItalic.ttf">
 | |
| <link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-ExtraBold.ttf">
 | |
| <link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-ExtraBoldItalic.ttf">
 | |
| <link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-Italic.ttf">
 | |
| <link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-Light.ttf">
 | |
| <link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-LightItalic.ttf">
 | |
| <link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-Regular.ttf">
 | |
| <link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-Semibold.ttf">
 | |
| <link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-SemiboldItalic.ttf">
 | |
| 
 | |
| <link rel="stylesheet" href="css/fonts.css">
 | |
| 
 | |
| 
 | |
| <!-- NativeMarkdown -->
 | |
| <script src="ext-lib/showdown.min.js"></script>
 | |
| <script>
 | |
| var MarkdownPage = {
 | |
| }
 | |
| </script>
 | |
| <!-- NativeMarkdown -->
 | |
| 
 | |
| 
 | |
| 
 | |
| <!-- MediumEditor -->
 | |
| <!--link rel="stylesheet" href="experiments/medium-editor/css/medium-editor.css">
 | |
| <link rel="stylesheet" href="experiments/medium-editor/css/themes/default.css">
 | |
| <script src="ext-lib/showdown.min.js"></script>
 | |
| <script src="experiments/medium-editor/js/medium-editor.js"></script>
 | |
| <script src="experiments/medium-editor/js/me-markdown.standalone.js"></script>
 | |
| <script>
 | |
| var setupMediumEditor = async function () {
 | |
| 	var editorelem = document.querySelector('.medium-editor')
 | |
| 	if(editorelem){
 | |
| 		console.log('MediumEditor: setup...')
 | |
| 		var page = pwiki.get('..')
 | |
| 
 | |
| 		// load the initial state...
 | |
| 		var converter = new showdown.Converter()
 | |
| 		editorelem.innerHTML = converter.makeHtml(await page.raw)
 | |
| 
 | |
| 		var elem = document.querySelector('.medium-markdown')
 | |
| 		editor = new MediumEditor(editorelem, {
 | |
| 			extensions: {
 | |
| 				markdown: new MeMarkdown(function(code) {
 | |
| 					saveLiveContent(page.path, code)
 | |
| 					// XXX DEBUG...
 | |
| 					elem 
 | |
| 						&& (elem.textContent = code) }) } }) } }
 | |
| </script-->
 | |
| <!-- MediumEditor -->
 | |
| 
 | |
| <!-- ToastUIEditor -->
 | |
| <!-- ToastUIEditor -->
 | |
| 
 | |
| 
 | |
| 
 | |
| </head>
 | |
| 
 | |
| <style>
 | |
| 
 | |
| body {
 | |
| 	font-size: 1.1em;
 | |
| }
 | |
| 
 | |
| h1:empty {
 | |
| 	display: none;
 | |
| }
 | |
| 
 | |
| a {
 | |
| 	text-decoration: none;
 | |
| }
 | |
| a:hover {
 | |
| 	text-decoration: underline;
 | |
| }
 | |
| 
 | |
| .show-on-hover {
 | |
| 	opacity: 0;
 | |
| }
 | |
| :hover>.show-on-hover {
 | |
| 	opacity: 0.4;
 | |
| }
 | |
| .show-on-hover:hover {
 | |
| 	opacity: 0.8;
 | |
| }
 | |
| 
 | |
| /* Spinner... */
 | |
| .spinner {
 | |
| 	position: fixed;
 | |
| 	display: flex;
 | |
| 	text-align: center;
 | |
| 	left: 50%;
 | |
| 	top: 50%;
 | |
| 	width: 100px;
 | |
| 	height: 100px;
 | |
| 	margin-top: -50px;
 | |
| 	margin-left: -50px;
 | |
| 	white-space: nowrap;
 | |
| 
 | |
| 	background: transparent;
 | |
| 	pointer-events: none;
 | |
| 
 | |
| 	animation: fadein 2s 1;
 | |
| }
 | |
| .spinner span {
 | |
| 	position: relative;
 | |
| 	display: inline-block;
 | |
| 	font-size: 2em;
 | |
| 
 | |
| 	animation: loading 2s infinite ;
 | |
| 	animation-delay: calc(0.2s * var(--i));
 | |
| }
 | |
| body:not(.loading) .page.spinner {
 | |
| 	display: none;
 | |
| }
 | |
| body.loading .page.spinner {
 | |
| 	opacity: 0.9;
 | |
| 	animation: none;
 | |
| }
 | |
| body.loading .page.spinner span {
 | |
| 	font-size: 4em;
 | |
| 	/*
 | |
| 	animation: loading 2s infinite ;
 | |
| 	animation-delay: calc(0.2s * var(--i));
 | |
| 	*/
 | |
| 	transform: rotateY(90deg);
 | |
| 	animation: loading-ninty 2s infinite ;
 | |
| 	animation-delay: calc(0.2s * var(--i));
 | |
| }
 | |
| @keyframes fadein {
 | |
| 	from {
 | |
| 		opacity: 0;
 | |
| 	}
 | |
| 	50% {
 | |
| 		opacity: 0.8;
 | |
| 	}
 | |
| 	to {
 | |
| 		opacity: 1;
 | |
| 	}
 | |
| }
 | |
| @keyframes loading {
 | |
| 	0%, 60% {
 | |
| 		transform: rotateY(360deg);
 | |
| 	}
 | |
| }
 | |
| @keyframes loading-ninty {
 | |
| 	0% {
 | |
| 		transform: rotateY(90deg);
 | |
| 	}
 | |
| 	98% {
 | |
| 		transform: rotateY(360deg);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| .placeholder {
 | |
| 	opacity: 0.4;
 | |
| }
 | |
| 
 | |
| .new-page-indicator {
 | |
| 	position: absolute;	
 | |
| 	font-size: small;
 | |
| 	font-style: italic;
 | |
| 	opacity: 0.5;
 | |
| }
 | |
| 
 | |
| /* TOC */
 | |
| toc {
 | |
| 	--toc-level-offset: 2em;
 | |
| }
 | |
| toc a {
 | |
| 	display: block;
 | |
| }
 | |
| toc .h1 {
 | |
| 	margin-left: 0em;
 | |
| }
 | |
| toc .h2 {
 | |
| 	margin-left: calc(var(--toc-level-offset) * 1);
 | |
| }
 | |
| toc .h3 {
 | |
| 	margin-left: calc(var(--toc-level-offset) * 2);
 | |
| }
 | |
| toc .h4 {
 | |
| 	margin-left: calc(var(--toc-level-offset) * 3);
 | |
| }
 | |
| toc .h5 {
 | |
| 	margin-left: calc(var(--toc-level-offset) * 4);
 | |
| }
 | |
| toc .h5 {
 | |
| 	margin-left: calc(var(--toc-level-offset) * 5);
 | |
| }
 | |
| 
 | |
| 
 | |
| .error .msg {
 | |
| 	font-weight: bold;
 | |
| 	color: red;
 | |
| 	margin-bottom: 1em;
 | |
| }
 | |
| .error {
 | |
| 	background: lightyellow;
 | |
| 	padding: 1em;
 | |
| 	box-shadow: inset 3px 5px 15px 5px rgba(0,0,0,0.03);
 | |
| 	border: dashed red 1px;
 | |
| }
 | |
| 
 | |
| textarea {
 | |
| 	font-size: 1.2em;
 | |
| 	border: none;
 | |
| 	resize: none;
 | |
| }
 | |
| [contenteditable] {
 | |
| 	outline: 0px solid transparent;
 | |
| }
 | |
| textarea:empty:after,
 | |
| [contenteditable]:empty:after {
 | |
| 	display: block;
 | |
| 	content: 'Empty';
 | |
| 	opacity: 0.3;
 | |
| }
 | |
| 
 | |
| 
 | |
| .tree-page-title:empty:after {
 | |
| 	content: "/";
 | |
| }
 | |
| 
 | |
| </style>
 | |
| <!-- 
 | |
| Do not edit here...
 | |
| This is loaded with the style defined by the system 
 | |
| -->
 | |
| <style id="style"></style>
 | |
| 
 | |
| <!-- XXX do we need this??? -->
 | |
| <script src="bootstrap.js"></script>
 | |
| 
 | |
| <!--script data-main="pwiki2" src="ext-lib/require.js"></script-->
 | |
| <script src="ext-lib/require.js"></script>
 | |
| <script>
 | |
| //---------------------------------------------------------------------
 | |
| 
 | |
| var DEBUG = true
 | |
| 
 | |
| 
 | |
| 
 | |
| //---------------------------------------------------------------------
 | |
| // Module loading...
 | |
| 
 | |
| var makeFallbacks = 
 | |
| function(paths, search=['lib']){
 | |
| 	return Object.entries(paths)
 | |
| 		.map(function([key, path]){
 | |
| 			// package...
 | |
| 			if(path.endsWith('/')){
 | |
| 				return [key, path] }
 | |
| 			return [
 | |
| 				key,
 | |
| 				[
 | |
| 					path,
 | |
| 					...search
 | |
| 						.map(function(base){
 | |
| 							return base +'/'+ path.split(/[\\\/]+/g).pop() }),
 | |
| 				]
 | |
| 			] })
 | |
| 		.reduce(function(res, [key, value]){
 | |
| 			res[key] = value
 | |
| 			return res }, {}) }
 | |
| 
 | |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | |
| require.config({
 | |
| 	paths: {
 | |
| 		...makeFallbacks({
 | |
| 			'ig-doc': 'node_modules/ig-doc/doc',
 | |
| 			'ig-stoppable': 'node_modules/ig-stoppable/stoppable',
 | |
| 			'object-run': 'node_modules/object-run/run',
 | |
| 			'ig-object': 'node_modules/ig-object/object',
 | |
| 		}),
 | |
| 
 | |
| 		// packages...
 | |
| 		'ig-types': [
 | |
| 			'node_modules/ig-types',
 | |
| 			'lib/types',
 | |
| 		],
 | |
| 
 | |
| 		// external stuff...
 | |
| 		...makeFallbacks({
 | |
| 			'jszip': 'node_modules/jszip/dist/jszip',
 | |
| 			'pouchdb': 'node_modules/pouchdb/dist/pouchdb',
 | |
| 			'showdown': 'node_modules/showdown/dist/showdown',
 | |
| 			'idb-keyval': 'node_modules/idb-keyval/dist/umd',
 | |
| 			'flexsearch': 'node_modules/flexsearch/dist/flexsearch.bundle',
 | |
| 			'any-date-parser': 'node_modules/any-date-parser/dist/browser-bundle',
 | |
| 		}, ['ext-lib']),
 | |
| 	},
 | |
| 	packages: [
 | |
| 		'ig-types',
 | |
| 	]
 | |
| })
 | |
| 
 | |
| 
 | |
| //---------------------------------------------------------------------
 | |
| // Editor -- save changes...
 | |
| 
 | |
| /* XXX GLOBAL_STYLE
 | |
| // XXX might be a good idea to make this a method of pwiki???
 | |
| var STYLE_UPDATED = false
 | |
| var updateStyle = async function(){
 | |
| 	document.querySelector('#style').innerHTML = 
 | |
| 		await pwiki.get('/.config/Style/_text').text }
 | |
| //*/
 | |
| 
 | |
| // XXX might be a good idea to make this a method of pwiki???
 | |
| // XXX the page seems to be broken...
 | |
| var CONFIG_UPDATED = false
 | |
| var updateConfig = async function(){
 | |
| 	// XXX need to set this to something...
 | |
| 	// XXX
 | |
| 	// XXX need to use a parser that supports comments and stuff...
 | |
| 	return JSON.parse(await pwiki.get('/.config/Config/_text').text) }
 | |
| 
 | |
| 
 | |
| // XXX versioning??
 | |
| var SAVE_LIVE_TIMEOUT = 5000
 | |
| var SAVE_LIVE_QUEUE = {}
 | |
| 
 | |
| var saveLiveContent = 
 | |
| function(path, text){
 | |
| 	SAVE_LIVE_QUEUE[path] = text 
 | |
| 	// clear editor page cache...
 | |
| 	pwiki.cache = null }
 | |
| 
 | |
| var SAVE_QUEUE = {}
 | |
| var saveContent =
 | |
| function(path, text){
 | |
| 	SAVE_QUEUE[path] = text }
 | |
| 
 | |
| var saveAll =
 | |
| function(){
 | |
| 	saveNow()
 | |
| 	var queue = Object.entries(SAVE_QUEUE)
 | |
| 	SAVE_QUEUE = {}
 | |
| 	queue
 | |
| 		.forEach(function([path, text]){
 | |
| 			console.log('saving changes to:', path)
 | |
| 			pwiki.get(path).raw = text }) }
 | |
| 
 | |
| var saveNow = 
 | |
| function(){
 | |
| 	var queue = Object.entries(SAVE_LIVE_QUEUE)
 | |
| 	SAVE_LIVE_QUEUE = {}
 | |
| 	NEW_TITLE = undefined
 | |
| 	queue
 | |
| 		.forEach(function([path, text]){
 | |
| 			console.log('saving changes to:', path)
 | |
| 			pwiki.get(path).raw = text }) }
 | |
| setInterval(saveNow, SAVE_LIVE_TIMEOUT)
 | |
| 
 | |
| 
 | |
| 
 | |
| //---------------------------------------------------------------------
 | |
| // Spinner/loading...
 | |
| 
 | |
| // loading spinner...
 | |
| window.startSpinner = function(){
 | |
| 	document.body.classList.add('loading') }
 | |
| window.stopSpinner = function(){
 | |
| 	document.body.classList.remove('loading') }
 | |
| 
 | |
| 
 | |
| 
 | |
| //---------------------------------------------------------------------
 | |
| // General setup...
 | |
| 
 | |
| document.pwikiloaded = new Event('pwikiloaded')
 | |
| 
 | |
| var logTime = async function(promise, msg=''){
 | |
| 	var t = Date.now()
 | |
| 	var res = await promise
 | |
| 	t = Date.now() - t
 | |
| 	DEBUG 
 | |
| 		&& console.log(`## ${
 | |
| 				typeof(msg) == 'function' ? 
 | |
| 					msg(res) 
 | |
| 					: msg
 | |
| 			} (${t}ms)`)
 | |
| 	return res }
 | |
| 
 | |
| 
 | |
| REFRESH_DELAY = 20
 | |
| 
 | |
| var refresh = async function(){
 | |
| 	pwiki.__prev_path = pwiki.path
 | |
| 	startSpinner()
 | |
| 	setTimeout(function(){
 | |
| 		logTime(
 | |
| 			pwiki.refresh(), 
 | |
| 			pwiki.location) }, REFRESH_DELAY) }
 | |
| 
 | |
| 
 | |
| history.scrollRestoration = 'manual'
 | |
| 
 | |
| 
 | |
| 
 | |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | |
| // start loading pWiki...
 | |
| require(['./browser'], function(browser){ 
 | |
| 	var pwiki = window.pwiki = browser.pwiki 
 | |
| 	//var pwpath = window.path = browser.pwpath
 | |
| 	pwiki.dom = document.querySelector('#pWiki')
 | |
| 
 | |
| 	// handle location.hash/history (both directions)
 | |
| 	window.addEventListener('hashchange', function(evt){
 | |
| 		evt.preventDefault()
 | |
| 		var [path, hash] = decodeURI(location.hash).slice(1).split('#')
 | |
| 		path = path.trim() == '' ? 
 | |
| 			pwiki.location
 | |
| 			//'/'
 | |
| 			: path
 | |
| 		// treat links as absolute unless explicitly relative...
 | |
| 		path = /^\.\.?([\\\/].*)?$/.test(path) ?
 | |
| 			path
 | |
| 			: '/'+path
 | |
| 		startSpinner()
 | |
| 		// NOTE: setTimeout(..) to allow the spinner to start...
 | |
| 		// NOTE: this seems not to work if the REFRESH_DELAY is too small...
 | |
| 		setTimeout(function(){
 | |
| 			pwiki.location = [path, hash] }, REFRESH_DELAY) })
 | |
| 
 | |
| 	// scroll...
 | |
| 	// NOTE: we restore scroll position only on history navigation...
 | |
| 	var save_scroll = async function(){ 
 | |
| 		history.replaceState({ 
 | |
| 			path: pwiki.location,
 | |
| 			// XXX HACK this will work only on full page...
 | |
| 			scroll: document.scrollingElement.scrollTop,
 | |
| 		}, '', window.location.hash) }
 | |
| 	// save scroll position just after scroll is done...
 | |
| 	var _scrolling
 | |
| 	window.addEventListener('scroll', function(evt){
 | |
| 		_scrolling
 | |
| 			&& clearTimeout(_scrolling)
 | |
| 		_scrolling = setTimeout(save_scroll, 200) })
 | |
| 	// get scroll position from history state...
 | |
| 	window.addEventListener('popstate', function(evt){
 | |
| 		pwiki.__scroll = (evt.state ?? {}).scroll })
 | |
| 
 | |
| 	pwiki
 | |
| 		.onBeforeNavigate(function(){
 | |
| 			this.__prev_path = this.path
 | |
| 			saveAll() })
 | |
| 		.navigate(async function(){
 | |
| 			// NOTE: we do not need to directly update location.hash here as
 | |
| 			//		that will push an extra history item...
 | |
| 			history.replaceState(
 | |
| 				{ path: this.location }, 
 | |
| 				'',
 | |
| 				'#'+this.location 
 | |
| 					+(this.hash ? 
 | |
| 						'#'+this.hash 
 | |
| 						: ''))
 | |
| 
 | |
| 			/* XXX GLOBAL_STYLE
 | |
| 			// style...
 | |
| 			if(STYLE_UPDATED){
 | |
| 				STYLE_UPDATED = false
 | |
| 				await updateStyle() }
 | |
| 			// config...
 | |
| 			if(CONFIG_UPDATED){
 | |
| 				CONFIG_UPDATED = false
 | |
| 				await updateConfig() }
 | |
| 			//*/
 | |
| 			// NOTE: we are intentionally not awaiting for this -- this 
 | |
| 			//		separates the navigate and load events...
 | |
| 			logTime(
 | |
| 				this.refresh(), 
 | |
| 				this.location) })
 | |
| 		.onLoad(function(evt){
 | |
| 			var that = this
 | |
| 			// stop spinner...
 | |
| 			stopSpinner()
 | |
| 			// handle title...
 | |
| 			// NOTE: we set the global title to either the last <title> 
 | |
| 			//		tag value or the attr .title 
 | |
| 			var titles = [...document.querySelectorAll('title')]
 | |
| 			titles[0].innerHTML = 
 | |
| 				`${titles.length > 1 ? 
 | |
| 					titles.at(-1).innerText
 | |
| 					: this.title} — pWiki`
 | |
| 			// scroll...
 | |
| 			this.hash ?
 | |
| 				// to anchor element...
 | |
| 				this.dom
 | |
| 					.querySelector('#'+ this.hash)
 | |
| 					.scrollIntoView() 
 | |
| 				// restore history position...
 | |
| 				// NOTE: only on navigate to new page...
 | |
| 				// XXX HACK this will work only on full page pWiki and 
 | |
| 				//		not on a element/nested pWiki...
 | |
| 				: (this.__prev_path != this.path 
 | |
| 					&& (document.scrollingElement.scrollTop = this.__scroll ?? 0))
 | |
| 
 | |
| 			// handle refresh...
 | |
| 			// NOTE: we need to do this as hashchange is only triggered 
 | |
| 			//		when the hash is actually changed...
 | |
| 			for(var lnk of this.dom.querySelectorAll(`a[href="${location.hash}"]`)){
 | |
| 				lnk.addEventListener('click', refresh) } })
 | |
| 		.delete(refresh)
 | |
| 
 | |
| 	// handle special file updates...
 | |
| 	// NOTE: the actual updates are done .navigate(..)
 | |
| 	pwiki.store
 | |
| 		.update(function(_, path){
 | |
| 			// XXX GLOBAL_STYLE
 | |
| 			//if(path == '/.config/Style'){
 | |
| 			//	STYLE_UPDATED = true }
 | |
| 			if(path == '/.config/Config'){
 | |
| 				CONFIG_UPDATED = true } })
 | |
| 
 | |
| 	// wait for stuff to finish...
 | |
| 	browser.setup.then(async function(){
 | |
| 		// index...
 | |
| 		await logTime(
 | |
| 			pwiki.store.index(), 
 | |
| 			'Indexing')
 | |
| 		// setup global stuff...
 | |
| 		// XXX GLOBAL_STYLE
 | |
| 		//updateStyle()
 | |
| 		//updateConfig()
 | |
| 		// show current page...
 | |
| 		pwiki.location = decodeURI(location.hash).slice(1) }) }) 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| //---------------------------------------------------------------------
 | |
| // Export/Import...
 | |
| 
 | |
| // XXX
 | |
| var importData = function(evt){
 | |
| 	var files = event.target.files
 | |
| 	var reader = new FileReader()
 | |
| 	reader.addEventListener('load', function(evt){
 | |
| 		var json = JSON.parse(evt.target.result)
 | |
| 		console.log('LOADING JSON:', json)
 | |
| 		pwiki.store
 | |
| 			.load(json)
 | |
| 			.then(function(){
 | |
| 				location.reload() }) })
 | |
| 	reader.readAsText(files[0]) }
 | |
| 
 | |
| // XXX
 | |
| var exportData = async function(options={}){
 | |
| 	var filename
 | |
| 	if(typeof(options) == 'string'){
 | |
| 		filename = options
 | |
| 		options = arguments[1] ?? {} }
 | |
| 	var blob = new Blob(
 | |
| 		[await pwiki.store.json({stringify: true, space: 4, ...options})], 
 | |
| 		{type: "text/plain;charset=utf-8"});
 | |
| 
 | |
| 	var a = document.createElement('a')
 | |
|     var blobURL = a.href = URL.createObjectURL(blob)
 | |
|     a.download = filename
 | |
| 		?? options.filename 
 | |
| 		?? (Date.timeStamp() +'.pWiki-export.pwiki')
 | |
| 
 | |
|     //document.body.appendChild(a)
 | |
|     a.dispatchEvent(new MouseEvent("click"))
 | |
|     //document.body.removeChild(a)
 | |
|     //URL.revokeObjectURL(blobURL)
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| //---------------------------------------------------------------------
 | |
| </script>
 | |
| 
 | |
| <body>
 | |
| 
 | |
| <!-- XXX need to add something passive but animated here... -->
 | |
| <div id="pWiki">
 | |
| 	<div class="spinner">
 | |
| 		<span style="--i:0">p</span>
 | |
| 		<span style="--i:1">W</span>
 | |
| 		<span style="--i:2">i</span>
 | |
| 		<span style="--i:3">k</span>
 | |
| 		<span style="--i:4">i</span>
 | |
| 	</div>
 | |
| </div>
 | |
| 
 | |
| <div class="page spinner">
 | |
| 	<span style="--i:0">.</span>
 | |
| 	<span style="--i:1">.</span>
 | |
| 	<span style="--i:2">.</span>
 | |
| </div>
 | |
| 
 | |
| </body>
 | |
| </html>
 | |
| 
 | |
| <!-- vim:set sw=4 ts=4 : -->
 |