| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | /********************************************************************** | 
					
						
							|  |  |  | *  | 
					
						
							|  |  |  | * | 
					
						
							| 
									
										
										
										
											2022-04-16 10:53:41 +03:00
										 |  |  | * XXX might be a good idea to try signature based security: | 
					
						
							|  |  |  | * 		- sign changes | 
					
						
							|  |  |  | * 		- sign sync session | 
					
						
							|  |  |  | * 		- refuse changes with wrong signatures | 
					
						
							|  |  |  | * 		- public keys available on client and on server | 
					
						
							|  |  |  | * 			- check signatures localy | 
					
						
							|  |  |  | * 			- check signatures remotely | 
					
						
							|  |  |  | * 		- private key available only with author | 
					
						
							|  |  |  | * 		- keep both the last signed and superceding unsigned version | 
					
						
							|  |  |  | * 		- on sync ask to overwrite unsigned with signed | 
					
						
							|  |  |  | * 		- check if we can use the same mechanics as ssh... | 
					
						
							|  |  |  | * 		- in this view a user in the system is simply a set of keys and  | 
					
						
							|  |  |  | * 			a signature (a page =)) | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | * | 
					
						
							|  |  |  | **********************************************************************/ | 
					
						
							|  |  |  | ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | 
					
						
							|  |  |  | (function(require){ var module={} // make module AMD/node compatible...
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-21 18:33:08 +03:00
										 |  |  | // XXX
 | 
					
						
							|  |  |  | //var object = require('lib/object')
 | 
					
						
							|  |  |  | var object = require('ig-object') | 
					
						
							| 
									
										
										
										
											2022-04-25 16:00:12 +03:00
										 |  |  | var types = require('ig-types') | 
					
						
							| 
									
										
										
										
											2022-04-21 18:33:08 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-12 22:35:06 +03:00
										 |  |  | // XXX might be a good idea to make this compatible with node's path API...
 | 
					
						
							| 
									
										
										
										
											2022-04-16 12:28:06 +03:00
										 |  |  | var path =  | 
					
						
							|  |  |  | module.path = { | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// The page returned when getting the '/' path...
 | 
					
						
							|  |  |  | 	ROOT_PAGE: 'WikiHome', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// The page returned when listing a path ending with '/'...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// If set to false treat dirs the same as pages (default)
 | 
					
						
							|  |  |  | 	// XXX revise...
 | 
					
						
							|  |  |  | 	//DEFAULT_DIR: 'pages',
 | 
					
						
							|  |  |  | 	DEFAULT_DIR: false, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ALTERNATIVE_PAGES: [ | 
					
						
							|  |  |  | 		'EmptyPage', | 
					
						
							|  |  |  | 		'NotFound', | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	SEARCH_PATHS: [ | 
					
						
							|  |  |  | 		'./Templates', | 
					
						
							|  |  |  | 		'/System', | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// NOTE: trailing/leading '/' are represented by '' at end/start of 
 | 
					
						
							|  |  |  | 	// 		path list...
 | 
					
						
							|  |  |  | 	normalize: function(path='.', format='auto'){ | 
					
						
							|  |  |  | 		format = format == 'auto' ? | 
					
						
							|  |  |  | 			(path instanceof Array ? | 
					
						
							|  |  |  | 				'array' | 
					
						
							|  |  |  | 				: 'string') | 
					
						
							|  |  |  | 			: format | 
					
						
							|  |  |  | 		path = (path instanceof Array ? | 
					
						
							|  |  |  | 				path | 
					
						
							|  |  |  | 				// NOTE: this will also trim the path elements...
 | 
					
						
							|  |  |  | 				: path.split(/\s*[\\\/]+\s*/)) | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 			.reduce(function(res, e, i, L){ | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 				// special case: leading '..' / '.'
 | 
					
						
							|  |  |  | 				if(res.length == 0  | 
					
						
							|  |  |  | 						&& e == '..'){ | 
					
						
							|  |  |  | 					return [e] } | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 				;(e == '.'  | 
					
						
							|  |  |  | 						// keep explicit '/' only at start/end of path...
 | 
					
						
							|  |  |  | 						|| (e == ''  | 
					
						
							|  |  |  | 							&& i != 0  | 
					
						
							|  |  |  | 							&& i != L.length-1)) ? | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 					undefined | 
					
						
							|  |  |  | 				: e == '..'  | 
					
						
							|  |  |  | 						|| res[res.length-1] == '>>' ? | 
					
						
							|  |  |  | 					res.pop() | 
					
						
							|  |  |  | 				// NOTE: the last '>>' will be retained...
 | 
					
						
							|  |  |  | 				: res.push(e) | 
					
						
							|  |  |  | 				return res }, [])  | 
					
						
							|  |  |  | 		return format == 'string' ? | 
					
						
							|  |  |  | 			path.join('/')  | 
					
						
							|  |  |  | 			: path }, | 
					
						
							|  |  |  | 	relative: function(parent, path, format='auto'){ | 
					
						
							|  |  |  | 		format = format == 'auto' ? | 
					
						
							|  |  |  | 			(path instanceof Array ? | 
					
						
							|  |  |  | 				'array' | 
					
						
							|  |  |  | 				: 'string') | 
					
						
							|  |  |  | 			: format | 
					
						
							|  |  |  | 		path = this.normalize(path, 'array') | 
					
						
							|  |  |  | 		// root path...
 | 
					
						
							|  |  |  | 		if(path[0] == ''){ | 
					
						
							|  |  |  | 			return format == 'string' ?  | 
					
						
							|  |  |  | 				path.join('/') | 
					
						
							|  |  |  | 				: path } | 
					
						
							|  |  |  | 		parent = this.normalize(parent, 'array') | 
					
						
							|  |  |  | 		return this.normalize(parent.concat(path), format) }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-15 01:59:55 +03:00
										 |  |  | 	//paths: function*(path='/', leading_slash=true){
 | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 	paths: function*(path='/'){ | 
					
						
							|  |  |  | 		path = this.normalize(path, 'array') | 
					
						
							|  |  |  | 		// handle '', '.', and '/' paths...
 | 
					
						
							|  |  |  | 		if(path.length == 0  | 
					
						
							|  |  |  | 				|| (path.length == 1 && path[0] == '') | 
					
						
							|  |  |  | 				|| (path.length == 2 && path[0] == '' && path[1] == '')){ | 
					
						
							|  |  |  | 			path = [this.ROOT_PAGE] } | 
					
						
							|  |  |  | 		// normalize relative paths to root...
 | 
					
						
							|  |  |  | 		path[0] != '' | 
					
						
							|  |  |  | 			&& path.unshift('') | 
					
						
							|  |  |  | 		// paths ending in '/' -- dir lister...
 | 
					
						
							|  |  |  | 		if(path[path.length-1] == ''){ | 
					
						
							|  |  |  | 			path.pop() | 
					
						
							|  |  |  | 			this.DEFAULT_DIR | 
					
						
							|  |  |  | 				&& path.push(this.DEFAULT_DIR) } | 
					
						
							|  |  |  | 		// generate path candidates...
 | 
					
						
							|  |  |  | 		for(var page of [path.pop(), ...this.ALTERNATIVE_PAGES]){ | 
					
						
							|  |  |  | 			for(var tpl of ['.', ...this.SEARCH_PATHS]){ | 
					
						
							|  |  |  | 				// search for page up the path...
 | 
					
						
							|  |  |  | 				var p = path.slice() | 
					
						
							|  |  |  | 				while(p.length > 0){ | 
					
						
							|  |  |  | 					yield this.relative(p, tpl +'/'+ page, 'string') | 
					
						
							| 
									
										
										
										
											2022-04-15 01:59:55 +03:00
										 |  |  | 					//yield leading_slash ? 
 | 
					
						
							|  |  |  | 					//	this.relative(p, tpl +'/'+ page, 'string')
 | 
					
						
							|  |  |  | 					//	: this.relative(p, tpl +'/'+ page, 'string').slice(1)
 | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 					// special case: non-relative template/page path...
 | 
					
						
							|  |  |  | 					if(tpl[0] == '/'){ | 
					
						
							|  |  |  | 						break } | 
					
						
							|  |  |  | 					p.pop() } } } }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-12 22:35:06 +03:00
										 |  |  | // NOTE: store keys must be normalized...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2022-04-15 11:11:02 +03:00
										 |  |  | // XXX LEADING_SLASH should this be strict about leading '/' in paths???
 | 
					
						
							|  |  |  | // 		...this may lead to duplicate paths created -- '/a/b' and 'a/b'
 | 
					
						
							| 
									
										
										
										
											2022-04-24 23:23:47 +03:00
										 |  |  | // XXX would be nice to be able to create sub-stores, i.e. an object that
 | 
					
						
							|  |  |  | // 		would store multiple sub-pages for things like todo docs... (???)
 | 
					
						
							|  |  |  | // 		...the question is how to separate the two from the wiki side...
 | 
					
						
							| 
									
										
										
										
											2022-04-12 02:03:33 +03:00
										 |  |  | // XXX must support store stacks...
 | 
					
						
							| 
									
										
										
										
											2022-04-12 22:35:06 +03:00
										 |  |  | // XXX path macros???
 | 
					
						
							|  |  |  | // XXX should we support page symlinking???
 | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | var store =  | 
					
						
							|  |  |  | module.store = { | 
					
						
							|  |  |  | 	exists: function(path){ | 
					
						
							| 
									
										
										
										
											2022-04-21 18:33:08 +03:00
										 |  |  | 		path = module.path.normalize(path, 'string') | 
					
						
							|  |  |  | 		return path in this | 
					
						
							|  |  |  |    			|| (path[0] == '/' ? | 
					
						
							|  |  |  |    				path.slice(1) in this | 
					
						
							|  |  |  | 				: ('/'+ path) in this) }, | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	paths: function(){ | 
					
						
							| 
									
										
										
										
											2022-04-16 16:56:30 +03:00
										 |  |  | 		return Object.keys(this) }, | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 	pages: function(){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2022-04-16 16:56:30 +03:00
										 |  |  | 		return this.paths() | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 			.map(function(p){ | 
					
						
							|  |  |  | 				return [p, that[p]] }) }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-15 23:32:49 +03:00
										 |  |  | 	// 
 | 
					
						
							|  |  |  | 	// 	Resolve page for path
 | 
					
						
							|  |  |  | 	// 	.match(<path>)
 | 
					
						
							|  |  |  | 	// 		-> <path>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Match paths (non-strict mode)
 | 
					
						
							|  |  |  | 	// 	.match(<pattern>)
 | 
					
						
							|  |  |  | 	// 	.match(<pattern>, false)
 | 
					
						
							|  |  |  | 	// 		-> [<path>, ...]
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Match pages (paths in strict mode)
 | 
					
						
							|  |  |  | 	// 	.match(<pattern>, true)
 | 
					
						
							|  |  |  | 	// 		-> [<path>, ...]
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// In strict mode the trailing star in the pattern will only match 
 | 
					
						
							|  |  |  | 	// actual existing pages, while in non-strict mode the pattern will 
 | 
					
						
							|  |  |  | 	// match all sub-paths.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	match: function(path, strict=false){ | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 		// pattern match * / **
 | 
					
						
							|  |  |  | 		if(path.includes('*')  | 
					
						
							|  |  |  | 				|| path.includes('**')){ | 
					
						
							| 
									
										
										
										
											2022-04-21 11:43:53 +03:00
										 |  |  | 			// NOTE: we are matching full paths only here so leading and 
 | 
					
						
							|  |  |  | 			// 		trainling '/' are optional...
 | 
					
						
							| 
									
										
										
										
											2022-04-15 11:11:02 +03:00
										 |  |  | 			var pattern = new RegExp(`^\\/?${ | 
					
						
							| 
									
										
										
										
											2022-04-16 12:28:06 +03:00
										 |  |  | 				module.path.normalize(path, 'string') | 
					
						
							| 
									
										
										
										
											2022-04-21 11:43:53 +03:00
										 |  |  | 					.replace(/^\/|\/$/g, '') | 
					
						
							| 
									
										
										
										
											2022-04-15 23:32:49 +03:00
										 |  |  | 					.replace(/\//g, '\\/') | 
					
						
							|  |  |  | 					.replace(/\*\*/g, '.+') | 
					
						
							|  |  |  | 					.replace(/\*/g, '[^\\/]+') }`)
 | 
					
						
							|  |  |  | 			return [...this.paths() | 
					
						
							|  |  |  | 				.reduce(function(res, p){ | 
					
						
							|  |  |  | 					var m = p.match(pattern) | 
					
						
							|  |  |  | 					m | 
					
						
							|  |  |  | 						&& (!strict  | 
					
						
							|  |  |  | 							|| m[0] == p)  | 
					
						
							|  |  |  | 						&& res.add(m[0]) | 
					
						
							|  |  |  | 					return res }, new Set())] } | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 		// search...
 | 
					
						
							| 
									
										
										
										
											2022-04-16 12:28:06 +03:00
										 |  |  | 		for(var p of module.path.paths(path)){ | 
					
						
							| 
									
										
										
										
											2022-04-21 11:43:53 +03:00
										 |  |  | 			if(p in this){ | 
					
						
							|  |  |  | 				return p } | 
					
						
							|  |  |  | 			// NOTE: all paths at this point and in store are absolute, 
 | 
					
						
							|  |  |  | 			// 		so we check both with the leading '/' and without 
 | 
					
						
							|  |  |  | 			// 		it to make things a bit more relaxed and return the 
 | 
					
						
							|  |  |  | 			// 		actual matching path...
 | 
					
						
							|  |  |  | 			if(p[0] == '/'  | 
					
						
							|  |  |  | 					&& p.slice(1) in this){ | 
					
						
							|  |  |  | 				return p.slice(1) } | 
					
						
							|  |  |  | 			if(p[0] != '/' | 
					
						
							|  |  |  | 					&& ('/'+p) in this){ | 
					
						
							|  |  |  | 				return '/'+p } } }, | 
					
						
							| 
									
										
										
										
											2022-04-15 23:32:49 +03:00
										 |  |  | 	// 
 | 
					
						
							|  |  |  | 	// 	Resolve page
 | 
					
						
							|  |  |  | 	// 	.get(<path>)
 | 
					
						
							|  |  |  | 	// 		-> <value>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Resolve pages (non-strict mode)
 | 
					
						
							|  |  |  | 	// 	.get(<pattern>)
 | 
					
						
							|  |  |  | 	// 	.get(<pattern>, false)
 | 
					
						
							|  |  |  | 	// 		-> [<value>, .. ]
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Get pages (strict mode)
 | 
					
						
							|  |  |  | 	// 	.get(<pattern>, true)
 | 
					
						
							|  |  |  | 	// 		-> [<value>, .. ]
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// In strict mode this will not try to resolve pages and will not 
 | 
					
						
							|  |  |  | 	// return pages at paths that do not explicitly exist.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2022-04-12 02:03:33 +03:00
										 |  |  | 	// XXX should this call actions???
 | 
					
						
							| 
									
										
										
										
											2022-04-21 11:43:53 +03:00
										 |  |  | 	// XXX should this return a map for pattern matches???
 | 
					
						
							| 
									
										
										
										
											2022-04-15 23:32:49 +03:00
										 |  |  | 	get: function(path, strict=false){ | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2022-04-15 23:32:49 +03:00
										 |  |  | 		path = this.match(path, strict) | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 		return path instanceof Array ? | 
					
						
							| 
									
										
										
										
											2022-04-21 11:43:53 +03:00
										 |  |  | 			// XXX should we return matched paths???
 | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  |    			path.map(function(p){ | 
					
						
							| 
									
										
										
										
											2022-04-21 11:43:53 +03:00
										 |  |  | 				// NOTE: p can match a non existing page at this point, 
 | 
					
						
							|  |  |  | 				// 		this can be the result of matching a/* in a a/b/c
 | 
					
						
							|  |  |  | 				// 		and returning a a/b which can be undefined...
 | 
					
						
							| 
									
										
										
										
											2022-04-15 23:32:49 +03:00
										 |  |  | 				return that[p]  | 
					
						
							|  |  |  | 					?? that[that.match(p)] }) | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 			: this[path] }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// NOTE: deleting and updating only applies to explicit matching 
 | 
					
						
							|  |  |  | 	// 		paths -- no page acquisition is performed...
 | 
					
						
							| 
									
										
										
										
											2022-04-15 23:32:49 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 	// XXX should these return this or the data???
 | 
					
						
							| 
									
										
										
										
											2022-04-23 11:29:51 +03:00
										 |  |  | 	// XXX FUNC handle functions as pages...
 | 
					
						
							| 
									
										
										
										
											2022-04-30 11:00:30 +03:00
										 |  |  | 	// XXX BUG: for path '/' this adds an entry at '', but when getting 
 | 
					
						
							|  |  |  | 	// 		'/', the later is not found...
 | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 	update: function(path, data, mode='update'){ | 
					
						
							| 
									
										
										
										
											2022-04-16 12:28:06 +03:00
										 |  |  | 		path = module.path.normalize('/'+ path, 'string') | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 		path = path[path.length-1] == '/' ? | 
					
						
							|  |  |  | 			path.slice(0, -1) | 
					
						
							|  |  |  | 			: path | 
					
						
							| 
									
										
										
										
											2022-04-15 23:32:49 +03:00
										 |  |  | 		this[path] =  | 
					
						
							|  |  |  | 			mode == 'update' ? | 
					
						
							|  |  |  | 				Object.assign( | 
					
						
							|  |  |  | 					this[path] ?? {},  | 
					
						
							|  |  |  | 					data) | 
					
						
							|  |  |  | 				: data | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2022-04-15 23:32:49 +03:00
										 |  |  | 	// XXX revise...
 | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 	delete: function(path){ | 
					
						
							| 
									
										
										
										
											2022-04-16 12:28:06 +03:00
										 |  |  | 		path = module.path.normalize(path, 'string') | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 		path = path[path.length-1] == '/' ? | 
					
						
							|  |  |  | 			path.slice(0, -1) | 
					
						
							|  |  |  | 			: path | 
					
						
							| 
									
										
										
										
											2022-04-15 23:32:49 +03:00
										 |  |  | 		// XXX revise...
 | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 		delete this[path]  | 
					
						
							| 
									
										
										
										
											2022-04-15 23:32:49 +03:00
										 |  |  | 		delete this['/'+ path]  | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-12 02:03:33 +03:00
										 |  |  | // XXX need to specify page format....
 | 
					
						
							|  |  |  | // XXX need a way to set the page path...
 | 
					
						
							|  |  |  | var actions =  | 
					
						
							|  |  |  | module.actions = { | 
					
						
							|  |  |  | 	__proto__: store, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// base actions (virtual pages)...
 | 
					
						
							| 
									
										
										
										
											2022-04-21 18:33:08 +03:00
										 |  |  | 	'System/raw': function(page, path){ | 
					
						
							| 
									
										
										
										
											2022-04-12 02:03:33 +03:00
										 |  |  | 		return { text: this.get(path +'/..') } }, | 
					
						
							|  |  |  | 	// XXX ...
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-12 02:07:19 +03:00
										 |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-12 02:03:33 +03:00
										 |  |  | var relProxy =  | 
					
						
							|  |  |  | function(name){ | 
					
						
							|  |  |  | 	return function(path='.', ...args){ | 
					
						
							|  |  |  | 		return this.store[name]( | 
					
						
							| 
									
										
										
										
											2022-04-22 12:00:48 +03:00
										 |  |  | 			module.path.relative(this.location, path),  | 
					
						
							| 
									
										
										
										
											2022-04-12 02:03:33 +03:00
										 |  |  | 			...args) } }  | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-23 11:07:30 +03:00
										 |  |  | // XXX HISTORY do we need history management??? 
 | 
					
						
							|  |  |  | // XXX FUNC need to handle functions in store...
 | 
					
						
							| 
									
										
										
										
											2022-04-23 13:35:35 +03:00
										 |  |  | // XXX EVENT add event triggers/handlers...
 | 
					
						
							|  |  |  | // 		...event handlers must be local and not propogate to the root page.
 | 
					
						
							| 
									
										
										
										
											2022-04-21 18:33:08 +03:00
										 |  |  | var BasePage = | 
					
						
							|  |  |  | module.BasePage =  | 
					
						
							|  |  |  | object.Constructor('BasePage', { | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 	// NOTE: this can be inherited...
 | 
					
						
							|  |  |  | 	//store: undefined,
 | 
					
						
							|  |  |  | 	 | 
					
						
							|  |  |  | 	// root page used to clone new instances via the .clone(..) method...
 | 
					
						
							|  |  |  | 	//root: undefined,
 | 
					
						
							| 
									
										
										
										
											2022-04-14 20:58:09 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 	// page location...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	__location: undefined, | 
					
						
							|  |  |  | 	get location(){ | 
					
						
							|  |  |  | 		return this.__location ?? '/' }, | 
					
						
							| 
									
										
										
										
											2022-04-23 13:35:35 +03:00
										 |  |  | 	// XXX EVENT need to be able to trigger a callback/event on this...
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 	set location(path){ | 
					
						
							|  |  |  | 		this.referrer = this.location | 
					
						
							| 
									
										
										
										
											2022-04-22 01:49:11 +03:00
										 |  |  | 		var cur = this.__location =  | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 			module.path.relative( | 
					
						
							|  |  |  | 				this.location,  | 
					
						
							| 
									
										
										
										
											2022-04-22 01:49:11 +03:00
										 |  |  | 				path) | 
					
						
							|  |  |  | 		//* XXX HISTORY...
 | 
					
						
							|  |  |  | 		if(this.history !== false){ | 
					
						
							|  |  |  | 			this.history.includes(this.__location) | 
					
						
							|  |  |  | 				&& this.history.splice( | 
					
						
							|  |  |  | 					this.history.indexOf(this.__location)+1,  | 
					
						
							|  |  |  | 					this.history.length) | 
					
						
							|  |  |  | 			this.history.push(cur) } }, | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 	// referrer -- a previous page location...
 | 
					
						
							| 
									
										
										
										
											2022-04-14 20:58:09 +03:00
										 |  |  | 	referrer: undefined, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 	//* XXX HISTORY...
 | 
					
						
							| 
									
										
										
										
											2022-04-22 01:49:11 +03:00
										 |  |  | 	// NOTE: set this to false to disable history...
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 	__history: undefined, | 
					
						
							|  |  |  | 	get history(){ | 
					
						
							| 
									
										
										
										
											2022-04-22 01:49:11 +03:00
										 |  |  | 		if(this.__history === false){ | 
					
						
							|  |  |  | 			return false } | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 		if(!this.hasOwnProperty('__history')){ | 
					
						
							| 
									
										
										
										
											2022-04-22 12:00:48 +03:00
										 |  |  | 			this.__history = [] } | 
					
						
							|  |  |  | 			//this.__history = (this.__history ?? []).slice() }
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 		return this.__history }, | 
					
						
							| 
									
										
										
										
											2022-04-23 13:35:35 +03:00
										 |  |  | 	// XXX EVENT trigger location change event..,
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 	back: function(offset=1){ | 
					
						
							|  |  |  | 		var h = this.history | 
					
						
							| 
									
										
										
										
											2022-04-22 01:49:11 +03:00
										 |  |  | 		if(h === false  | 
					
						
							|  |  |  | 				|| h.length <= 1){ | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 			return this } | 
					
						
							|  |  |  | 		// get position in history...
 | 
					
						
							|  |  |  | 		var p = h.indexOf(this.location) | 
					
						
							| 
									
										
										
										
											2022-04-22 01:49:11 +03:00
										 |  |  | 		// if outside of history go to last element...
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 		p = p < 0 ?  | 
					
						
							|  |  |  | 			h.length | 
					
						
							|  |  |  | 			: p | 
					
						
							|  |  |  | 		p = Math.max( | 
					
						
							|  |  |  | 			Math.min( | 
					
						
							|  |  |  | 				h.length-1  | 
					
						
							|  |  |  | 					- p  | 
					
						
							|  |  |  | 					+ offset, | 
					
						
							|  |  |  | 				h.length-1),  | 
					
						
							|  |  |  | 			0) | 
					
						
							|  |  |  | 		this.referrer = this.location | 
					
						
							|  |  |  | 		this.__location = h[h.length-1 - p] | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	forward: function(offset=1){ | 
					
						
							|  |  |  | 		return this.back(-offset) }, | 
					
						
							|  |  |  | 	//*/
 | 
					
						
							|  |  |  | 	 | 
					
						
							|  |  |  | 	// page data...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2022-04-23 11:07:30 +03:00
										 |  |  | 	// XXX FUNC handle functions as pages...
 | 
					
						
							|  |  |  | 	// XXX need to support pattern pages...
 | 
					
						
							| 
									
										
										
										
											2022-04-21 18:33:08 +03:00
										 |  |  | 	get data(){ | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 		return this.store.get(this.location) }, | 
					
						
							| 
									
										
										
										
											2022-04-21 18:33:08 +03:00
										 |  |  | 	set data(value){ | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 		this.store.update(this.location, value) }, | 
					
						
							| 
									
										
										
										
											2022-04-12 02:03:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-30 01:39:23 +03:00
										 |  |  | 	// number of matching pages...
 | 
					
						
							|  |  |  | 	get length(){ | 
					
						
							|  |  |  | 		var p = this.match(this.location) | 
					
						
							|  |  |  | 		return p instanceof Array ? | 
					
						
							|  |  |  | 			p.length | 
					
						
							|  |  |  | 			: 1 }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-14 20:58:09 +03:00
										 |  |  | 	// relative proxies to store...
 | 
					
						
							| 
									
										
										
										
											2022-04-12 02:03:33 +03:00
										 |  |  | 	exists: relProxy('exists'),  | 
					
						
							|  |  |  | 	match: relProxy('match'),  | 
					
						
							|  |  |  | 	delete: relProxy('delete'), | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-23 10:56:24 +03:00
										 |  |  | 	// XXX how should this handle functions as values???
 | 
					
						
							| 
									
										
										
										
											2022-04-21 18:33:08 +03:00
										 |  |  | 	get: function(path, referrer){ | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 		return this.clone({ | 
					
						
							|  |  |  | 				location: path,  | 
					
						
							|  |  |  | 				referrer: referrer  | 
					
						
							|  |  |  | 					?? this.location, | 
					
						
							|  |  |  | 			}) }, | 
					
						
							| 
									
										
										
										
											2022-04-21 18:33:08 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// XXX should this be an iterator???
 | 
					
						
							|  |  |  | 	each: function(path){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		var paths = this.match(path) | 
					
						
							|  |  |  | 		paths = paths instanceof Array ?  | 
					
						
							|  |  |  | 			paths  | 
					
						
							|  |  |  | 			: [paths] | 
					
						
							|  |  |  | 		return paths | 
					
						
							|  |  |  | 			.map(function(path){ | 
					
						
							| 
									
										
										
										
											2022-04-22 12:00:48 +03:00
										 |  |  | 				return that.get('/'+ path) }) }, | 
					
						
							| 
									
										
										
										
											2022-04-21 18:33:08 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	map: function(func){ | 
					
						
							|  |  |  | 		return this.each().map(func) }, | 
					
						
							|  |  |  | 	filter: function(func){ | 
					
						
							|  |  |  | 		return this.each().filter(func) }, | 
					
						
							|  |  |  | 	reduce: function(func, dfl){ | 
					
						
							|  |  |  | 		return this.each().reduce(func, dfl) }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Clone a page optionally asigning data into it...
 | 
					
						
							|  |  |  | 	// 	.clone()
 | 
					
						
							| 
									
										
										
										
											2022-04-22 12:00:48 +03:00
										 |  |  | 	// 	.clone({ .. }[, <clone-history>])
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 	// 		-> <page>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Fully clone a page optionally asigning data into it...
 | 
					
						
							| 
									
										
										
										
											2022-04-22 12:00:48 +03:00
										 |  |  | 	// 	.clone(true[, <clone-history>])
 | 
					
						
							|  |  |  | 	// 	.clone(true, { .. }[, <clone-history>])
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 	// 		-> <page>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// Normal cloning will inherit all the "clones" from the original 
 | 
					
						
							|  |  |  | 	// page overloading .location and .referrer
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2022-04-22 12:00:48 +03:00
										 |  |  | 	// NOTE: <clone-history> by default is false unless fully cloning
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX revise...
 | 
					
						
							|  |  |  | 	// XXX HISTORY should we clear history by default...
 | 
					
						
							|  |  |  | 	clone: function(data={}, history=false){ | 
					
						
							|  |  |  | 		var [data, ...args] = [...arguments] | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 		var full = data === true | 
					
						
							| 
									
										
										
										
											2022-04-22 12:00:48 +03:00
										 |  |  | 		history =  | 
					
						
							|  |  |  | 			typeof(args[args.length-1]) == 'boolean' ?  | 
					
						
							|  |  |  | 				args.pop()  | 
					
						
							|  |  |  | 				: full | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 		data = full ?  | 
					
						
							| 
									
										
										
										
											2022-04-22 12:00:48 +03:00
										 |  |  | 			args[0] ?? {}  | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 			: data | 
					
						
							|  |  |  | 		return Object.assign( | 
					
						
							|  |  |  | 			full ? | 
					
						
							|  |  |  | 				// full copy...
 | 
					
						
							|  |  |  | 				this.constructor(this.path, this.referrer, this.store) | 
					
						
							|  |  |  | 				// NOTE: this will restrict all the clones to the first 
 | 
					
						
							|  |  |  | 				// 		generation maintaining the original (.root) page as 
 | 
					
						
							|  |  |  | 				// 		the common root...
 | 
					
						
							|  |  |  | 				// 		this will make all the non-shadowed attrs set on the
 | 
					
						
							|  |  |  | 				// 		root visible to all sub-pages.
 | 
					
						
							|  |  |  | 				: Object.create(this.root ?? this), | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				root: this.root ?? this, | 
					
						
							|  |  |  | 				location: this.location,  | 
					
						
							|  |  |  | 				referrer: this.referrer, | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2022-04-22 12:00:48 +03:00
										 |  |  | 			// XXX HISTORY...
 | 
					
						
							|  |  |  | 			this.__history !== false ? | 
					
						
							|  |  |  | 				{ __history:  | 
					
						
							|  |  |  | 					history ? | 
					
						
							|  |  |  | 						(this.__history ?? []).slice()  | 
					
						
							|  |  |  | 						: [] } | 
					
						
							|  |  |  | 				:{}, | 
					
						
							|  |  |  | 			//*/
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 			data) }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-25 20:53:20 +03:00
										 |  |  | 	update: function(...data){ | 
					
						
							|  |  |  | 		return Object.assign(this, ...data) }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 	__init__: function(path, referrer, store){ | 
					
						
							|  |  |  | 		// NOTE: this will allow inheriting .store from the prototype
 | 
					
						
							|  |  |  | 		if(store){ | 
					
						
							|  |  |  | 			this.store = store } | 
					
						
							|  |  |  | 		this.location = path | 
					
						
							| 
									
										
										
										
											2022-04-21 18:33:08 +03:00
										 |  |  | 		this.referrer = referrer }, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-12 02:03:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 15:53:21 +03:00
										 |  |  | // XXX BUG? '<slot name=x text="moo <now/> foo">' is parsed semi-wrong...
 | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | var parser = | 
					
						
							|  |  |  | module.parser = { | 
					
						
							| 
									
										
										
										
											2022-04-28 15:53:21 +03:00
										 |  |  | 	// patterns...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 	// NOTE: the actual macro pattern is not stored as it depends on 
 | 
					
						
							|  |  |  | 	// 		the macro name list which is page dependant...
 | 
					
						
							|  |  |  | 	// XXX add escaping...
 | 
					
						
							|  |  |  | 	MACRO_PATTERN_STR: [[ | 
					
						
							|  |  |  | 			// @macro(arg ..)
 | 
					
						
							|  |  |  | 			// XXX add support for '\)' in args...
 | 
					
						
							|  |  |  | 			'\\\\?@(?<nameInline>MACROS)\\((?<argsInline>([^)])*)\\)', | 
					
						
							|  |  |  | 			// <macro ..> | <macro ../>
 | 
					
						
							|  |  |  | 			// XXX revise escaped > and />
 | 
					
						
							|  |  |  | 			'<\\s*(?<nameOpen>MACROS)(?<argsOpen>\\s+([^>/])*)?/?>', | 
					
						
							|  |  |  | 			// </macro>
 | 
					
						
							|  |  |  | 			'</\\s*(?<nameClose>MACROS)\\s*>', | 
					
						
							|  |  |  | 		].join('|'), 'smig'], | 
					
						
							|  |  |  | 	// NOTE: this depends on .MACRO_PATTERN_STR and thus is lazily generated...
 | 
					
						
							|  |  |  | 	__MACRO_PATTERN_GROUPS: undefined, | 
					
						
							|  |  |  | 	get MACRO_PATTERN_GROUPS(){ | 
					
						
							|  |  |  | 		return this.__MACRO_PATTERN_GROUPS  | 
					
						
							|  |  |  | 			?? (this.__MACRO_PATTERN_GROUPS = | 
					
						
							|  |  |  | 				'<MACROS>'.split(new RegExp(`(${ this.MACRO_PATTERN_STR })`)).length-2) }, | 
					
						
							|  |  |  | 	// XXX still buggy...
 | 
					
						
							|  |  |  | 	MACRO_ARGS_PATTERN: RegExp('('+[ | 
					
						
							|  |  |  | 			// named args...
 | 
					
						
							|  |  |  | 			'(?<nameQuoted>[a-zA-Z-_]+)\\s*=([\'"])(?<valueQuoted>([^\\3]|\\\\3)*)\\3\\s*', | 
					
						
							|  |  |  | 			'(?<nameUnquoted>[a-zA-Z-_]+)\\s*=(?<valueUnquoted>[^\\s]*)', | 
					
						
							|  |  |  | 			// positional args...
 | 
					
						
							|  |  |  | 			'([\'"])(?<argQuoted>([^\\8]|\\\\8)*)\\8', | 
					
						
							|  |  |  | 			'(?<arg>[^\\s]+)', | 
					
						
							|  |  |  | 		].join('|') +')', 'smig'), | 
					
						
							|  |  |  | 	// XXX do we need basic inline and block commets a-la lisp???
 | 
					
						
							|  |  |  | 	COMMENT_PATTERN: RegExp('('+[ | 
					
						
							|  |  |  | 			// <!--[pwiki[ .. ]]-->
 | 
					
						
							|  |  |  | 			'<!--\\[pwiki\\[(?<uncomment>.*)\\]\\]-->', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// <pwiki-comment> .. </pwiki-comment>
 | 
					
						
							|  |  |  | 			'<\\s*pwiki-comment[^>]*>.*<\\/\\s*pwiki-comment\\s*>', | 
					
						
							|  |  |  | 			// <pwiki-comment .. />
 | 
					
						
							|  |  |  | 			'<\\s*pwiki-comment[^\\/>]*\\/>', | 
					
						
							|  |  |  | 		].join('|') +')', 'smig'), | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 15:53:21 +03:00
										 |  |  | 	// helpers...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	getPositional: function(args){ | 
					
						
							|  |  |  | 		return Object.entries(args) | 
					
						
							|  |  |  | 			.reduce(function(res, [key, value]){ | 
					
						
							|  |  |  | 				/^[0-9]+$/.test(key) | 
					
						
							|  |  |  | 					&& (res[key*1] = value) | 
					
						
							|  |  |  | 				return res }, []) }, | 
					
						
							|  |  |  | 	normalizeFilters: function(filters){ | 
					
						
							|  |  |  | 		var skip = new Set() | 
					
						
							|  |  |  | 		return filters | 
					
						
							|  |  |  | 			.flat() | 
					
						
							|  |  |  | 			.tailUnique() | 
					
						
							|  |  |  | 			.filter(function(filter){ | 
					
						
							|  |  |  | 				filter[0] == '-' | 
					
						
							|  |  |  | 					&& skip.add(filter.slice(1)) | 
					
						
							|  |  |  | 				return filter[0] != '-' })  | 
					
						
							|  |  |  | 			.filter(function(filter){ | 
					
						
							|  |  |  | 				return !skip.has(filter) })}, | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 	// Strip comments...
 | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 	stripComments: function(str){ | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 		return str | 
					
						
							|  |  |  | 			.replace(this.COMMENT_PATTERN,  | 
					
						
							|  |  |  | 				function(...a){ | 
					
						
							|  |  |  | 					return a.pop().uncomment  | 
					
						
							|  |  |  | 						|| '' }) }, | 
					
						
							| 
									
										
										
										
											2022-04-13 13:53:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 	// Lexically split the string...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 	// 	<item> ::=
 | 
					
						
							|  |  |  | 	// 		<string>
 | 
					
						
							|  |  |  | 	// 		| {
 | 
					
						
							|  |  |  | 	// 			name: <string>,
 | 
					
						
							|  |  |  | 	// 			type: 'inline'
 | 
					
						
							|  |  |  | 	// 				| 'element'
 | 
					
						
							|  |  |  | 	// 				| 'opening'
 | 
					
						
							|  |  |  | 	// 				| 'closing',
 | 
					
						
							|  |  |  | 	// 			args: {
 | 
					
						
							|  |  |  | 	// 				<index>: <value>,
 | 
					
						
							|  |  |  | 	// 				<key>: <value>,
 | 
					
						
							|  |  |  | 	// 				...
 | 
					
						
							|  |  |  | 	// 			}
 | 
					
						
							|  |  |  | 	// 			match: <string>,
 | 
					
						
							|  |  |  | 	// 		}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this internally uses macros' keys to generate the lexing pattern.
 | 
					
						
							|  |  |  | 	lex: function*(page, str){ | 
					
						
							|  |  |  | 		str = str  | 
					
						
							|  |  |  | 			?? page.raw | 
					
						
							|  |  |  | 		// NOTE: we are doing a separate pass for comments to completely 
 | 
					
						
							|  |  |  | 		// 		decouple them from the base macro syntax, making them fully 
 | 
					
						
							|  |  |  | 		// 		transparent...
 | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 		str = this.stripComments(str) | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// XXX should this be cached???
 | 
					
						
							|  |  |  | 		var MACRO_PATTERN = new RegExp( | 
					
						
							|  |  |  | 			'('+ this.MACRO_PATTERN_STR[0] | 
					
						
							|  |  |  | 				.replace(/MACROS/g, Object.keys(page.macros).join('|')) +')', | 
					
						
							|  |  |  | 			this.MACRO_PATTERN_STR[1])  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var lst = str.split(MACRO_PATTERN) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var macro = false | 
					
						
							|  |  |  | 		while(lst.length > 0){ | 
					
						
							|  |  |  | 			if(macro){ | 
					
						
							|  |  |  | 				var match = lst.splice(0, this.MACRO_PATTERN_GROUPS)[0] | 
					
						
							|  |  |  | 				// NOTE: we essentially are parsing the detected macro a 
 | 
					
						
							|  |  |  | 				// 		second time here, this gives us access to named groups
 | 
					
						
							|  |  |  | 				// 		avoiding maintaining match indexes with the .split(..) 
 | 
					
						
							|  |  |  | 				// 		output...
 | 
					
						
							|  |  |  | 				// XXX for some reason .match(..) here returns a list with a string...
 | 
					
						
							|  |  |  | 				var cur = [...match.matchAll(MACRO_PATTERN)][0].groups | 
					
						
							|  |  |  | 				// special case: escaped inline macro -> keep as text...
 | 
					
						
							|  |  |  | 				if(match.startsWith('\\@')){ | 
					
						
							|  |  |  | 					yield match | 
					
						
							|  |  |  | 					macro = false  | 
					
						
							|  |  |  | 					continue } | 
					
						
							|  |  |  | 				// args...
 | 
					
						
							|  |  |  | 				var args = {} | 
					
						
							|  |  |  | 				var i = -1 | 
					
						
							|  |  |  | 				for(var {groups}  | 
					
						
							|  |  |  | 						of (cur.argsInline ?? cur.argsOpen ?? '') | 
					
						
							|  |  |  | 							.matchAll(this.MACRO_ARGS_PATTERN)){ | 
					
						
							|  |  |  | 					i++ | 
					
						
							|  |  |  | 					args[groups.nameQuoted ?? groups.nameUnquoted ?? i] = | 
					
						
							|  |  |  | 						groups.valueQuoted  | 
					
						
							|  |  |  | 						?? groups.valueUnquoted  | 
					
						
							|  |  |  | 						?? groups.argQuoted  | 
					
						
							|  |  |  | 						?? groups.arg } | 
					
						
							|  |  |  | 				// macro-spec...
 | 
					
						
							|  |  |  | 				yield { | 
					
						
							|  |  |  | 					name: (cur.nameInline  | 
					
						
							|  |  |  | 							?? cur.nameOpen  | 
					
						
							|  |  |  | 							?? cur.nameClose) | 
					
						
							|  |  |  | 						.toLowerCase(), | 
					
						
							|  |  |  | 					type: match[0] == '@' ? | 
					
						
							|  |  |  | 							'inline' | 
					
						
							|  |  |  | 						: match[1] == '/' ? | 
					
						
							|  |  |  | 							'closing' | 
					
						
							|  |  |  | 						: match[match.length-2] == '/' ? | 
					
						
							|  |  |  | 							'element' | 
					
						
							|  |  |  | 						: 'opening', | 
					
						
							|  |  |  | 					args,  | 
					
						
							|  |  |  | 					match, | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				macro = false | 
					
						
							|  |  |  | 			// normal text...
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				var str = lst.shift() | 
					
						
							|  |  |  | 				// skip empty strings from output...
 | 
					
						
							|  |  |  | 				if(str != ''){ | 
					
						
							|  |  |  | 					yield str } | 
					
						
							|  |  |  | 				macro = true } } }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Group block elements...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	<item> ::=
 | 
					
						
							|  |  |  | 	// 		<string>
 | 
					
						
							|  |  |  | 	// 		| {
 | 
					
						
							|  |  |  | 	// 			type: 'inline'
 | 
					
						
							|  |  |  | 	// 				| 'element'
 | 
					
						
							|  |  |  | 	// 				| 'block',
 | 
					
						
							|  |  |  | 	// 			body: [
 | 
					
						
							|  |  |  | 	// 				<item>,
 | 
					
						
							|  |  |  | 	// 				...
 | 
					
						
							|  |  |  | 	// 			],
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//			// rest of items are the same as for lex(..)
 | 
					
						
							|  |  |  | 	// 			...
 | 
					
						
							|  |  |  | 	// 		}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this internaly uses macros to check for propper nesting
 | 
					
						
							| 
									
										
										
										
											2022-04-30 01:39:23 +03:00
										 |  |  | 	//group: function*(page, lex, to=false){
 | 
					
						
							|  |  |  | 	group: function*(page, lex, to=false, parent){ | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 		lex = lex | 
					
						
							|  |  |  | 			?? this.lex(page)  | 
					
						
							|  |  |  | 		lex = typeof(lex) == 'string' ? | 
					
						
							|  |  |  | 			this.lex(page, lex) | 
					
						
							|  |  |  | 			: lex | 
					
						
							|  |  |  | 		// NOTE: we are not using for .. of .. here as it depletes the 
 | 
					
						
							|  |  |  | 		// 		generator even if the end is not reached...
 | 
					
						
							|  |  |  | 		while(true){ | 
					
						
							|  |  |  | 			var {value, done} = lex.next() | 
					
						
							|  |  |  | 			// check if unclosed blocks remaining...
 | 
					
						
							|  |  |  | 			if(done){ | 
					
						
							|  |  |  | 				if(to){ | 
					
						
							|  |  |  | 					throw new Error( | 
					
						
							|  |  |  | 						'Premature end of unpit: Expected closing "'+ to +'"') } | 
					
						
							|  |  |  | 				return } | 
					
						
							|  |  |  | 			// assert nesting rules...
 | 
					
						
							| 
									
										
										
										
											2022-04-30 01:39:23 +03:00
										 |  |  | 			// NOTE: we only check for direct nesting...
 | 
					
						
							|  |  |  | 			// XXX might be a good idea to link nested block to the parent...
 | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 			if(page.macros[value.name] instanceof Array | 
					
						
							| 
									
										
										
										
											2022-04-30 01:39:23 +03:00
										 |  |  | 					&& !page.macros[value.name].includes(to) | 
					
						
							|  |  |  | 					// do not complain about closing nestable tags...
 | 
					
						
							|  |  |  | 					&& !(value.name == to  | 
					
						
							|  |  |  | 						&& value.type == 'closing')){ | 
					
						
							| 
									
										
										
										
											2022-04-13 20:24:08 +03:00
										 |  |  | 				throw new Error( | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 					'Unexpected "'+ value.name +'" macro'  | 
					
						
							|  |  |  | 						+(to ?  | 
					
						
							|  |  |  | 							' in "'+to+'"'  | 
					
						
							|  |  |  | 							: '')) } | 
					
						
							|  |  |  | 			// open block...
 | 
					
						
							|  |  |  | 			if(value.type == 'opening'){ | 
					
						
							| 
									
										
										
										
											2022-04-30 01:39:23 +03:00
										 |  |  | 				//value.body = [...this.group(page, lex, value.name)]
 | 
					
						
							|  |  |  | 				value.body = [...this.group(page, lex, value.name, value)] | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 				value.type = 'block' | 
					
						
							|  |  |  | 				yield value | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			// close block...
 | 
					
						
							|  |  |  | 			} else if(value.type == 'closing'){ | 
					
						
							|  |  |  | 				if(value.name != to){ | 
					
						
							|  |  |  | 					throw new Error('Unexpected closing "'+ value.name +'"') } | 
					
						
							|  |  |  | 				// NOTE: we are intentionally not yielding the value here...
 | 
					
						
							|  |  |  | 				return }  | 
					
						
							|  |  |  | 			yield value } },  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Expand macros...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 	// 	<item> ::=
 | 
					
						
							|  |  |  | 	// 		<string>
 | 
					
						
							|  |  |  | 	// 		// returned by .macros.filter(..)
 | 
					
						
							|  |  |  | 	// 		| {
 | 
					
						
							|  |  |  | 	// 			filters: [
 | 
					
						
							|  |  |  | 	// 				'<filter>'
 | 
					
						
							|  |  |  | 	// 					| '-<filter>',
 | 
					
						
							|  |  |  | 	// 				...
 | 
					
						
							|  |  |  | 	// 			],
 | 
					
						
							|  |  |  | 	// 			data: [ <item>, .. ],
 | 
					
						
							|  |  |  | 	// 		}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 	expand: function*(page, ast, state={}){ | 
					
						
							|  |  |  | 		ast = ast == null ? | 
					
						
							|  |  |  | 				this.group(page) | 
					
						
							|  |  |  | 			: typeof(ast) == 'string' ? | 
					
						
							|  |  |  | 				this.group(page, ast) | 
					
						
							|  |  |  | 			: ast instanceof types.Generator ? | 
					
						
							|  |  |  | 				ast | 
					
						
							|  |  |  | 			: ast.iter() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		while(true){ | 
					
						
							|  |  |  | 			var {value, done} = ast.next() | 
					
						
							|  |  |  | 			if(done){ | 
					
						
							|  |  |  | 				return } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// text block...
 | 
					
						
							|  |  |  | 			if(typeof(value) == 'string'){ | 
					
						
							|  |  |  | 				yield value  | 
					
						
							|  |  |  | 				continue } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// macro...
 | 
					
						
							|  |  |  | 			var {name, args, body} = value | 
					
						
							|  |  |  | 			var res =  | 
					
						
							| 
									
										
										
										
											2022-04-30 01:39:23 +03:00
										 |  |  | 				page.macros[name].call(page, args, body, state, value) | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 					?? '' | 
					
						
							|  |  |  | 			if(res instanceof Array  | 
					
						
							|  |  |  | 					|| page.macros[name] instanceof types.Generator){ | 
					
						
							|  |  |  | 				yield* res | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				yield res } } }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Fully parse a page...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// This runs in two stages:
 | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 	// 	- expand the page
 | 
					
						
							|  |  |  | 	// 		- lex the page -- .lex(..)
 | 
					
						
							|  |  |  | 	// 		- group block elements -- .group(..)
 | 
					
						
							|  |  |  | 	// 		- expand macros -- .expand(..)
 | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 	// 	- apply filters
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX add a special filter to clear pending filters... (???)
 | 
					
						
							|  |  |  | 	parse: function(page, ast, state={}){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		// XXX should we handle strings as input???
 | 
					
						
							|  |  |  | 		ast = ast  | 
					
						
							|  |  |  | 			?? this.expand(page, null, state) | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 		ast = typeof(ast) == 'string' ? | 
					
						
							|  |  |  | 			this.expand(page, ast, state) | 
					
						
							|  |  |  | 			: ast | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		return [...ast] | 
					
						
							| 
									
										
										
										
											2022-04-27 14:28:19 +03:00
										 |  |  | 			// post handlers...
 | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 			.map(function(section){ | 
					
						
							| 
									
										
										
										
											2022-04-27 14:28:19 +03:00
										 |  |  | 				return section instanceof Function ?  | 
					
						
							|  |  |  | 					section.call(page, state) | 
					
						
							| 
									
										
										
										
											2022-04-27 13:06:40 +03:00
										 |  |  | 					: section }) | 
					
						
							|  |  |  | 			.flat() | 
					
						
							|  |  |  | 			// filters...
 | 
					
						
							|  |  |  | 			.map(function(section){ | 
					
						
							| 
									
										
										
										
											2022-04-27 14:28:19 +03:00
										 |  |  | 				return ( | 
					
						
							|  |  |  | 					// expand section...
 | 
					
						
							|  |  |  | 					typeof(section) != 'string' ? | 
					
						
							|  |  |  | 						section.data | 
					
						
							|  |  |  | 					// global filters... 
 | 
					
						
							|  |  |  | 					: state.filters ? | 
					
						
							| 
									
										
										
										
											2022-04-27 13:06:40 +03:00
										 |  |  | 						that.normalizeFilters(state.filters) | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 							.reduce(function(res, filter){ | 
					
						
							|  |  |  | 								if(page.filters[filter] == null){ | 
					
						
							|  |  |  | 									throw new Error( | 
					
						
							|  |  |  | 										'.parse(..): unsupported filter: '+ filter) } | 
					
						
							|  |  |  | 								return page.filters[filter].call(page, res)  | 
					
						
							|  |  |  | 									?? res }, section) | 
					
						
							| 
									
										
										
										
											2022-04-27 14:28:19 +03:00
										 |  |  | 					// no global filters...
 | 
					
						
							|  |  |  | 					: section ) }) | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 			.flat() | 
					
						
							|  |  |  | 			.join('') }, | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-04-13 03:14:21 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-25 20:53:20 +03:00
										 |  |  | // XXX PATH_VARS need to handle path variables...
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | // XXX macros and filters should be features for simpler plugin handlng (???)
 | 
					
						
							| 
									
										
										
										
											2022-04-21 18:33:08 +03:00
										 |  |  | var Page = | 
					
						
							|  |  |  | module.Page =  | 
					
						
							|  |  |  | object.Constructor('Page', BasePage, { | 
					
						
							| 
									
										
										
										
											2022-04-26 03:52:28 +03:00
										 |  |  | 	//NO_FILTERS: 'nofilters',
 | 
					
						
							|  |  |  | 	ISOLATED_FILTERS: 'isolated', | 
					
						
							| 
									
										
										
										
											2022-04-25 20:53:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-26 14:42:01 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	<filter>(<source>)
 | 
					
						
							|  |  |  | 	// 		-> <result>
 | 
					
						
							|  |  |  | 	// 		-> undefined
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2022-04-25 20:53:20 +03:00
										 |  |  | 	// XXX might be a good idea to fix filter order...
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 	filters: { | 
					
						
							| 
									
										
										
										
											2022-04-26 14:42:01 +03:00
										 |  |  | 		// placeholders...
 | 
					
						
							|  |  |  | 		nofilters: function(){}, | 
					
						
							|  |  |  | 		isolated: function(){}, | 
					
						
							| 
									
										
										
										
											2022-04-25 20:53:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-26 14:42:01 +03:00
										 |  |  | 		// XXX TESTING...
 | 
					
						
							|  |  |  | 		dummy: function(){}, | 
					
						
							| 
									
										
										
										
											2022-04-25 20:53:20 +03:00
										 |  |  | 		test: function(source){ | 
					
						
							|  |  |  | 			return source  | 
					
						
							|  |  |  | 				.replace(/ test /g, ' TEST ') }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		wikiword: function(source){ | 
					
						
							| 
									
										
										
										
											2022-04-26 14:42:01 +03:00
										 |  |  | 			// XXX
 | 
					
						
							| 
									
										
										
										
											2022-04-25 20:53:20 +03:00
										 |  |  | 			return source }, | 
					
						
							|  |  |  | 		markdown: function(source){ | 
					
						
							| 
									
										
										
										
											2022-04-26 14:42:01 +03:00
										 |  |  | 			// XXX
 | 
					
						
							| 
									
										
										
										
											2022-04-25 20:53:20 +03:00
										 |  |  | 			return source }, | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2022-04-26 03:52:28 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 	macros: { | 
					
						
							| 
									
										
										
										
											2022-04-25 16:00:12 +03:00
										 |  |  | 		// XXX move to docs...
 | 
					
						
							|  |  |  | 		test: function*(args, body, state){ | 
					
						
							|  |  |  | 			if(body){ | 
					
						
							|  |  |  | 				state.testBlock = (state.testBlock ?? 0) + 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				yield '\n<test>\n\n' | 
					
						
							|  |  |  | 				yield* this.expand(body)  | 
					
						
							|  |  |  | 				yield '\n\n</test>\n' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				--state.testBlock == 0 | 
					
						
							|  |  |  | 					&& (delete state.testBlock) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				yield '<test/>' } }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 		now: function(){ | 
					
						
							| 
									
										
										
										
											2022-04-25 16:00:12 +03:00
										 |  |  | 			return ''+ Date.now() }, | 
					
						
							| 
									
										
										
										
											2022-04-26 03:52:28 +03:00
										 |  |  | 		//
 | 
					
						
							|  |  |  | 		// 	@filter(<filter-spec>)
 | 
					
						
							|  |  |  | 		// 	<filter <filter-spec>/>
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 	<filter <filter-spec>>
 | 
					
						
							|  |  |  | 		// 		...
 | 
					
						
							|  |  |  | 		// 	</filter>
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 	<filter-spec> ::=
 | 
					
						
							|  |  |  | 		// 		<filter> <filter-spec>
 | 
					
						
							|  |  |  | 		// 		| -<filter> <filter-spec>
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// XXX support .NO_FILTERS ...
 | 
					
						
							| 
									
										
										
										
											2022-04-25 16:00:12 +03:00
										 |  |  | 		filter: function*(args, body, state){ | 
					
						
							| 
									
										
										
										
											2022-04-25 20:53:20 +03:00
										 |  |  | 			var filters = state.filters =  | 
					
						
							| 
									
										
										
										
											2022-04-27 23:31:11 +03:00
										 |  |  | 				state.filters ?? [] | 
					
						
							|  |  |  | 			// separate local filters...
 | 
					
						
							| 
									
										
										
										
											2022-04-25 16:00:12 +03:00
										 |  |  | 			if(body){ | 
					
						
							| 
									
										
										
										
											2022-04-27 23:31:11 +03:00
										 |  |  | 				var outer_filters = filters | 
					
						
							|  |  |  | 				filters = state.filters = | 
					
						
							|  |  |  | 					[outer_filters] } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// merge in new filters...
 | 
					
						
							|  |  |  | 			filters.splice(filters.length, 0, ...Object.values(args)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-25 20:53:20 +03:00
										 |  |  | 			// local filters...
 | 
					
						
							|  |  |  | 			if(body){ | 
					
						
							| 
									
										
										
										
											2022-04-26 03:52:28 +03:00
										 |  |  | 				// isolate from parent...
 | 
					
						
							|  |  |  | 				state.filters.includes(this.ISOLATED_FILTERS) | 
					
						
							|  |  |  | 					&& state.filters[0] instanceof Array | 
					
						
							|  |  |  | 					&& state.filters.shift() | 
					
						
							| 
									
										
										
										
											2022-04-27 14:28:19 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-27 23:31:11 +03:00
										 |  |  | 				// expand the body...
 | 
					
						
							|  |  |  | 				var ast = [...this.__parser__.expand(this, body, state)] | 
					
						
							| 
									
										
										
										
											2022-04-27 14:28:19 +03:00
										 |  |  | 				filters = state.filters | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-27 23:31:11 +03:00
										 |  |  | 				state.filters = outer_filters | 
					
						
							| 
									
										
										
										
											2022-04-27 14:28:19 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-27 23:31:11 +03:00
										 |  |  | 				// parse the body after we are done expanding...
 | 
					
						
							| 
									
										
										
										
											2022-04-27 14:28:19 +03:00
										 |  |  | 				yield function(state){ | 
					
						
							| 
									
										
										
										
											2022-04-27 23:31:11 +03:00
										 |  |  | 					var outer_filters = state.filters | 
					
						
							| 
									
										
										
										
											2022-04-27 14:28:19 +03:00
										 |  |  | 					state.filters = this.__parser__.normalizeFilters(filters) | 
					
						
							| 
									
										
										
										
											2022-04-27 23:31:11 +03:00
										 |  |  | 					var res = [...this.__parser__.parse(this, ast, state)] | 
					
						
							| 
									
										
										
										
											2022-04-27 14:28:19 +03:00
										 |  |  | 						.flat() | 
					
						
							|  |  |  | 						.join('')  | 
					
						
							| 
									
										
										
										
											2022-04-27 23:31:11 +03:00
										 |  |  | 					state.filters = outer_filters | 
					
						
							|  |  |  | 					return { data: res } } } }, | 
					
						
							| 
									
										
										
										
											2022-04-27 13:06:40 +03:00
										 |  |  | 		//
 | 
					
						
							|  |  |  | 		// 	@include(<path>)
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 	@include(<path> isolated recursive=<text>)
 | 
					
						
							|  |  |  | 		// 	@include(src=<path> isolated recursive=<text>)
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 	<include src=<path> .. >
 | 
					
						
							|  |  |  | 		// 		<text>
 | 
					
						
							|  |  |  | 		// 	</include>
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 		// XXX 'text' argument is changed to 'recursive'...
 | 
					
						
							|  |  |  | 		// XXX should we track recursion via the resolved (current) path 
 | 
					
						
							|  |  |  | 		// 		or the given path???
 | 
					
						
							|  |  |  | 		// XXX should this be lazy???
 | 
					
						
							|  |  |  | 		include: function(args, body, state, key='included', handler){ | 
					
						
							|  |  |  | 			// positional args...
 | 
					
						
							|  |  |  | 			var src = args.src || args[0] | 
					
						
							|  |  |  | 			var recursive = args.recursive || body | 
					
						
							| 
									
										
										
										
											2022-04-28 15:53:21 +03:00
										 |  |  | 			var isolated = this.__parser__.getPositional(args).includes('isolated') | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			if(!src){ | 
					
						
							|  |  |  | 				return '' } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			handler = handler  | 
					
						
							|  |  |  | 				?? function(){ | 
					
						
							|  |  |  | 					return this.get(src) | 
					
						
							|  |  |  | 						.parse( | 
					
						
							| 
									
										
										
										
											2022-04-28 15:53:21 +03:00
										 |  |  | 							isolated ?  | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 								{[key]: state[key]}  | 
					
						
							|  |  |  | 								: state) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// handle recursion...
 | 
					
						
							|  |  |  | 			var parent_seen = state[key] | 
					
						
							|  |  |  | 			var seen = state[key] =  | 
					
						
							|  |  |  | 				(state[key] ?? [this.location]).slice() | 
					
						
							|  |  |  | 			var target = this.match(src) | 
					
						
							|  |  |  | 			target = target instanceof Array ? | 
					
						
							|  |  |  | 				target.join(',') | 
					
						
							|  |  |  | 				: target | 
					
						
							|  |  |  | 			// recursion detected...
 | 
					
						
							|  |  |  | 			if(this.match() == this.match(src) | 
					
						
							|  |  |  | 					|| seen.includes(target)){ | 
					
						
							|  |  |  | 				if(!recursive){ | 
					
						
							|  |  |  | 					throw new Error( | 
					
						
							|  |  |  | 						'include: include recursion detected: ' | 
					
						
							|  |  |  | 							+ seen.concat([target]).join(' -> ')) } | 
					
						
							|  |  |  | 				// have the 'recursive' arg...
 | 
					
						
							|  |  |  | 				return this.__parser__.parse(this, recursive, state) } | 
					
						
							|  |  |  | 			seen.push(target) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// load the included page...
 | 
					
						
							|  |  |  | 			var res = handler.call(this) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// restore previous include chain...
 | 
					
						
							|  |  |  | 			if(parent_seen){ | 
					
						
							|  |  |  | 				state[key] = parent_seen | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				delete state[key] } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return res }, | 
					
						
							| 
									
										
										
										
											2022-04-26 03:52:28 +03:00
										 |  |  | 		source: function(args, body, state){ | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 			var src = args.src || args[0] | 
					
						
							|  |  |  | 			return this.macros.include.call(this,  | 
					
						
							|  |  |  | 				args, body, state, 'sources',  | 
					
						
							|  |  |  | 				function(){ | 
					
						
							|  |  |  | 					return this.__parser__.parse(this, this.get(src).raw, state) }) }, | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-27 13:06:40 +03:00
										 |  |  | 		// XXX this will need to quote pWiki code...
 | 
					
						
							|  |  |  | 		// 		...not sure about anything else...
 | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 		quote: function(){}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 15:53:21 +03:00
										 |  |  | 		//
 | 
					
						
							|  |  |  | 		//	<slot name=<name>/>
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		//	<slot name=<name> text=<text>/>
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		//	<slot name=<name>>
 | 
					
						
							|  |  |  | 		//		...
 | 
					
						
							|  |  |  | 		//	</slot>
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		//	Force show a slot...
 | 
					
						
							|  |  |  | 		//	<slot shown ... />
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		//	Force hide a slot...
 | 
					
						
							|  |  |  | 		//	<slot hidden ... />
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: by default only the first slot with <name> is visible, 
 | 
					
						
							|  |  |  | 		// 		all other slot with <name> will replace its content, unless
 | 
					
						
							|  |  |  | 		// 		explicit shown/hidden arguments are given.
 | 
					
						
							|  |  |  | 		// NOTE: hidden has precedence over shown if both are given.
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2022-04-27 13:06:40 +03:00
										 |  |  | 		// XXX how do we handle a slot defined within a slot????
 | 
					
						
							| 
									
										
										
										
											2022-04-28 18:01:03 +03:00
										 |  |  | 		// 		...seems that we'll fall into recursion on definition...
 | 
					
						
							| 
									
										
										
										
											2022-04-27 13:06:40 +03:00
										 |  |  | 		slot: function(args, body, state){ | 
					
						
							|  |  |  | 			var name = args.name | 
					
						
							| 
									
										
										
										
											2022-04-29 04:13:42 +03:00
										 |  |  | 			var text = args.text  | 
					
						
							|  |  |  | 				?? body  | 
					
						
							|  |  |  | 				// NOTE: this can't be undefined for .expand(..) to work 
 | 
					
						
							|  |  |  | 				// 		correctly...
 | 
					
						
							|  |  |  | 				?? [] | 
					
						
							| 
									
										
										
										
											2022-04-27 13:06:40 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			var slots = state.slots =  | 
					
						
							|  |  |  | 				state.slots  | 
					
						
							|  |  |  | 					?? {} | 
					
						
							| 
									
										
										
										
											2022-04-28 15:53:21 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 17:56:23 +03:00
										 |  |  | 			//var hidden = name in slots
 | 
					
						
							|  |  |  | 			// XXX EXPERIMENTAL
 | 
					
						
							|  |  |  | 			var pos = this.__parser__.getPositional(args) | 
					
						
							| 
									
										
										
										
											2022-04-28 15:53:21 +03:00
										 |  |  | 			var hidden =  | 
					
						
							| 
									
										
										
										
											2022-04-28 17:56:23 +03:00
										 |  |  | 				// 'hidden' has priority... 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 15:53:21 +03:00
										 |  |  | 				(pos.includes('hidden') || args.hidden) | 
					
						
							| 
									
										
										
										
											2022-04-28 17:56:23 +03:00
										 |  |  | 					// explicitly show... ()
 | 
					
						
							| 
									
										
										
										
											2022-04-28 15:53:21 +03:00
										 |  |  | 					|| ((pos.includes('shown') || args.shown) ? | 
					
						
							|  |  |  | 						false | 
					
						
							|  |  |  | 						// show first instance...
 | 
					
						
							|  |  |  | 						: name in slots) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-29 04:13:42 +03:00
										 |  |  | 			slots[name] = [...this.__parser__.expand(this, text, state)] | 
					
						
							| 
									
										
										
										
											2022-04-27 13:06:40 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 15:53:21 +03:00
										 |  |  | 			return hidden ? | 
					
						
							| 
									
										
										
										
											2022-04-27 14:28:19 +03:00
										 |  |  | 				'' | 
					
						
							|  |  |  | 				: function(state){ | 
					
						
							|  |  |  | 					return state.slots[name] } },  | 
					
						
							| 
									
										
										
										
											2022-04-30 01:39:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// XXX BUG: '<macro src="/moo/*"> <else> no moo!</else> </macro>' breaks...
 | 
					
						
							|  |  |  | 		// 		...seams to be a bug in the parser...
 | 
					
						
							|  |  |  | 		macro: function(args, body, state){ | 
					
						
							|  |  |  | 			// XXX
 | 
					
						
							|  |  |  | 			//return
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var name = args.name ?? args[0] | 
					
						
							|  |  |  | 			var src = args.src | 
					
						
							|  |  |  | 			var sort = (args.sort ?? '') | 
					
						
							|  |  |  | 				.split(/\s+/g) | 
					
						
							|  |  |  | 				.filter(function(e){  | 
					
						
							|  |  |  | 					return e != '' }) | 
					
						
							|  |  |  | 			var text = args.text  | 
					
						
							|  |  |  | 				?? body  | 
					
						
							|  |  |  | 				?? [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(name){ | 
					
						
							|  |  |  | 				// define new named macro...
 | 
					
						
							|  |  |  | 				if(text){ | 
					
						
							| 
									
										
										
										
											2022-04-30 11:00:30 +03:00
										 |  |  | 					;(state.macros = state.macros ?? {})[name] = text | 
					
						
							| 
									
										
										
										
											2022-04-30 01:39:23 +03:00
										 |  |  | 				// use existing macro...
 | 
					
						
							| 
									
										
										
										
											2022-04-30 11:00:30 +03:00
										 |  |  | 				} else if(state.macros  | 
					
						
							|  |  |  | 						&& name in state.macros){ | 
					
						
							| 
									
										
										
										
											2022-04-30 01:39:23 +03:00
										 |  |  | 					text = state.macros[name] } } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(src){ | 
					
						
							|  |  |  | 				var pages = this.get(src).each() | 
					
						
							| 
									
										
										
										
											2022-04-30 11:00:30 +03:00
										 |  |  | 				console.log('---', pages.length) | 
					
						
							| 
									
										
										
										
											2022-04-30 01:39:23 +03:00
										 |  |  | 				// no matching pages -> get the else block...
 | 
					
						
							|  |  |  | 				if(pages.length == 0 && text){ | 
					
						
							|  |  |  | 					// XXX get the else block...
 | 
					
						
							|  |  |  | 					var else_block = (text ?? []) | 
					
						
							|  |  |  | 						.filter(function(e){  | 
					
						
							|  |  |  | 							return typeof(e) != 'string'  | 
					
						
							|  |  |  | 								&& e.name == 'else' })  | 
					
						
							| 
									
										
										
										
											2022-04-30 11:00:30 +03:00
										 |  |  | 					if(else_block.length == 0){ | 
					
						
							|  |  |  | 						return } | 
					
						
							| 
									
										
										
										
											2022-04-30 01:39:23 +03:00
										 |  |  | 					// XXX do we take the first or the last (now) block???
 | 
					
						
							|  |  |  | 					else_block = else_block.pop() | 
					
						
							|  |  |  | 					else_block =  | 
					
						
							|  |  |  | 						else_block.args.text  | 
					
						
							|  |  |  | 							?? else_block.body | 
					
						
							|  |  |  | 					return else_block ? | 
					
						
							|  |  |  | 						this.__parser__.expand(this, else_block, state) | 
					
						
							| 
									
										
										
										
											2022-04-30 11:00:30 +03:00
										 |  |  | 						: undefined } | 
					
						
							| 
									
										
										
										
											2022-04-30 01:39:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				// sort pages...
 | 
					
						
							|  |  |  | 				if(sort.length > 0){ | 
					
						
							|  |  |  | 					// XXX
 | 
					
						
							|  |  |  | 					throw new Error('macro sort: not implemented') | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// XXX apply macro text...
 | 
					
						
							|  |  |  | 				return pages | 
					
						
							|  |  |  | 					.map(function(page){ | 
					
						
							|  |  |  | 						return this.__parser__.expand(page, text, state) }) | 
					
						
							|  |  |  | 			} }, | 
					
						
							| 
									
										
										
										
											2022-04-27 13:06:40 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-22 00:14:41 +03:00
										 |  |  | 		// nesting rules...
 | 
					
						
							|  |  |  | 		'else': ['macro'], | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 	// page parser...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	__parser__: module.parser, | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 	parse: function(state={}){ | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 		return this.__parser__.parse(this, null, state) }, | 
					
						
							| 
									
										
										
										
											2022-04-26 03:52:28 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-25 16:00:12 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// raw page text...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: writing to .raw is the same as writing to .text...
 | 
					
						
							|  |  |  | 	// XXX FUNC handle functions as pages...
 | 
					
						
							|  |  |  | 	// XXX need to support pattern pages...
 | 
					
						
							|  |  |  | 	get raw(){ | 
					
						
							|  |  |  | 		var data = this.data | 
					
						
							|  |  |  | 		return data instanceof Function ? | 
					
						
							|  |  |  | 			// XXX FUNC not sure about this...
 | 
					
						
							|  |  |  | 			data.call(this, 'text') | 
					
						
							|  |  |  |    			: data.text	}, | 
					
						
							|  |  |  | 	set raw(value){ | 
					
						
							|  |  |  | 		this.store.update(this.location, {text: value}) }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// expanded page text...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: writing to .raw is the same as writing to .text...
 | 
					
						
							|  |  |  | 	// XXX FUNC handle functions as pages...
 | 
					
						
							|  |  |  | 	// XXX need to support pattern pages...
 | 
					
						
							|  |  |  | 	get text(){ | 
					
						
							| 
									
										
										
										
											2022-04-26 16:20:25 +03:00
										 |  |  | 		return this.parse() }, | 
					
						
							| 
									
										
										
										
											2022-04-25 16:00:12 +03:00
										 |  |  | 	set text(value){ | 
					
						
							|  |  |  | 		this.store.update(this.location, {text: value}) }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-21 18:33:08 +03:00
										 |  |  | }) | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-13 03:14:21 +03:00
										 |  |  | var WIKIWORD_PATTERN = | 
					
						
							|  |  |  | 	RegExp('('+[ | 
					
						
							|  |  |  | 		//'\\\\?(\\/|\\./|\\.\\./|>>|[A-Z][_a-z0-9]+[A-Z/])[_a-zA-Z0-9/]*',
 | 
					
						
							|  |  |  | 		'\\\\?\\/?(\\./|\\.\\./|>>|[A-Z][_a-z0-9]+[A-Z/])[_a-zA-Z0-9/]*', | 
					
						
							|  |  |  | 		'\\\\?\\[[^\\]]+\\]', | 
					
						
							|  |  |  | 	].join('|') +')', 'g') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-13 12:18:13 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-13 03:14:21 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2022-04-22 12:00:48 +03:00
										 |  |  | // XXX experiments and testing...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-13 03:14:21 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-23 10:56:24 +03:00
										 |  |  | // NOTE: in general the root wiki api is simply a page instance.
 | 
					
						
							|  |  |  | // XXX not yet sure how to organize the actual alient -- UI, hooks, .. etc
 | 
					
						
							| 
									
										
										
										
											2022-04-25 20:53:20 +03:00
										 |  |  | var pwiki = | 
					
						
							| 
									
										
										
										
											2022-04-22 12:00:48 +03:00
										 |  |  | module.pwiki =  | 
					
						
							|  |  |  | Page('/', '/',  | 
					
						
							|  |  |  | 	Object.assign( | 
					
						
							|  |  |  | 		Object.create(store),  | 
					
						
							|  |  |  | 		require('./bootstrap'))) | 
					
						
							| 
									
										
										
										
											2022-04-13 12:18:13 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-13 03:14:21 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-25 20:53:20 +03:00
										 |  |  | // XXX TEST...
 | 
					
						
							| 
									
										
										
										
											2022-04-27 13:06:40 +03:00
										 |  |  | // XXX add filter tests...
 | 
					
						
							| 
									
										
										
										
											2022-04-25 20:53:20 +03:00
										 |  |  | console.log('loading test page...') | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | pwiki | 
					
						
							|  |  |  | 	.update({ | 
					
						
							|  |  |  | 		location: '/page', | 
					
						
							|  |  |  | 		text: 'PAGE\n' | 
					
						
							|  |  |  | 			+'\n' | 
					
						
							|  |  |  | 			// XXX BUG this is parsed incorrectly -- macro pattern...
 | 
					
						
							|  |  |  | 			//+'@include(/test recursive="Recursion type 2 (<now/>)")\n',
 | 
					
						
							| 
									
										
										
										
											2022-04-27 13:06:40 +03:00
										 |  |  | 			+'@include(/test recursive="Recursion type 2 <now/>")\n' | 
					
						
							|  |  |  | 			+'\n' | 
					
						
							|  |  |  | 			+'@slot(name=b text="filled slot")\n', | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 	.update({ | 
					
						
							|  |  |  | 		location: '/other', | 
					
						
							|  |  |  | 		text: 'OTHER', | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	.update({ | 
					
						
							|  |  |  | 		location: '/test', | 
					
						
							|  |  |  | 		text: 'TEST\n' | 
					
						
							| 
									
										
										
										
											2022-04-27 13:06:40 +03:00
										 |  |  | 			+'\n' | 
					
						
							|  |  |  | 			+'globally filtered test text...\n' | 
					
						
							|  |  |  | 			+'\n' | 
					
						
							|  |  |  | 			+'<filter -test>...unfiltered test text</filter>\n' | 
					
						
							|  |  |  | 			+'\n' | 
					
						
							| 
									
										
										
										
											2022-04-27 14:28:19 +03:00
										 |  |  | 			//+'<filter test>locally filtered test text</filter>\n'
 | 
					
						
							|  |  |  | 			+'\n' | 
					
						
							| 
									
										
										
										
											2022-04-27 13:06:40 +03:00
										 |  |  | 			+'@slot(name=a text="non-filled slot")\n' | 
					
						
							|  |  |  | 			+'\n' | 
					
						
							|  |  |  | 			+'@slot(name=b text="non-filled slot")\n' | 
					
						
							|  |  |  | 			+'\n' | 
					
						
							| 
									
										
										
										
											2022-04-26 20:58:09 +03:00
										 |  |  | 			+'Including /other #1: @include(/other)\n' | 
					
						
							|  |  |  | 			+'Including /other #2: @include(/other)\n' | 
					
						
							|  |  |  | 			+'\n' | 
					
						
							|  |  |  | 			// XXX BUG this is parsed incorrectly -- macro pattern...
 | 
					
						
							|  |  |  | 			//+'Including /test: @include(/test recursive="Recursion type 1 (<now/>)")\n'
 | 
					
						
							|  |  |  | 			+'Including /test: @include(/test recursive="Recursion type 1 <now/>")\n' | 
					
						
							|  |  |  | 			+'\n' | 
					
						
							|  |  |  | 			+'Including /page: @include(/page)\n' | 
					
						
							|  |  |  | 			+'\n' | 
					
						
							|  |  |  | 			+'Including /: \\@include(/)\n' | 
					
						
							|  |  |  | 			+'\n' | 
					
						
							|  |  |  | 			+'@filter(test)', | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-04-25 20:53:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-12 01:31:28 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /********************************************************************** | 
					
						
							| 
									
										
										
										
											2022-04-28 14:12:02 +03:00
										 |  |  | * vim:set ts=4 sw=4 nowrap :                        */ return module }) |