| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | /********************************************************************** | 
					
						
							|  |  |  | *  | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | **********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX do a caret api...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX only for text areas...
 | 
					
						
							|  |  |  | var atLine = function(elem, index){ | 
					
						
							|  |  |  | 	// XXX add support for range...
 | 
					
						
							|  |  |  | 	var text = elem.value | 
					
						
							|  |  |  | 	var lines = text.split(/\n/g).length | 
					
						
							|  |  |  | 	var line = elem.caretLine  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX STUB index handling...
 | 
					
						
							|  |  |  | 	if((index == -1 && line == lines)  | 
					
						
							|  |  |  | 			|| (index == 0 && line == 1)){ | 
					
						
							|  |  |  | 		return true } | 
					
						
							|  |  |  | 	return false } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | /* | 
					
						
							|  |  |  | function clickPoint(x,y){ | 
					
						
							|  |  |  | 	document | 
					
						
							|  |  |  | 		.elementFromPoint(x, y) | 
					
						
							|  |  |  | 		.dispatchEvent( | 
					
						
							|  |  |  | 			new MouseEvent( 'click', {  | 
					
						
							|  |  |  | 				view: window, | 
					
						
							|  |  |  | 				bubbles: true, | 
					
						
							|  |  |  | 				cancelable: true, | 
					
						
							|  |  |  | 				screenX: x,  | 
					
						
							|  |  |  | 				screenY: y,  | 
					
						
							|  |  |  | 			} )) } | 
					
						
							|  |  |  | //*/
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-04 15:33:07 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-18 23:30:15 +03:00
										 |  |  | var getCharOffset = function(elem, x, y, c){ | 
					
						
							|  |  |  | 	c = c ?? 0 | 
					
						
							|  |  |  | 	var r = document.createRange() | 
					
						
							|  |  |  | 	for(var e of [...elem.childNodes]){ | 
					
						
							|  |  |  | 		if(e instanceof Text){ | 
					
						
							|  |  |  | 			for(var i=0; i < e.length; i++){ | 
					
						
							|  |  |  | 				r.setStart(e, i) | 
					
						
							|  |  |  | 				r.setEnd(e, i) | 
					
						
							|  |  |  | 				var b = r.getBoundingClientRect() | 
					
						
							|  |  |  | 				// found target...
 | 
					
						
							|  |  |  | 				if(b.x >= x  | 
					
						
							|  |  |  | 						&& b.y <= y  | 
					
						
							|  |  |  | 						&& b.bottom >= y){ | 
					
						
							|  |  |  | 					return c + i } } | 
					
						
							|  |  |  | 			c += i | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			var res = getCharOffset(e, x, y, c) | 
					
						
							|  |  |  | 			if(!(res instanceof Array)){ | 
					
						
							|  |  |  | 				return res }  | 
					
						
							|  |  |  | 			;[c, res] = res } } | 
					
						
							|  |  |  | 	return arguments.length > 3 ? | 
					
						
							|  |  |  | 		[c, null] | 
					
						
							|  |  |  | 		: null } | 
					
						
							|  |  |  | var getMarkdownOffset = function(markdown, text, i){ | 
					
						
							|  |  |  | 	i = i ?? text.length | 
					
						
							|  |  |  | 	var m = 0 | 
					
						
							|  |  |  | 	// walk both strings skipping/counting non-matching stuff...
 | 
					
						
							|  |  |  | 	for(var n=0; n < i; n++, m++){ | 
					
						
							|  |  |  | 		var c = text[n] | 
					
						
							|  |  |  | 		while(c != markdown[m]){ | 
					
						
							|  |  |  | 			m++ } } | 
					
						
							|  |  |  | 	return m - n } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2023-10-14 15:46:42 +03:00
										 |  |  | // Plugins...
 | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-14 15:46:42 +03:00
										 |  |  | // general helpers and utils...
 | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | var plugin = { | 
					
						
							| 
									
										
										
										
											2023-10-14 15:46:42 +03:00
										 |  |  | 	encode: function(text){ | 
					
						
							|  |  |  | 		return text | 
					
						
							|  |  |  | 			.replace(/(?<!\\)&/g, '&') | 
					
						
							|  |  |  | 			.replace(/(?<!\\)</g, '<') | 
					
						
							|  |  |  | 			.replace(/(?<!\\)>/g, '>') | 
					
						
							|  |  |  | 			.replace(/\\(?!`)/g, '\\\\') }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 	// XXX make this more generic...
 | 
					
						
							|  |  |  | 	style: function(editor, elem, style, code=undefined){ | 
					
						
							|  |  |  | 		style = [style].flat() | 
					
						
							|  |  |  | 		editor.__styles = [...new Set([ | 
					
						
							|  |  |  | 			...(editor.__styles ?? []), | 
					
						
							|  |  |  | 			...style, | 
					
						
							|  |  |  | 		])] | 
					
						
							|  |  |  | 		return function(_, text){ | 
					
						
							|  |  |  | 			elem.style ??= [] | 
					
						
							|  |  |  | 			elem.style.push(...style) | 
					
						
							|  |  |  | 			return code  | 
					
						
							|  |  |  | 				?? text } }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -      
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var attributes = { | 
					
						
							|  |  |  | 	__proto__: plugin, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	__pre_parse__: function(text, editor, elem){ | 
					
						
							|  |  |  | 		return text  | 
					
						
							|  |  |  | 			// hidden attributes...
 | 
					
						
							|  |  |  | 			// XXX make this generic...
 | 
					
						
							|  |  |  | 			// collapsed...
 | 
					
						
							|  |  |  | 			.replace(/(\n|^)\s*collapsed::\s*(.*)\s*(\n|$)/,  | 
					
						
							|  |  |  | 				function(_, value){ | 
					
						
							|  |  |  | 					elem.collapsed = value.trim() == 'true' | 
					
						
							|  |  |  | 					return '' }) | 
					
						
							|  |  |  | 			// id...
 | 
					
						
							|  |  |  | 			.replace(/(\n|^)\s*id::\s*(.*)\s*(\n|$)/,  | 
					
						
							|  |  |  | 				function(_, value){ | 
					
						
							|  |  |  | 					elem.id = value.trim() | 
					
						
							|  |  |  | 					return '' }) }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -      
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var blocks = { | 
					
						
							|  |  |  | 	__proto__: plugin, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	__pre_parse__: function(text, editor, elem){ | 
					
						
							|  |  |  | 		return text  | 
					
						
							|  |  |  | 			// markdown...
 | 
					
						
							|  |  |  | 			// style: headings...
 | 
					
						
							|  |  |  | 			.replace(/^(?<!\\)######\s+(.*)$/m, this.style(editor, elem, 'heading-6')) | 
					
						
							|  |  |  | 			.replace(/^(?<!\\)#####\s+(.*)$/m, this.style(editor, elem, 'heading-5')) | 
					
						
							|  |  |  | 			.replace(/^(?<!\\)####\s+(.*)$/m, this.style(editor, elem, 'heading-4')) | 
					
						
							|  |  |  | 			.replace(/^(?<!\\)###\s+(.*)$/m, this.style(editor, elem, 'heading-3')) | 
					
						
							|  |  |  | 			.replace(/^(?<!\\)##\s+(.*)$/m, this.style(editor, elem, 'heading-2')) | 
					
						
							|  |  |  | 			.replace(/^(?<!\\)#\s+(.*)$/m, this.style(editor, elem, 'heading-1')) | 
					
						
							|  |  |  | 			// style: list...
 | 
					
						
							|  |  |  | 			//.replace(/^(?<!\\)[-\*]\s+(.*)$/m, style('list-item'))
 | 
					
						
							|  |  |  | 			.replace(/^\s*(.*)(?<!\\):\s*$/m, this.style(editor, elem, 'list')) | 
					
						
							|  |  |  | 			.replace(/^\s*(.*)(?<!\\)#\s*$/m, this.style(editor, elem, 'numbered-list')) | 
					
						
							|  |  |  | 			// style: misc...
 | 
					
						
							|  |  |  | 			.replace(/^\s*(?<!\\)>\s+(.*)$/m, this.style(editor, elem, 'quote')) | 
					
						
							|  |  |  | 			.replace(/^\s*(?<!\\)((\/\/|;)\s+.*)$/m, this.style(editor, elem, 'comment')) | 
					
						
							|  |  |  | 			.replace(/^\s*(?<!\\)NOTE:?\s*(.*)$/m, this.style(editor, elem, 'NOTE')) | 
					
						
							|  |  |  | 			.replace(/^\s*(?<!\\)XXX\s+(.*)$/m, this.style(editor, elem, 'XXX')) | 
					
						
							|  |  |  | 			.replace(/^(.*)\s*(?<!\\)XXX$/m, this.style(editor, elem, 'XXX')) } , | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -      
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX add actions...
 | 
					
						
							|  |  |  | var quoted = { | 
					
						
							|  |  |  | 	__proto__: plugin, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | 	// can be used in:
 | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 	// 		<string>.replace(quoted.pattern, quoted.handler)
 | 
					
						
							|  |  |  | 	quote_pattern: /(?<!\\)`(?=[^\s])(([^`]|\\`)*[^\s])(?<!\\)`/gm, | 
					
						
							|  |  |  | 	quote: function(_, code){ | 
					
						
							|  |  |  | 		return `<code>${ this.encode(code) }</code>` }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pre_pattern: /(?<!\\)```(.*\s*\n)((\n|.)*?)\h*(?<!\\)```/g, | 
					
						
							|  |  |  | 	pre: function(_, language, code){ | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | 		language = language.trim() | 
					
						
							|  |  |  | 		language = language ? | 
					
						
							|  |  |  | 			'language-'+language | 
					
						
							|  |  |  | 			: language | 
					
						
							|  |  |  | 		return `<pre>` | 
					
						
							|  |  |  | 				+`<code contenteditable="true" class="${language}">${  | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 					this.encode(code) | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | 				}</code>` | 
					
						
							|  |  |  | 			+`</pre>` }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	map: function(text, func){ | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 		return text.replace(this.pre_pattern, func) }, | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | 	replace: function(text, index, updated){ | 
					
						
							|  |  |  | 		return this.map(text,  | 
					
						
							|  |  |  | 			function(match, language, code){ | 
					
						
							|  |  |  | 				return index-- != 0 ? | 
					
						
							|  |  |  | 					match | 
					
						
							|  |  |  | 					: ('```'+language | 
					
						
							|  |  |  | 						+ (typeof(updated) == 'function' ? | 
					
						
							|  |  |  | 							updated(code) | 
					
						
							|  |  |  | 							: updated) | 
					
						
							|  |  |  | 						+'```') }) }, | 
					
						
							|  |  |  | 	toHTML: function(text){ | 
					
						
							|  |  |  | 		return this.map(text, this.handler) }, | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	__pre_parse__: function(text, editor, elem){ | 
					
						
							|  |  |  | 		return text | 
					
						
							|  |  |  | 			.replace(this.pre_pattern, this.pre.bind(this))  | 
					
						
							|  |  |  | 			.replace(this.quote_pattern, this.quote.bind(this)) }, | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// XXX is this a good strategy???
 | 
					
						
							|  |  |  | 	__state: undefined, | 
					
						
							|  |  |  | 	__keydown__: function(evt, editor, elem){ | 
					
						
							|  |  |  | 		// code editing...
 | 
					
						
							|  |  |  | 		if(elem.nodeName == 'CODE'  | 
					
						
							|  |  |  | 				&& elem.getAttribute('contenteditable') == 'true'){ | 
					
						
							|  |  |  | 			// XXX can keydown and keyup be triggered from different elements???
 | 
					
						
							|  |  |  | 			this.__state = elem.innerText | 
					
						
							|  |  |  | 			return false } }, | 
					
						
							|  |  |  | 	// defined <plugin>.__editedview__(..) handler
 | 
					
						
							|  |  |  | 	__keyup__: function(evt, editor, elem){ | 
					
						
							|  |  |  | 		var elem = evt.target | 
					
						
							|  |  |  | 		if(elem.nodeName == 'CODE'  | 
					
						
							|  |  |  | 				&& elem.getAttribute('contenteditable') == 'true'){ | 
					
						
							|  |  |  | 			// trigger if state actually changed..
 | 
					
						
							|  |  |  | 			this.__state != elem.innerText | 
					
						
							|  |  |  | 				&& editor.runPlugins('__editedview__', evt, editor, elem) } }, | 
					
						
							|  |  |  | 	__focusout__: function(){ | 
					
						
							|  |  |  | 		this.__state = undefined }, | 
					
						
							|  |  |  | 	__editedview__: function(evt, editor, elem){ | 
					
						
							|  |  |  | 		// editable code...
 | 
					
						
							|  |  |  | 		var block = editor.get(elem) | 
					
						
							|  |  |  | 		var code = block.querySelector('.code') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var update = elem.innerText | 
					
						
							|  |  |  | 		var i = [...block | 
					
						
							|  |  |  | 				.querySelectorAll('.view code[contenteditable=true]')] | 
					
						
							|  |  |  | 			.indexOf(elem) | 
					
						
							|  |  |  | 		// update element content...
 | 
					
						
							|  |  |  | 		code.value = quoted.replace(code.value, i, update) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-18 14:41:49 +03:00
										 |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -      
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var tasks = { | 
					
						
							|  |  |  | 	__proto__: plugin, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-18 19:48:00 +03:00
										 |  |  | 	done_patterns: [ | 
					
						
							|  |  |  | 		/^\s*(?<!\\)DONE\s+(.*)$/m, | 
					
						
							|  |  |  | 		/^(.*)\s*(?<!\\)DONE\s*$/m, | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-18 23:30:15 +03:00
										 |  |  | 	// State...
 | 
					
						
							| 
									
										
										
										
											2023-10-18 14:41:49 +03:00
										 |  |  | 	updateStatus: function(editor, node){ | 
					
						
							|  |  |  | 		node = editor.get(node) | 
					
						
							|  |  |  | 		if(node == null){ | 
					
						
							|  |  |  | 			return this } | 
					
						
							|  |  |  | 		var state = node | 
					
						
							|  |  |  | 			.querySelector('.view') | 
					
						
							|  |  |  | 				.querySelector('.completion') | 
					
						
							|  |  |  | 		if(state){ | 
					
						
							|  |  |  | 			var c =  | 
					
						
							|  |  |  | 				((node.querySelectorAll('input[type=checkbox]:checked').length | 
					
						
							|  |  |  | 						/ node.querySelectorAll('input[type=checkbox]').length) | 
					
						
							|  |  |  | 					* 100) | 
					
						
							|  |  |  | 				.toFixed(0) | 
					
						
							|  |  |  | 			!isNaN(c) | 
					
						
							|  |  |  | 				&& state.setAttribute('completion', c +'%') } | 
					
						
							|  |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-10-18 18:44:24 +03:00
										 |  |  | 	updateBranchStatus: function(editor, node){ | 
					
						
							| 
									
										
										
										
											2023-10-18 14:41:49 +03:00
										 |  |  | 		if(!node){ | 
					
						
							|  |  |  | 			return this } | 
					
						
							|  |  |  | 		var outline = editor.outline | 
					
						
							|  |  |  | 		var p = node | 
					
						
							|  |  |  | 		while(p !== outline){ | 
					
						
							|  |  |  | 			this.updateStatus(editor, p) | 
					
						
							|  |  |  | 			p = editor.get(p, 'parent') }  | 
					
						
							|  |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-10-18 18:44:24 +03:00
										 |  |  | 	updateAllStatus: function(editor){ | 
					
						
							| 
									
										
										
										
											2023-10-18 14:41:49 +03:00
										 |  |  | 		for(var e of [...editor.outline.querySelectorAll('.block>.view .completion')]){ | 
					
						
							|  |  |  | 			this.updateStatus(editor, e) } | 
					
						
							|  |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-10-18 23:30:15 +03:00
										 |  |  | 	// Checkboxes...
 | 
					
						
							| 
									
										
										
										
											2023-10-18 18:40:04 +03:00
										 |  |  | 	getCheckbox: function(editor, elem, offset=0){ | 
					
						
							|  |  |  | 		elem = elem  | 
					
						
							|  |  |  | 			?? editor.get() | 
					
						
							|  |  |  | 		if(elem == null  | 
					
						
							|  |  |  | 				|| (offset == 0 | 
					
						
							|  |  |  | 					&& elem.type == 'checkbox')){ | 
					
						
							|  |  |  | 			return elem } | 
					
						
							|  |  |  | 		var node = editor.get(elem) | 
					
						
							|  |  |  | 		var view = node.querySelector('.view') | 
					
						
							|  |  |  | 		var cur = view.querySelector('input[type=checkbox].selected')  | 
					
						
							|  |  |  | 			?? view.querySelector('input[type=checkbox]')  | 
					
						
							|  |  |  | 		if(offset == 0 && cur == null){ | 
					
						
							|  |  |  | 			return} | 
					
						
							|  |  |  | 		var checkboxes = [...editor.outline.querySelectorAll('.view input[type=checkbox]')] | 
					
						
							|  |  |  | 		if(checkboxes.length == 0){ | 
					
						
							|  |  |  | 			return } | 
					
						
							|  |  |  | 		// no checkbox in node -> get closest to cur in offset direction...
 | 
					
						
							|  |  |  | 		if(cur == null){ | 
					
						
							|  |  |  | 			var nodes = [...editor.outline.querySelectorAll('.block')] | 
					
						
							|  |  |  | 			var checkbox_nodes = checkboxes | 
					
						
							|  |  |  | 				.map(function(e){  | 
					
						
							|  |  |  | 					return editor.get(e) }) | 
					
						
							|  |  |  | 			var i = nodes.indexOf(node) | 
					
						
							|  |  |  | 			var p, n | 
					
						
							|  |  |  | 			for(var c of checkbox_nodes){ | 
					
						
							|  |  |  | 				p = n | 
					
						
							|  |  |  | 				var j = nodes.indexOf(c) | 
					
						
							|  |  |  | 				if(j >= i){ | 
					
						
							|  |  |  | 					n = j | 
					
						
							|  |  |  | 					break } } | 
					
						
							|  |  |  | 			cur = offset < 0 ? | 
					
						
							|  |  |  | 				nodes[p]  | 
					
						
							|  |  |  | 				: nodes[n] } | 
					
						
							|  |  |  | 		var elem = cur == null ? | 
					
						
							|  |  |  | 			checkboxes.at( | 
					
						
							|  |  |  | 				offset > 0 ?  | 
					
						
							|  |  |  | 					offset -1  | 
					
						
							|  |  |  | 					: offset) | 
					
						
							|  |  |  | 			: checkboxes.at( | 
					
						
							|  |  |  | 				(checkboxes.indexOf(cur) + offset) % checkboxes.length) | 
					
						
							|  |  |  | 		return elem }, | 
					
						
							| 
									
										
										
										
											2023-10-18 18:44:24 +03:00
										 |  |  | 	updateCheckboxes: function(editor, elem){ | 
					
						
							| 
									
										
										
										
											2023-10-18 18:40:04 +03:00
										 |  |  | 		elem = this.getCheckbox(editor, elem) | 
					
						
							|  |  |  | 		var node = editor.get(elem) | 
					
						
							|  |  |  | 		var text = node.querySelector('.code') | 
					
						
							|  |  |  | 		// get the checkbox order...
 | 
					
						
							|  |  |  | 		var i = [...node.querySelectorAll('input[type=checkbox]')].indexOf(elem) | 
					
						
							|  |  |  | 		var to = elem.checked ? | 
					
						
							|  |  |  | 			'[X]' | 
					
						
							|  |  |  | 			: '[_]' | 
					
						
							|  |  |  | 		var toggle = function(m){ | 
					
						
							|  |  |  | 			return i-- == 0 ? | 
					
						
							|  |  |  | 				to | 
					
						
							|  |  |  | 				: m } | 
					
						
							|  |  |  | 		text.value = text.value.replace(/\[[Xx_]\]/g, toggle)  | 
					
						
							|  |  |  | 		return elem }, | 
					
						
							|  |  |  | 	toggleCheckbox: function(editor, checkbox, offset){ | 
					
						
							|  |  |  | 		checkbox = this.getCheckbox(editor, checkbox, offset) | 
					
						
							|  |  |  | 		if(checkbox){ | 
					
						
							|  |  |  | 			checkbox.checked = !checkbox.checked | 
					
						
							| 
									
										
										
										
											2023-10-18 18:44:24 +03:00
										 |  |  | 			this.updateCheckboxes(editor, checkbox)  | 
					
						
							|  |  |  | 			this.updateBranchStatus(editor, checkbox) } | 
					
						
							| 
									
										
										
										
											2023-10-18 18:40:04 +03:00
										 |  |  | 		return checkbox }, | 
					
						
							|  |  |  | 	selectCheckbox: function(editor, checkbox, offset){ | 
					
						
							|  |  |  | 		checkbox = this.getCheckbox(editor, checkbox, offset) | 
					
						
							|  |  |  | 		if(checkbox == null){ | 
					
						
							|  |  |  | 			return } | 
					
						
							|  |  |  | 		var checkboxes = editor.get(checkbox) | 
					
						
							|  |  |  | 			.querySelector('.view') | 
					
						
							|  |  |  | 				.querySelectorAll('input[type=checkbox]') | 
					
						
							|  |  |  | 		if(checkboxes.length == 0){ | 
					
						
							|  |  |  | 			return } | 
					
						
							|  |  |  | 		for(var c of checkboxes){ | 
					
						
							|  |  |  | 			c.classList.remove('selected') } | 
					
						
							|  |  |  | 		checkbox.classList.add('selected') | 
					
						
							|  |  |  | 		editor.show(checkbox) | 
					
						
							|  |  |  | 		return checkbox }, | 
					
						
							|  |  |  | 	nextCheckbox: function(editor, node='focused', offset=1){ | 
					
						
							|  |  |  | 		node = this.selectCheckbox(editor, node, offset) | 
					
						
							|  |  |  | 		editor.focus(node) | 
					
						
							|  |  |  | 		return node }, | 
					
						
							|  |  |  | 	prevCheckbox: function(editor, node='focused', offset=-1){ | 
					
						
							|  |  |  | 		return this.nextCheckbox(editor, node, offset) }, | 
					
						
							| 
									
										
										
										
											2023-10-18 23:30:15 +03:00
										 |  |  | 	// DONE...
 | 
					
						
							| 
									
										
										
										
											2023-10-18 19:48:00 +03:00
										 |  |  | 	toggleDone: function(editor, elem){ | 
					
						
							|  |  |  | 		var node = editor.get(elem) | 
					
						
							|  |  |  | 		if(node == null){ | 
					
						
							|  |  |  | 			return } | 
					
						
							|  |  |  | 		var text = node.querySelector('.code') | 
					
						
							|  |  |  | 		var value = text.value | 
					
						
							|  |  |  | 		var s = text.selectionStart | 
					
						
							|  |  |  | 		var e = text.selectionEnd | 
					
						
							|  |  |  | 		var l = text.value.length | 
					
						
							|  |  |  | 		if(this.done_patterns | 
					
						
							|  |  |  | 				.reduce(function(res, p){  | 
					
						
							|  |  |  | 					return res  | 
					
						
							|  |  |  | 						|| p.test(text.value) } , false)){ | 
					
						
							|  |  |  | 			for(var p of this.done_patterns){ | 
					
						
							|  |  |  | 				value = value.replace(p, '$1') } | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			value = 'DONE ' + value } | 
					
						
							|  |  |  | 		text.value = value | 
					
						
							|  |  |  | 		text.selectionStart = s + (value.length - l) | 
					
						
							|  |  |  | 		text.selectionEnd = e + (value.length - l) | 
					
						
							|  |  |  | 		editor.update(node) | 
					
						
							|  |  |  | 		return node }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-18 14:41:49 +03:00
										 |  |  | 	__setup__: function(editor){ | 
					
						
							| 
									
										
										
										
											2023-10-18 18:44:24 +03:00
										 |  |  | 		return this.updateAllStatus(editor) }, | 
					
						
							| 
									
										
										
										
											2023-10-18 20:04:39 +03:00
										 |  |  | 	__pre_parse__: function(text, editor, elem){ | 
					
						
							| 
									
										
										
										
											2023-10-18 19:48:00 +03:00
										 |  |  | 		// handle done..
 | 
					
						
							| 
									
										
										
										
											2023-10-18 20:04:39 +03:00
										 |  |  | 		var handler = this.style(editor, elem, 'DONE') | 
					
						
							| 
									
										
										
										
											2023-10-18 19:48:00 +03:00
										 |  |  | 		for(var p of this.done_patterns){ | 
					
						
							| 
									
										
										
										
											2023-10-18 20:04:39 +03:00
										 |  |  | 			text = text | 
					
						
							|  |  |  | 				.replace(p, handler) } | 
					
						
							|  |  |  | 		return text }, | 
					
						
							|  |  |  | 	__parse__: function(text, editor, elem){ | 
					
						
							| 
									
										
										
										
											2023-10-18 14:41:49 +03:00
										 |  |  | 		return text | 
					
						
							|  |  |  | 			// block checkboxes...
 | 
					
						
							|  |  |  | 			// NOTE: these are separate as we need to align block text 
 | 
					
						
							|  |  |  | 			// 		to leading chekbox...
 | 
					
						
							|  |  |  | 			.replace(/^\s*(?<!\\)\[[_ ]\]\s*/m,  | 
					
						
							|  |  |  | 				this.style(editor, elem, 'todo', '<input type="checkbox">')) | 
					
						
							|  |  |  | 			.replace(/^\s*(?<!\\)\[[Xx]\]\s*/m,  | 
					
						
							|  |  |  | 				this.style(editor, elem, 'todo', '<input type="checkbox" checked>')) | 
					
						
							|  |  |  | 			// inline checkboxes...
 | 
					
						
							|  |  |  | 			.replace(/\s*(?<!\\)\[[_ ]\]\s*/gm,  | 
					
						
							|  |  |  | 				this.style(editor, elem, 'check', '<input type="checkbox">')) | 
					
						
							|  |  |  | 			.replace(/\s*(?<!\\)\[[Xx]\]\s*/gm,  | 
					
						
							|  |  |  | 				this.style(editor, elem, 'check', '<input type="checkbox" checked>')) | 
					
						
							|  |  |  | 			// completion...
 | 
					
						
							|  |  |  | 			// XXX add support for being like a todo checkbox...
 | 
					
						
							|  |  |  | 			.replace(/(?<!\\)\[[%]\]/gm, '<span class="completion"></span>') }, | 
					
						
							| 
									
										
										
										
											2023-10-18 18:40:04 +03:00
										 |  |  | 	__focusin__: function(evt, editor, elem){ | 
					
						
							|  |  |  | 		elem.classList.contains('block') | 
					
						
							|  |  |  | 			&& this.selectCheckbox(editor, elem) }, | 
					
						
							|  |  |  | 	__editedcode__: function(evt, editor, elem){ | 
					
						
							| 
									
										
										
										
											2023-10-18 18:44:24 +03:00
										 |  |  | 		this.updateBranchStatus(editor, elem)  | 
					
						
							| 
									
										
										
										
											2023-10-18 18:40:04 +03:00
										 |  |  | 		this.selectCheckbox(editor, elem) }, | 
					
						
							| 
									
										
										
										
											2023-10-18 14:41:49 +03:00
										 |  |  | 	__click__: function(evt, editor, elem){ | 
					
						
							|  |  |  | 		// toggle checkbox...
 | 
					
						
							|  |  |  | 		if(elem.type == 'checkbox'){ | 
					
						
							|  |  |  | 			var node = editor.get(elem) | 
					
						
							| 
									
										
										
										
											2023-10-18 18:44:24 +03:00
										 |  |  | 			this.updateCheckboxes(editor, elem) | 
					
						
							|  |  |  | 			this.updateBranchStatus(editor, node)  | 
					
						
							| 
									
										
										
										
											2023-10-18 18:40:04 +03:00
										 |  |  | 			this.selectCheckbox(editor, elem)  | 
					
						
							|  |  |  | 			node.focus() }  | 
					
						
							| 
									
										
										
										
											2023-10-18 14:41:49 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -      
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX Hackish...
 | 
					
						
							|  |  |  | var syntax = { | 
					
						
							|  |  |  | 	__proto__: plugin, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	update: function(){ | 
					
						
							|  |  |  | 		window.hljs | 
					
						
							|  |  |  | 			&& hljs.highlightAll()  | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	__setup__: function(editor){ | 
					
						
							|  |  |  | 		return this.update() }, | 
					
						
							|  |  |  | 	// XXX make a local update...
 | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 	__editedcode__: function(evt, editor, elem){ | 
					
						
							|  |  |  | 		return this.update(elem) }, | 
					
						
							|  |  |  | 	__editedview__: function(evt, editor, elem){ | 
					
						
							|  |  |  | 		// XXX should we also clear the syntax???
 | 
					
						
							|  |  |  | 		delete elem.dataset.highlighted | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	// XXX this removes highlighting, can we make it update live???
 | 
					
						
							|  |  |  | 	__focusin__: function(evt, editor, elem){ | 
					
						
							|  |  |  | 		if(elem.nodeName == 'CODE'  | 
					
						
							|  |  |  | 				&& elem.getAttribute('contenteditable') == 'true'){ | 
					
						
							|  |  |  | 			elem.classList.remove('hljs') } }, | 
					
						
							|  |  |  | 	__focusout__: function(evt, editor, elem){ | 
					
						
							|  |  |  | 		if(elem.nodeName == 'CODE'  | 
					
						
							|  |  |  | 				&& elem.getAttribute('contenteditable') == 'true'){ | 
					
						
							|  |  |  | 			this.update(elem) } | 
					
						
							|  |  |  | 		return this },	 | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -      
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var tables = { | 
					
						
							|  |  |  | 	__proto__: plugin, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	__parse__: function(text, editor, elem){ | 
					
						
							|  |  |  | 		return text | 
					
						
							|  |  |  | 			.replace(/^\s*(?<!\\)\|\s*((.|\n)*)\s*\|\s*$/,  | 
					
						
							|  |  |  | 				function(_, body){ | 
					
						
							|  |  |  | 					return `<table><tr><td>${ | 
					
						
							|  |  |  | 						body | 
					
						
							|  |  |  | 							.replace(/\s*\|\s*\n\s*\|\s*/gm, '</td></tr>\n<tr><td>') | 
					
						
							|  |  |  | 							.replace(/\s*\|\s*/gm, '</td><td>') | 
					
						
							|  |  |  | 					}</td></td></table>` }) }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -      
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var styling = { | 
					
						
							|  |  |  | 	__proto__: plugin, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	__parse__: function(text, editor, elem){ | 
					
						
							|  |  |  | 		return text | 
					
						
							|  |  |  | 			// markers...
 | 
					
						
							| 
									
										
										
										
											2023-10-15 00:40:04 +03:00
										 |  |  | 			.replace(/(\s*)(?<!\\)(FEATURE[:?]|Q:|Question:|Note:)(\s*)/gm,  | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 				'$1<b class="$2">$2</b>$3') | 
					
						
							|  |  |  | 			.replace(/(\s*)(?<!\\)(ASAP|BUG|FIX|HACK|STUB|WARNING|CAUTION)(\s*)/gm,  | 
					
						
							|  |  |  | 				'$1<span class="highlight $2">$2</span>$3') | 
					
						
							|  |  |  | 			// elements...
 | 
					
						
							|  |  |  | 			.replace(/(\n|^)(?<!\\)---*\h*(\n|$)/m, '$1<hr>') | 
					
						
							|  |  |  | 			// basic styling...
 | 
					
						
							|  |  |  | 			.replace(/(?<!\\)\*(?=[^\s*])(([^*]|\\\*)*[^\s*])(?<!\\)\*/gm, '<b>$1</b>') | 
					
						
							|  |  |  | 			.replace(/(?<!\\)~(?=[^\s~])(([^~]|\\~)*[^\s~])(?<!\\)~/gm, '<s>$1</s>') | 
					
						
							| 
									
										
										
										
											2023-10-18 14:41:49 +03:00
										 |  |  | 			// XXX this can clash with '[_] .. [_]' checkboxes...
 | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 			.replace(/(?<!\\)_(?=[^\s_])(([^_]|\\_)*[^\s_])(?<!\\)_/gm, '<i>$1</i>')  | 
					
						
							|  |  |  | 			// code/quoting...
 | 
					
						
							|  |  |  | 			//.replace(/(?<!\\)`(?=[^\s])(([^`]|\\`)*[^\s])(?<!\\)`/gm, quote) 
 | 
					
						
							|  |  |  | 			// XXX support "\==" in mark...
 | 
					
						
							|  |  |  | 			.replace(/(?<!\\)==(?=[^\s])(.*[^\s])(?<!\\)==/gm, '<mark>$1</mark>')  | 
					
						
							|  |  |  | 			// links...
 | 
					
						
							|  |  |  | 			.replace(/(?<!\\)\[([^\]]*)\]\(([^)]*)\)/g, '<a href="$2">$1</a>') | 
					
						
							|  |  |  | 			.replace(/((?:https?:|ftps?:)[^\s]*)(\s*)/g, '<a href="$1">$1</a>$2') }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -      
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX use ligatures for these???
 | 
					
						
							|  |  |  | var symbols = { | 
					
						
							|  |  |  | 	__proto__: plugin, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	__parse__: function(text, editor, elem){ | 
					
						
							|  |  |  | 		return text | 
					
						
							|  |  |  | 			// characters...
 | 
					
						
							|  |  |  | 			.replace(/(?<!\\)\(i\)/gm, '🛈')  | 
					
						
							|  |  |  | 			.replace(/(?<!\\)\(c\)/gm, '©')  | 
					
						
							|  |  |  | 			.replace(/(?<!\\)\/!\\/gm, '⚠')  | 
					
						
							|  |  |  | 			.replace(/(?<!\\)---(?!-)/gm, '—')  | 
					
						
							|  |  |  | 			.replace(/(?<!\\)--(?!-)/gm, '–') }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -      
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var escaping = { | 
					
						
							|  |  |  | 	__proto__: plugin, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	__post_parse__: function(text, editor, elem){ | 
					
						
							|  |  |  | 		return text | 
					
						
							|  |  |  | 			// quoting...
 | 
					
						
							|  |  |  | 			// NOTE: this must be last...
 | 
					
						
							|  |  |  | 			.replace(/(?<!\\)\\(.)/gm, '$1') }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-09 01:32:46 +03:00
										 |  |  | // XXX experiment with a concatinative model...
 | 
					
						
							|  |  |  | // 		.get(..) -> Outline (view)
 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | var Outline = { | 
					
						
							|  |  |  | 	dom: undefined, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:41:29 +03:00
										 |  |  | 	// config...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2023-09-29 15:38:17 +03:00
										 |  |  | 	left_key_collapses: true, | 
					
						
							|  |  |  | 	right_key_expands: true, | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 	code_update_interval: 5000, | 
					
						
							| 
									
										
										
										
											2023-10-08 02:36:29 +03:00
										 |  |  | 	tab_size: 4, | 
					
						
							| 
									
										
										
										
											2023-10-11 07:16:18 +03:00
										 |  |  | 	carot_jump_edge_then_block: false, | 
					
						
							| 
									
										
										
										
											2023-09-27 15:41:29 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-18 14:41:49 +03:00
										 |  |  | 	// Plugins...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 	// The order of plugins can be significant in the following cases:
 | 
					
						
							|  |  |  | 	// 	- parsing
 | 
					
						
							|  |  |  | 	// 	- event dropping
 | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 	plugins: [ | 
					
						
							|  |  |  | 		attributes, | 
					
						
							|  |  |  | 		blocks, | 
					
						
							|  |  |  | 		quoted, | 
					
						
							| 
									
										
										
										
											2023-10-18 14:41:49 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// NOTE: this needs to be before styling to prevent it from 
 | 
					
						
							|  |  |  | 		// 		treating '[_] ... [_]' as italic...
 | 
					
						
							|  |  |  | 		tasks, | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 		styling, | 
					
						
							|  |  |  | 		tables, | 
					
						
							|  |  |  | 		symbols, | 
					
						
							| 
									
										
										
										
											2023-10-18 14:58:08 +03:00
										 |  |  | 		//syntax,
 | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// keep this last...
 | 
					
						
							|  |  |  | 		// XXX revise -- should this be external???
 | 
					
						
							|  |  |  | 		escaping, | 
					
						
							|  |  |  | 	], | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 	// NOTE: if a handler returns false it will break plugin execution...
 | 
					
						
							|  |  |  | 	// 		XXX is this the right way to go???
 | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 	runPlugins: function(method, ...args){ | 
					
						
							|  |  |  | 		for(var plugin of this.plugins){ | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 			if(method in plugin){ | 
					
						
							|  |  |  | 				if(plugin[method](...args) === false){ | 
					
						
							|  |  |  | 					return false } } }  | 
					
						
							|  |  |  | 		return true }, | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 	threadPlugins: function(method, value, ...args){ | 
					
						
							|  |  |  | 		for(var plugin of this.plugins){ | 
					
						
							|  |  |  | 			method in plugin | 
					
						
							|  |  |  | 				&& (value = plugin[method](value, ...args)) } | 
					
						
							|  |  |  | 		return value }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 	get code(){ | 
					
						
							|  |  |  | 		return this.dom.querySelector('.code') }, | 
					
						
							| 
									
										
										
										
											2023-10-04 15:33:07 +03:00
										 |  |  | 	get outline(){ | 
					
						
							|  |  |  | 		return this.dom.querySelector('.outline') }, | 
					
						
							| 
									
										
										
										
											2023-10-04 15:40:29 +03:00
										 |  |  | 	get toolbar(){ | 
					
						
							|  |  |  | 		return this.dom.querySelector('.toolbar') }, | 
					
						
							| 
									
										
										
										
											2023-10-04 15:33:07 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.get([<offset>])
 | 
					
						
							|  |  |  | 	// 	.get('focused'[, <offset>])
 | 
					
						
							|  |  |  | 	// 		-> <node>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.get('edited'[, <offset>])
 | 
					
						
							|  |  |  | 	// 		-> <node>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.get('siblings')
 | 
					
						
							|  |  |  | 	// 	.get('focused', 'siblings')
 | 
					
						
							|  |  |  | 	// 		-> <nodes>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.get('children')
 | 
					
						
							|  |  |  | 	// 	.get('focused', 'children')
 | 
					
						
							|  |  |  | 	// 		-> <nodes>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.get('next')
 | 
					
						
							|  |  |  | 	// 	.get('focused', 'next')
 | 
					
						
							|  |  |  | 	// 		-> <node>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.get('prev')
 | 
					
						
							|  |  |  | 	// 	.get('focused', 'prev')
 | 
					
						
							|  |  |  | 	// 		-> <node>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.get('all')
 | 
					
						
							|  |  |  | 	// 	.get('visible')
 | 
					
						
							|  |  |  | 	// 	.get('editable')
 | 
					
						
							|  |  |  | 	// 	.get('selected')
 | 
					
						
							|  |  |  | 	// 	.get('top')
 | 
					
						
							|  |  |  | 	// 		-> <nodes>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX add support for node ID...
 | 
					
						
							| 
									
										
										
										
											2023-10-03 16:32:29 +03:00
										 |  |  | 	// XXX need to be able to get the next elem on same level...
 | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 	get: function(node='focused', offset){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		offset = | 
					
						
							|  |  |  | 			offset == 'next' ? | 
					
						
							|  |  |  | 				1 | 
					
						
							|  |  |  | 			: offset == 'prev' ? | 
					
						
							|  |  |  | 				-1 | 
					
						
							|  |  |  | 			: offset | 
					
						
							|  |  |  | 		var outline = this.outline | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// root nodes...
 | 
					
						
							|  |  |  | 		if(node == 'top'){ | 
					
						
							|  |  |  | 			return [...outline.children] } | 
					
						
							|  |  |  | 		// groups defaulting to .outline as base...
 | 
					
						
							|  |  |  | 		if(['all', 'visible', 'editable', 'selected'].includes(node)){ | 
					
						
							|  |  |  | 			return this.get(outline, node) } | 
					
						
							|  |  |  | 		// groups defaulting to .focused as base...
 | 
					
						
							|  |  |  | 		if(['parent', 'next', 'prev', 'children', 'siblings'].includes(node)){ | 
					
						
							|  |  |  | 			return this.get('focused', node) } | 
					
						
							|  |  |  | 		// helpers...
 | 
					
						
							|  |  |  | 		var parent = function(node){ | 
					
						
							|  |  |  | 			return node === outline ? | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | 					outline | 
					
						
							|  |  |  | 				: node.parentElement === outline ? | 
					
						
							|  |  |  | 					outline | 
					
						
							|  |  |  | 				: node.parentElement.parentElement } | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 		var children = function(node){ | 
					
						
							|  |  |  | 			return node === outline ? | 
					
						
							|  |  |  | 				[...node.children] | 
					
						
							|  |  |  | 				: [...node?.lastChild?.children] } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// single base node...
 | 
					
						
							|  |  |  | 		var edited | 
					
						
							|  |  |  | 		;[node, edited] =  | 
					
						
							|  |  |  | 			typeof(node) == 'number' ? | 
					
						
							|  |  |  | 				[this.get('visible').at(node),  | 
					
						
							|  |  |  | 					edited] | 
					
						
							|  |  |  | 			: (node == 'outline' || node == 'root') ? | 
					
						
							|  |  |  | 				[outline, edited] | 
					
						
							|  |  |  | 			: node == 'focused' ? | 
					
						
							|  |  |  | 				[outline.querySelector(`.block:focus`) | 
					
						
							|  |  |  | 						|| outline.querySelector(`.code:focus`) | 
					
						
							|  |  |  | 						|| outline.querySelector('.block.focused'),  | 
					
						
							|  |  |  | 					edited] | 
					
						
							|  |  |  | 			: node == 'edited' ? | 
					
						
							|  |  |  | 				[outline.querySelector(`.code:focus`), | 
					
						
							|  |  |  | 					outline.querySelector(`.code:focus`)] | 
					
						
							|  |  |  | 			: [node , edited] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// get the .block...
 | 
					
						
							|  |  |  | 		if(node instanceof HTMLElement){ | 
					
						
							|  |  |  | 			while(node !== outline | 
					
						
							|  |  |  | 					&& !node.classList.contains('block')){ | 
					
						
							|  |  |  | 				node = node.parentElement } } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// no reference node...
 | 
					
						
							|  |  |  | 		if(node == null  | 
					
						
							|  |  |  | 				|| typeof(node) == 'string'){ | 
					
						
							|  |  |  | 			return undefined } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// parent...
 | 
					
						
							|  |  |  | 		if(offset == 'parent'){ | 
					
						
							|  |  |  | 			return edited ? | 
					
						
							|  |  |  | 				parent(node).querySelector('.code') | 
					
						
							|  |  |  | 				: parent(node) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// node groups...
 | 
					
						
							|  |  |  | 		var nodes =  | 
					
						
							|  |  |  | 			typeof(offset) == 'number' ? | 
					
						
							|  |  |  | 				this.get('visible') | 
					
						
							|  |  |  | 			: offset == 'all' ? | 
					
						
							|  |  |  | 				[...node.querySelectorAll('.block')] | 
					
						
							|  |  |  | 			: offset == 'visible' ? | 
					
						
							|  |  |  | 				[...node.querySelectorAll('.block')]  | 
					
						
							|  |  |  | 					.filter(function(e){ | 
					
						
							|  |  |  | 						return e.offsetParent != null }) | 
					
						
							|  |  |  | 			: offset == 'editable' ? | 
					
						
							|  |  |  | 				[...node.querySelectorAll('.block>.code')]  | 
					
						
							|  |  |  | 			: offset == 'selected' ? | 
					
						
							|  |  |  | 				[...node.querySelectorAll('.block[selected]')]  | 
					
						
							|  |  |  | 					.filter(function(e){ | 
					
						
							|  |  |  | 						return e.offsetParent != null })  | 
					
						
							|  |  |  | 			: offset == 'children' ? | 
					
						
							|  |  |  | 				children(node) | 
					
						
							|  |  |  | 			: offset == 'siblings' ? | 
					
						
							|  |  |  | 				children(parent(node)) | 
					
						
							|  |  |  | 			: undefined | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// get node by offset...
 | 
					
						
							|  |  |  | 		if(typeof(offset) == 'number'){ | 
					
						
							|  |  |  | 			node = nodes.at(nodes.indexOf(node) + offset)  | 
					
						
							|  |  |  | 				?? nodes[0] | 
					
						
							|  |  |  | 			edited = edited ? | 
					
						
							|  |  |  | 				node.querySelector('.code') | 
					
						
							|  |  |  | 				: edited | 
					
						
							|  |  |  | 			nodes = undefined } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return nodes !== undefined ? | 
					
						
							|  |  |  | 				edited ? | 
					
						
							|  |  |  | 					nodes | 
					
						
							|  |  |  | 						.map(function(){ | 
					
						
							|  |  |  | 							return node.querySelector('.code') }) | 
					
						
							|  |  |  | 					: nodes | 
					
						
							|  |  |  | 			: (edited  | 
					
						
							|  |  |  | 				?? node) }, | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 	at: function(index, nodes='visible'){ | 
					
						
							|  |  |  | 		return this.get(nodes).at(index) }, | 
					
						
							| 
									
										
										
										
											2023-10-11 07:31:06 +03:00
										 |  |  | 	focus: function(node='focused', offset){ | 
					
						
							|  |  |  | 		var elem = this.get(...arguments) | 
					
						
							|  |  |  | 		elem?.focus() | 
					
						
							|  |  |  | 		return elem }, | 
					
						
							|  |  |  | 	edit: function(node='focused', offset){ | 
					
						
							|  |  |  | 		var elem = this.get(...arguments) | 
					
						
							|  |  |  | 		if(elem.nodeName != 'TEXTAREA'){ | 
					
						
							|  |  |  | 			elem = elem.querySelector('textarea') } | 
					
						
							|  |  |  | 		elem?.focus() | 
					
						
							|  |  |  | 		return elem }, | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-08 02:36:29 +03:00
										 |  |  | 	update: function(node='focused', data){ | 
					
						
							|  |  |  | 		var node = this.get(node) | 
					
						
							| 
									
										
										
										
											2023-10-10 21:45:24 +03:00
										 |  |  | 		data ??= this.data(node, false) | 
					
						
							| 
									
										
										
										
											2023-10-09 01:32:46 +03:00
										 |  |  | 		typeof(data.collapsed) == 'boolean' | 
					
						
							|  |  |  | 			&& (data.collapsed ? | 
					
						
							|  |  |  | 				node.setAttribute('collapsed', '') | 
					
						
							|  |  |  | 				: node.removeAttribute('collapsed')) | 
					
						
							| 
									
										
										
										
											2023-10-11 13:13:27 +03:00
										 |  |  | 		if(data.text != null){ | 
					
						
							| 
									
										
										
										
											2023-10-08 02:36:29 +03:00
										 |  |  | 			var text = node.querySelector('textarea') | 
					
						
							|  |  |  | 			var html = node.querySelector('span') | 
					
						
							|  |  |  | 			if(this.__code2html__){ | 
					
						
							|  |  |  | 				// NOTE: we are ignoring the .collapsed attr here 
 | 
					
						
							|  |  |  | 				var parsed = this.__code2html__(data.text) | 
					
						
							|  |  |  | 				html.innerHTML = parsed.text | 
					
						
							|  |  |  | 				// heading...
 | 
					
						
							| 
									
										
										
										
											2023-10-10 21:45:24 +03:00
										 |  |  | 				node.classList.remove(...this.__styles) | 
					
						
							|  |  |  | 				parsed.style | 
					
						
							|  |  |  | 					&& node.classList.add(...parsed.style) | 
					
						
							| 
									
										
										
										
											2023-10-08 02:36:29 +03:00
										 |  |  | 			} else { | 
					
						
							|  |  |  | 				html.innerHTML = data.text } | 
					
						
							|  |  |  | 			text.value = data.text | 
					
						
							|  |  |  | 			// XXX this does not see to work until we click in the textarea...
 | 
					
						
							|  |  |  | 			text.autoUpdateSize() } | 
					
						
							|  |  |  | 		return node }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 	indent: function(node='focused', indent=true){ | 
					
						
							|  |  |  | 		// .indent(<indent>)
 | 
					
						
							|  |  |  | 		if(node === true || node === false){ | 
					
						
							|  |  |  | 			indent = node | 
					
						
							|  |  |  | 			node = 'focused' } | 
					
						
							|  |  |  | 		var cur = this.get(node)  | 
					
						
							| 
									
										
										
										
											2023-10-13 22:19:16 +03:00
										 |  |  | 		if(!cur){  | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 			return } | 
					
						
							|  |  |  | 		var siblings = this.get(node, 'siblings') | 
					
						
							|  |  |  | 		// deindent...
 | 
					
						
							|  |  |  | 		if(!indent){ | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 			var parent = this.get(node, 'parent') | 
					
						
							| 
									
										
										
										
											2023-10-13 22:19:16 +03:00
										 |  |  | 			if(parent != this.outline){ | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 				var children = siblings | 
					
						
							|  |  |  | 					.slice(siblings.indexOf(cur)+1) | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 				parent.after(cur) | 
					
						
							|  |  |  | 				children.length > 0 | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 					&& cur.lastChild.append(...children) } | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 		// indent...
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			var parent = siblings[siblings.indexOf(cur) - 1] | 
					
						
							|  |  |  | 			if(parent){ | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 				parent.lastChild.append(cur) } }  | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 		return cur }, | 
					
						
							| 
									
										
										
										
											2023-10-01 02:58:55 +03:00
										 |  |  | 	deindent: function(node='focused', indent=false){ | 
					
						
							|  |  |  | 		return this.indent(node, indent) }, | 
					
						
							| 
									
										
										
										
											2023-10-15 00:03:22 +03:00
										 |  |  | 	shift: function(node='focused', direction){ | 
					
						
							|  |  |  | 		if(node == 'up' || node == 'down'){ | 
					
						
							|  |  |  | 			direction = node | 
					
						
							|  |  |  | 			node = 'focused' } | 
					
						
							|  |  |  | 		if(direction == null){ | 
					
						
							|  |  |  | 			return } | 
					
						
							|  |  |  | 		node = this.get(node) | 
					
						
							|  |  |  | 		var focused = node.classList.contains('focused') | 
					
						
							|  |  |  | 		var siblings = this.get(node, 'siblings') | 
					
						
							|  |  |  | 		var i = siblings.indexOf(node) | 
					
						
							|  |  |  | 		if(direction == 'up'  | 
					
						
							|  |  |  | 				&& i > 0){ | 
					
						
							|  |  |  | 			siblings[i-1].before(node) | 
					
						
							|  |  |  | 			focused  | 
					
						
							|  |  |  | 				&& this.focus() | 
					
						
							|  |  |  | 		} else if(direction == 'down'  | 
					
						
							|  |  |  | 				&& i < siblings.length-1){ | 
					
						
							|  |  |  | 			siblings[i+1].after(node)  | 
					
						
							|  |  |  | 			focused  | 
					
						
							|  |  |  | 				&& this.focus() } | 
					
						
							|  |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 	show: function(node='focused', offset){ | 
					
						
							|  |  |  | 		var node = this.get(...arguments) | 
					
						
							|  |  |  | 		var outline = this.outline | 
					
						
							|  |  |  | 		var parent = node | 
					
						
							|  |  |  | 		do{ | 
					
						
							|  |  |  | 			parent = parent.parentElement | 
					
						
							|  |  |  | 			parent.removeAttribute('collapsed') | 
					
						
							|  |  |  | 		} while(parent !== outline) | 
					
						
							|  |  |  | 		return node }, | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 	toggleCollapse: function(node='focused', state='next'){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		if(node == 'all'){ | 
					
						
							|  |  |  | 			return this.get('all') | 
					
						
							|  |  |  | 				.map(function(node){ | 
					
						
							|  |  |  | 					return that.toggleCollapse(node, state) }) } | 
					
						
							|  |  |  | 		// .toggleCollapse(<state>)
 | 
					
						
							|  |  |  | 		if(['next', true, false].includes(node)){ | 
					
						
							|  |  |  | 			state = node | 
					
						
							| 
									
										
										
										
											2023-10-04 03:00:57 +03:00
										 |  |  | 			node = 'focused' } | 
					
						
							|  |  |  | 		node = this.get(node) | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 		if(!node  | 
					
						
							|  |  |  | 				// only nodes with children can be collapsed...
 | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 				|| !node.querySelector('.block')){ | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 			return } | 
					
						
							|  |  |  | 		state = state == 'next' ? | 
					
						
							| 
									
										
										
										
											2023-10-04 03:00:57 +03:00
										 |  |  | 			node.getAttribute('collapsed') != '' | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 			: state | 
					
						
							|  |  |  | 		if(state){ | 
					
						
							|  |  |  | 			node.setAttribute('collapsed', '') | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			node.removeAttribute('collapsed') | 
					
						
							|  |  |  | 			for(var elem of [...node.querySelectorAll('textarea')]){ | 
					
						
							|  |  |  | 				elem.updateSize() } } | 
					
						
							|  |  |  | 		return node }, | 
					
						
							| 
									
										
										
										
											2023-10-03 16:12:19 +03:00
										 |  |  | 	remove: function(node='focused', offset){ | 
					
						
							| 
									
										
										
										
											2023-10-03 16:32:29 +03:00
										 |  |  | 		var elem = this.get(...arguments) | 
					
						
							|  |  |  | 		var next  | 
					
						
							|  |  |  | 		if(elem.classList.contains('focused')){ | 
					
						
							|  |  |  | 			// XXX need to be able to get the next elem on same level...
 | 
					
						
							| 
									
										
										
										
											2023-10-04 03:00:57 +03:00
										 |  |  | 			this.toggleCollapse(elem, true) | 
					
						
							| 
									
										
										
										
											2023-10-09 18:44:56 +03:00
										 |  |  | 			next = elem === this.get(-1) ? | 
					
						
							|  |  |  | 				this.get(elem, 'prev')  | 
					
						
							|  |  |  | 				: this.get(elem, 'next') } | 
					
						
							| 
									
										
										
										
											2023-10-03 16:32:29 +03:00
										 |  |  | 		elem?.remove() | 
					
						
							|  |  |  | 		next?.focus() | 
					
						
							| 
									
										
										
										
											2023-10-03 16:12:19 +03:00
										 |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-10-01 02:58:55 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 	clear: function(){ | 
					
						
							|  |  |  | 		this.outline.innerText = '' | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 	// block serialization...
 | 
					
						
							| 
									
										
										
										
											2023-10-09 01:32:46 +03:00
										 |  |  | 	// XXX need a way to filter input text...
 | 
					
						
							|  |  |  | 	// 		use-case: hidden attributes...
 | 
					
						
							| 
									
										
										
										
											2023-10-09 18:44:56 +03:00
										 |  |  | 	// NOTE: this is auto-populated by .__code2html__(..)
 | 
					
						
							|  |  |  | 	__styles: undefined, | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 	__code2html__: function(code){ | 
					
						
							| 
									
										
										
										
											2023-10-09 18:44:56 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 		var elem = { | 
					
						
							|  |  |  | 			collapsed: false, | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-09 23:58:32 +03:00
										 |  |  | 		// only whitespace -> keep element blank...
 | 
					
						
							|  |  |  | 		if(code.trim() == ''){ | 
					
						
							|  |  |  | 			elem.text = '' | 
					
						
							|  |  |  | 			return elem } | 
					
						
							| 
									
										
										
										
											2023-10-12 23:35:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// helpers...
 | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 		var run = function(stage, text){ | 
					
						
							|  |  |  | 			var meth = { | 
					
						
							|  |  |  | 				pre: '__pre_parse__', | 
					
						
							|  |  |  | 				main: '__parse__', | 
					
						
							|  |  |  | 				post: '__post_parse__', | 
					
						
							|  |  |  | 			}[stage] | 
					
						
							|  |  |  | 			return that.threadPlugins(meth, text, that, elem) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// stage: pre...
 | 
					
						
							|  |  |  | 		var text = run('pre',  | 
					
						
							|  |  |  | 			// pre-sanitize...
 | 
					
						
							|  |  |  | 			code.replace(/\x00/g, '')) | 
					
						
							|  |  |  | 		// split text into parsable and non-parsable sections...
 | 
					
						
							|  |  |  | 		var sections = text | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | 			// split fomat:
 | 
					
						
							|  |  |  | 			// 	[ text <match> <type> <body>, ... ]
 | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 			.split(/(<(pre|code)(?:|\s[^>]*)>((?:\n|.)*)<\/\2>)/g) | 
					
						
							|  |  |  | 		// sort out the sections...
 | 
					
						
							|  |  |  | 		var parsable = []  | 
					
						
							|  |  |  | 		var quoted = [] | 
					
						
							|  |  |  | 		while(sections.length > 0){ | 
					
						
							|  |  |  | 			var [section, match] = sections.splice(0, 4) | 
					
						
							|  |  |  | 			parsable.push(section) | 
					
						
							|  |  |  | 			quoted.push(match) } | 
					
						
							|  |  |  | 		// stage: main...
 | 
					
						
							|  |  |  | 		text = run('main',  | 
					
						
							|  |  |  | 				// parse only the parsable sections...
 | 
					
						
							|  |  |  | 				parsable.join('\x00')) | 
					
						
							|  |  |  | 			.split(/\x00/g) | 
					
						
							|  |  |  | 			// merge the quoted sections back in...
 | 
					
						
							|  |  |  | 			.map(function(section){ | 
					
						
							|  |  |  | 				return [section, quoted.shift() ?? '']	}) | 
					
						
							|  |  |  | 			.flat() | 
					
						
							|  |  |  | 			.join('')  | 
					
						
							|  |  |  | 		// stage: post...
 | 
					
						
							|  |  |  | 		elem.text = run('post', text)  | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 		return elem }, | 
					
						
							| 
									
										
										
										
											2023-10-15 00:03:22 +03:00
										 |  |  | 	// output format...
 | 
					
						
							| 
									
										
										
										
											2023-10-11 03:29:20 +03:00
										 |  |  | 	__code2text__: function(code){ | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 		return code  | 
					
						
							|  |  |  | 			.replace(/(\n\s*)-/g, '$1\\-') }, | 
					
						
							| 
									
										
										
										
											2023-10-11 03:29:20 +03:00
										 |  |  | 	__text2code__: function(text){ | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 		return text  | 
					
						
							|  |  |  | 			.replace(/(\n\s*)\\-/g, '$1-') }, | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:41:29 +03:00
										 |  |  | 	// serialization...
 | 
					
						
							| 
									
										
										
										
											2023-10-10 21:45:24 +03:00
										 |  |  | 	data: function(elem, deep=true){ | 
					
						
							|  |  |  | 		elem = this.get(elem)	 | 
					
						
							|  |  |  | 		return { | 
					
						
							|  |  |  | 			text: elem.querySelector('textarea').value, | 
					
						
							|  |  |  | 			collapsed: elem.getAttribute('collapsed') != null, | 
					
						
							|  |  |  | 			...(deep ?  | 
					
						
							|  |  |  | 				{children: this.json(elem)} | 
					
						
							|  |  |  | 				: {}), | 
					
						
							|  |  |  | 		} }, | 
					
						
							| 
									
										
										
										
											2023-09-27 15:41:29 +03:00
										 |  |  | 	json: function(node){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 		var children = [...(node ? | 
					
						
							|  |  |  | 			node.lastChild.children | 
					
						
							|  |  |  | 			: this.outline.children)] | 
					
						
							|  |  |  | 		return children | 
					
						
							| 
									
										
										
										
											2023-09-27 15:41:29 +03:00
										 |  |  | 			.map(function(elem){ | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 				return that.data(elem) }) }, | 
					
						
							| 
									
										
										
										
											2023-10-08 02:36:29 +03:00
										 |  |  | 	// XXX add option to customize indent size...
 | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 	text: function(node, indent, level){ | 
					
						
							|  |  |  | 		// .text(<indent>, <level>)
 | 
					
						
							|  |  |  | 		if(typeof(node) == 'string'){ | 
					
						
							|  |  |  | 			;[node, indent='  ', level=''] = [undefined, ...arguments] } | 
					
						
							| 
									
										
										
										
											2023-09-27 15:41:29 +03:00
										 |  |  | 		node ??= this.json(node) | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 		indent ??= '  ' | 
					
						
							|  |  |  | 		level ??= '' | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 		var text = [] | 
					
						
							| 
									
										
										
										
											2023-09-27 15:41:29 +03:00
										 |  |  | 		for(var elem of node){ | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 			text.push(  | 
					
						
							|  |  |  | 				level +'- ' | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 					+ this.__code2text__(elem.text) | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 						.replace(/\n/g, '\n'+ level +'  ')  | 
					
						
							|  |  |  | 					+ (elem.collapsed ? | 
					
						
							|  |  |  | 						'\n'+level+'  ' + 'collapsed:: true' | 
					
						
							|  |  |  | 						: ''), | 
					
						
							|  |  |  | 				(elem.children  | 
					
						
							|  |  |  | 						&& elem.children.length > 0) ? | 
					
						
							|  |  |  | 					this.text(elem.children || [], indent, level+indent)  | 
					
						
							|  |  |  | 					: [] ) } | 
					
						
							|  |  |  | 		return text | 
					
						
							|  |  |  | 			.flat() | 
					
						
							|  |  |  | 			.join('\n') }, | 
					
						
							| 
									
										
										
										
											2023-09-27 15:41:29 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 	parse: function(text){ | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 		text = text | 
					
						
							|  |  |  | 			.replace(/^\s*\n/, '') | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 		text = ('\n' + text) | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | 			.split(/\n(\s*)(?:- |-\s*$)/gm) | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 			.slice(1) | 
					
						
							| 
									
										
										
										
											2023-10-08 02:36:29 +03:00
										 |  |  | 		var tab = ' '.repeat(this.tab_size || 8) | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 		var level = function(lst, prev_sep=undefined, parent=[]){ | 
					
						
							|  |  |  | 			while(lst.length > 0){ | 
					
						
							| 
									
										
										
										
											2023-10-11 07:16:18 +03:00
										 |  |  | 				sep = lst[0].replace(/\t/gm, tab) | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 				// deindent...
 | 
					
						
							|  |  |  | 				if(prev_sep != null  | 
					
						
							|  |  |  | 						&& sep.length < prev_sep.length){ | 
					
						
							|  |  |  | 					break } | 
					
						
							|  |  |  | 				prev_sep ??= sep | 
					
						
							|  |  |  | 				// same level...
 | 
					
						
							|  |  |  | 				if(sep.length == prev_sep.length){ | 
					
						
							|  |  |  | 					var [_, block] = lst.splice(0, 2) | 
					
						
							|  |  |  | 					var collapsed = false | 
					
						
							|  |  |  | 					block = block | 
					
						
							|  |  |  | 						.replace(/\n\s*collapsed::\s*(.*)\s*$/,  | 
					
						
							|  |  |  | 							function(_, value){ | 
					
						
							|  |  |  | 								collapsed = value == 'true' | 
					
						
							|  |  |  | 								return '' }) | 
					
						
							|  |  |  | 					parent.push({  | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 						text: that.__text2code__(block | 
					
						
							|  |  |  | 								// normalize indent...
 | 
					
						
							|  |  |  | 								.split(new RegExp('\n'+sep+'  ', 'g')) | 
					
						
							|  |  |  | 								.join('\n')), | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 						collapsed, | 
					
						
							|  |  |  | 						children: [], | 
					
						
							|  |  |  | 					}) | 
					
						
							|  |  |  | 				// indent...
 | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					parent.at(-1).children = level(lst, sep) } } | 
					
						
							|  |  |  | 			return parent } | 
					
						
							|  |  |  | 		return level(text) }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 	// XXX should this handle children???
 | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 	// XXX revise name...
 | 
					
						
							|  |  |  | 	Block: function(data={}, place=null){ | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 		if(typeof(data) != 'object'){ | 
					
						
							|  |  |  | 			place = data | 
					
						
							|  |  |  | 			data = {} } | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// block...
 | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 		var block = document.createElement('div') | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 		block.classList.add('block') | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 		block.setAttribute('tabindex', '0') | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 		// code...
 | 
					
						
							|  |  |  | 		var code = document.createElement('textarea') | 
					
						
							| 
									
										
										
										
											2023-10-09 18:44:56 +03:00
										 |  |  | 			.autoUpdateSize() | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 		code.classList.add('code', 'text') | 
					
						
							|  |  |  | 		// view...
 | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 		var html = document.createElement('span') | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 		html.classList.add('view', 'text') | 
					
						
							|  |  |  | 		// children...
 | 
					
						
							|  |  |  | 		var children = document.createElement('div') | 
					
						
							|  |  |  | 		children.classList.add('children') | 
					
						
							|  |  |  | 		children.setAttribute('tabindex', '-1') | 
					
						
							|  |  |  | 		block.append(code, html, children) | 
					
						
							| 
									
										
										
										
											2023-10-08 02:36:29 +03:00
										 |  |  | 		this.update(block, data) | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-09 01:32:46 +03:00
										 |  |  | 		// place...
 | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 		var cur = this.get() | 
					
						
							| 
									
										
										
										
											2023-10-09 01:32:46 +03:00
										 |  |  | 		if(place && cur){ | 
					
						
							|  |  |  | 			place = place == 'prev' ? | 
					
						
							|  |  |  | 				'before' | 
					
						
							|  |  |  | 				: place | 
					
						
							|  |  |  | 			;(place == 'next'  | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 					&& (cur.querySelector('.block') | 
					
						
							| 
									
										
										
										
											2023-10-09 01:32:46 +03:00
										 |  |  | 						|| cur.nextElementSibling)) ? | 
					
						
							|  |  |  | 				this.get(place).before(block) | 
					
						
							| 
									
										
										
										
											2023-10-09 18:44:56 +03:00
										 |  |  | 			: (place == 'next'  | 
					
						
							|  |  |  | 					&& !cur.nextElementSibling) ? | 
					
						
							|  |  |  | 				cur.after(block) | 
					
						
							| 
									
										
										
										
											2023-10-09 01:32:46 +03:00
										 |  |  | 			: (place == 'before' || place == 'after') ? | 
					
						
							|  |  |  | 				cur[place](block) | 
					
						
							|  |  |  | 			: undefined } | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 		return block }, | 
					
						
							| 
									
										
										
										
											2023-10-04 15:33:07 +03:00
										 |  |  | 	load: function(data){ | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 		data = typeof(data) == 'string' ? | 
					
						
							|  |  |  | 			this.parse(data) | 
					
						
							|  |  |  | 			: data | 
					
						
							| 
									
										
										
										
											2023-10-06 17:53:28 +03:00
										 |  |  | 		// generate dom...
 | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 		var level = function(lst){ | 
					
						
							|  |  |  | 			return lst | 
					
						
							|  |  |  | 				.map(function(data){ | 
					
						
							|  |  |  | 					var elem = that.Block(data)  | 
					
						
							|  |  |  | 					if((data.children || []).length > 0){ | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 						elem.lastChild | 
					
						
							|  |  |  | 							.append(...level(data.children)) } | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 					return elem }) } | 
					
						
							|  |  |  | 		this | 
					
						
							|  |  |  | 			.clear() | 
					
						
							|  |  |  | 			.outline | 
					
						
							|  |  |  | 				.append(...level(data)) | 
					
						
							| 
									
										
										
										
											2023-10-09 01:32:46 +03:00
										 |  |  | 		// update sizes of all the textareas (transparent)...
 | 
					
						
							|  |  |  | 		for(var e of [...this.outline.querySelectorAll('textarea')]){ | 
					
						
							|  |  |  | 			e.updateSize() } | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sync: function(){ | 
					
						
							|  |  |  | 		var code = this.code | 
					
						
							|  |  |  | 		code  | 
					
						
							|  |  |  | 			&& (code.innerHTML = this.text()) | 
					
						
							| 
									
										
										
										
											2023-10-06 17:53:28 +03:00
										 |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Actions...
 | 
					
						
							|  |  |  | 	prev: function(){}, | 
					
						
							|  |  |  | 	next: function(){}, | 
					
						
							|  |  |  | 	above: function(){}, | 
					
						
							|  |  |  | 	below: function(){}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	up: function(){}, | 
					
						
							|  |  |  | 	down: function(){}, | 
					
						
							|  |  |  | 	left: function(){}, | 
					
						
							|  |  |  | 	right: function(){}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-12 23:35:14 +03:00
										 |  |  | 	// XXX move the code here into methods/actions...
 | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 	// XXX add scrollIntoView(..) to nav...
 | 
					
						
							| 
									
										
										
										
											2023-10-12 23:35:14 +03:00
										 |  |  | 	// XXX use keyboard.js...
 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 	keyboard: { | 
					
						
							|  |  |  | 		// vertical navigation...
 | 
					
						
							| 
									
										
										
										
											2023-10-11 07:16:18 +03:00
										 |  |  | 		// XXX this is a bit hacky but it works -- the caret blinks at 
 | 
					
						
							|  |  |  | 		// 		start/end of block before switching to next, would be 
 | 
					
						
							|  |  |  | 		// 		nice po prevent this...
 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 		ArrowUp: function(evt){ | 
					
						
							| 
									
										
										
										
											2023-10-11 07:16:18 +03:00
										 |  |  | 			var that = this | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 			var edited = this.get('edited') | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 			if(edited){ | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 				var line = edited.getTextGeometry().line | 
					
						
							|  |  |  | 				if(line == 0){ | 
					
						
							|  |  |  | 					evt.preventDefault()  | 
					
						
							|  |  |  | 					that.focus('edited', 'prev') } | 
					
						
							| 
									
										
										
										
											2023-10-11 07:16:18 +03:00
										 |  |  | 			} else { | 
					
						
							|  |  |  | 				evt.preventDefault()  | 
					
						
							| 
									
										
										
										
											2023-10-11 07:31:06 +03:00
										 |  |  | 				this.focus('focused', -1) } }, | 
					
						
							| 
									
										
										
										
											2023-10-11 07:16:18 +03:00
										 |  |  | 		ArrowDown: function(evt){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 			var edited = this.get('edited') | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 			if(edited){ | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 				var {line, lines} = edited.getTextGeometry() | 
					
						
							|  |  |  | 				if(line == lines -1){ | 
					
						
							|  |  |  | 					evt.preventDefault()  | 
					
						
							|  |  |  | 					that.focus('edited', 'next') } | 
					
						
							| 
									
										
										
										
											2023-10-11 07:16:18 +03:00
										 |  |  | 			} else { | 
					
						
							|  |  |  | 				evt.preventDefault()  | 
					
						
							| 
									
										
										
										
											2023-10-11 07:31:06 +03:00
										 |  |  | 				this.focus('focused', 1) } }, | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// horizontal navigation / collapse...
 | 
					
						
							|  |  |  | 		ArrowLeft: function(evt){ | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 			var edited = this.get('edited') | 
					
						
							|  |  |  | 			if(edited){ | 
					
						
							|  |  |  | 				// move caret to prev element...
 | 
					
						
							|  |  |  | 				if(edited.selectionStart == edited.selectionEnd | 
					
						
							|  |  |  | 						&& edited.selectionStart == 0){ | 
					
						
							|  |  |  | 					evt.preventDefault() | 
					
						
							| 
									
										
										
										
											2023-10-11 07:31:06 +03:00
										 |  |  | 					edited = this.focus('edited', 'prev')  | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 					edited.selectionStart =  | 
					
						
							|  |  |  | 						edited.selectionEnd = edited.value.length + 1 } | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 				return } | 
					
						
							| 
									
										
										
										
											2023-10-18 18:40:04 +03:00
										 |  |  | 			if(evt.ctrlKey){ | 
					
						
							|  |  |  | 				evt.preventDefault() | 
					
						
							|  |  |  | 				tasks.prevCheckbox(this) | 
					
						
							|  |  |  | 				return } | 
					
						
							| 
									
										
										
										
											2023-09-29 15:38:17 +03:00
										 |  |  | 			;((this.left_key_collapses  | 
					
						
							|  |  |  | 						|| evt.shiftKey) | 
					
						
							|  |  |  | 					&& this.get().getAttribute('collapsed') == null | 
					
						
							|  |  |  | 					&& this.get('children').length > 0) ? | 
					
						
							|  |  |  | 				this.toggleCollapse(true) | 
					
						
							| 
									
										
										
										
											2023-10-11 07:31:06 +03:00
										 |  |  | 				: this.focus('parent') }, | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 		ArrowRight: function(evt){ | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 			var edited = this.get('edited') | 
					
						
							|  |  |  | 			if(edited){ | 
					
						
							|  |  |  | 				// move caret to next element...
 | 
					
						
							|  |  |  | 				if(edited.selectionStart == edited.selectionEnd | 
					
						
							|  |  |  | 						&& edited.selectionStart == edited.value.length){ | 
					
						
							|  |  |  | 					evt.preventDefault() | 
					
						
							| 
									
										
										
										
											2023-10-11 07:31:06 +03:00
										 |  |  | 					edited = this.focus('edited', 'next')  | 
					
						
							| 
									
										
										
										
											2023-10-07 07:04:58 +03:00
										 |  |  | 					edited.selectionStart =  | 
					
						
							|  |  |  | 						edited.selectionEnd = 0 } | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 				return } | 
					
						
							| 
									
										
										
										
											2023-10-18 18:40:04 +03:00
										 |  |  | 			if(evt.ctrlKey){ | 
					
						
							|  |  |  | 				evt.preventDefault() | 
					
						
							|  |  |  | 				tasks.nextCheckbox(this) | 
					
						
							|  |  |  | 				return } | 
					
						
							| 
									
										
										
										
											2023-09-29 15:38:17 +03:00
										 |  |  | 			if(this.right_key_expands){ | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 				this.toggleCollapse(false)  | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 				this.focus('next') | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 			} else { | 
					
						
							|  |  |  | 				evt.shiftKey ? | 
					
						
							| 
									
										
										
										
											2023-09-28 20:49:06 +03:00
										 |  |  | 					this.toggleCollapse(false) | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 					: this.focus('next') } }, | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-15 00:03:22 +03:00
										 |  |  | 		PageUp: function(evt){ | 
					
						
							|  |  |  | 			var edited = this.get('edited') | 
					
						
							|  |  |  | 			if(!edited  | 
					
						
							|  |  |  | 					&& (evt.shiftKey  | 
					
						
							|  |  |  | 						|| evt.ctrlKey)){ | 
					
						
							|  |  |  | 				evt.preventDefault() | 
					
						
							|  |  |  | 				this.shift('up') } }, | 
					
						
							|  |  |  | 		PageDown: function(evt){ | 
					
						
							|  |  |  | 			var edited = this.get('edited') | 
					
						
							|  |  |  | 			if(!edited  | 
					
						
							|  |  |  | 					&& (evt.shiftKey  | 
					
						
							|  |  |  | 						|| evt.ctrlKey)){ | 
					
						
							|  |  |  | 				evt.preventDefault() | 
					
						
							|  |  |  | 				this.shift('down') } }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 		// indent...
 | 
					
						
							|  |  |  | 		Tab: function(evt){ | 
					
						
							|  |  |  | 			evt.preventDefault() | 
					
						
							| 
									
										
										
										
											2023-09-29 02:47:07 +03:00
										 |  |  | 			var edited = this.get('edited') | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 			var node = this.show( | 
					
						
							|  |  |  | 				this.indent(!evt.shiftKey)) | 
					
						
							|  |  |  | 			// keep focus in node...
 | 
					
						
							| 
									
										
										
										
											2023-09-29 02:47:07 +03:00
										 |  |  | 			;(edited ? | 
					
						
							|  |  |  | 				edited | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 				: node)?.focus() }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// edit mode...
 | 
					
						
							|  |  |  | 		O: function(evt){ | 
					
						
							| 
									
										
										
										
											2023-10-15 00:03:22 +03:00
										 |  |  | 			if(!this.get('edited')){ | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 				evt.preventDefault() | 
					
						
							| 
									
										
										
										
											2023-10-15 00:03:22 +03:00
										 |  |  | 				this.edit( | 
					
						
							|  |  |  | 					this.Block('before')) } }, | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 		o: function(evt){ | 
					
						
							| 
									
										
										
										
											2023-10-15 00:03:22 +03:00
										 |  |  | 			if(!this.get('edited')){ | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 				evt.preventDefault() | 
					
						
							| 
									
										
										
										
											2023-10-15 00:03:22 +03:00
										 |  |  | 				this.edit( | 
					
						
							|  |  |  | 					this.Block('next')) } }, | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 		Enter: function(evt){ | 
					
						
							| 
									
										
										
										
											2023-10-15 00:03:22 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			var edited = this.get('edited') | 
					
						
							|  |  |  | 			// edit -> split text...
 | 
					
						
							|  |  |  | 			if(edited){ | 
					
						
							|  |  |  | 				if(evt.ctrlKey | 
					
						
							|  |  |  | 						|| evt.shiftKey){ | 
					
						
							|  |  |  | 					return } | 
					
						
							|  |  |  | 				evt.preventDefault() | 
					
						
							|  |  |  | 				var a = edited.selectionStart | 
					
						
							|  |  |  | 				var b = edited.selectionEnd | 
					
						
							|  |  |  | 				var prev = edited.value.slice(0, a) | 
					
						
							|  |  |  | 				var next = edited.value.slice(b) | 
					
						
							|  |  |  | 				edited.value = prev | 
					
						
							|  |  |  | 				this.Block('next') | 
					
						
							|  |  |  | 				edited = this.edit('next') | 
					
						
							|  |  |  | 				edited.value = next | 
					
						
							|  |  |  | 				edited.selectionStart = 0 | 
					
						
							|  |  |  | 				edited.selectionEnd = 0 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 				return } | 
					
						
							| 
									
										
										
										
											2023-10-15 00:03:22 +03:00
										 |  |  | 			// view -> edit...
 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 			evt.preventDefault() | 
					
						
							| 
									
										
										
										
											2023-10-15 00:03:22 +03:00
										 |  |  | 			this.edit() }, | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 		Escape: function(evt){ | 
					
						
							| 
									
										
										
										
											2023-10-15 00:03:22 +03:00
										 |  |  | 			this.focus() }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 		Delete: function(evt){ | 
					
						
							| 
									
										
										
										
											2023-10-15 00:03:22 +03:00
										 |  |  | 			var edited = this.get('edited') | 
					
						
							|  |  |  | 			if(edited){ | 
					
						
							|  |  |  | 				if(edited.selectionStart == edited.value.length){ | 
					
						
							|  |  |  | 					var next = this.get('edited', 'next') | 
					
						
							|  |  |  | 					// can't reclaim nested children...
 | 
					
						
							|  |  |  | 					if(this.get(next, 'children').length > 0){ | 
					
						
							|  |  |  | 						return } | 
					
						
							|  |  |  | 					// do not delete past the top element...
 | 
					
						
							|  |  |  | 					if(this.get(0).querySelector('.code') === next){ | 
					
						
							|  |  |  | 						return } | 
					
						
							|  |  |  | 					evt.preventDefault() | 
					
						
							|  |  |  | 					var i = edited.value.length | 
					
						
							|  |  |  | 					edited.value += next.value | 
					
						
							|  |  |  | 					edited.selectionStart = i | 
					
						
							|  |  |  | 					edited.selectionEnd = i | 
					
						
							|  |  |  | 					this.remove(next) } | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 				return } | 
					
						
							| 
									
										
										
										
											2023-10-03 16:32:29 +03:00
										 |  |  | 			this.remove() }, | 
					
						
							| 
									
										
										
										
											2023-10-15 00:03:22 +03:00
										 |  |  | 		Backspace: function(evt){ | 
					
						
							|  |  |  | 			var edited = this.get('edited') | 
					
						
							|  |  |  | 			if(edited  | 
					
						
							| 
									
										
										
										
											2023-10-15 01:16:24 +03:00
										 |  |  | 					&& edited.selectionEnd == 0 | 
					
						
							| 
									
										
										
										
											2023-10-15 00:03:22 +03:00
										 |  |  | 					// can't reclaim nested children...
 | 
					
						
							|  |  |  | 					&& this.get(edited, 'children').length == 0){ | 
					
						
							|  |  |  | 				var prev = this.get('edited', 'prev') | 
					
						
							|  |  |  | 				// do not delete past the bottom element...
 | 
					
						
							|  |  |  | 				if(this.get(-1).querySelector('.code') === prev){ | 
					
						
							|  |  |  | 					return } | 
					
						
							|  |  |  | 				evt.preventDefault() | 
					
						
							|  |  |  | 				var i = prev.value.length | 
					
						
							|  |  |  | 				prev.value += edited.value | 
					
						
							|  |  |  | 				this.edit(prev) | 
					
						
							|  |  |  | 				prev.selectionStart = i | 
					
						
							|  |  |  | 				prev.selectionEnd = i | 
					
						
							|  |  |  | 				this.remove(edited) | 
					
						
							|  |  |  | 				return } }, | 
					
						
							| 
									
										
										
										
											2023-09-30 17:27:28 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-18 19:48:00 +03:00
										 |  |  | 		d: function(evt){ | 
					
						
							|  |  |  | 			// toggle done...
 | 
					
						
							|  |  |  | 			if(evt.ctrlKey){ | 
					
						
							|  |  |  | 				evt.preventDefault() | 
					
						
							|  |  |  | 				tasks.toggleDone(this) } }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// toggle checkbox...
 | 
					
						
							| 
									
										
										
										
											2023-09-30 17:27:28 +03:00
										 |  |  | 		' ': function(evt){ | 
					
						
							|  |  |  | 			if(this.get('edited') != null){ | 
					
						
							|  |  |  | 				return } | 
					
						
							|  |  |  | 			evt.preventDefault() | 
					
						
							| 
									
										
										
										
											2023-10-18 19:48:00 +03:00
										 |  |  | 			tasks.toggleCheckbox(this) }, | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	setup: function(dom){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		this.dom = dom | 
					
						
							| 
									
										
										
										
											2023-10-04 15:40:29 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// outline...
 | 
					
						
							| 
									
										
										
										
											2023-10-04 15:33:07 +03:00
										 |  |  | 		var outline = this.outline | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 		// update stuff already in DOM...
 | 
					
						
							| 
									
										
										
										
											2023-10-03 16:12:19 +03:00
										 |  |  | 		for(var elem of [...outline.querySelectorAll('textarea')]){ | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 			elem.autoUpdateSize() }  | 
					
						
							| 
									
										
										
										
											2023-10-08 17:42:44 +03:00
										 |  |  | 		// click...
 | 
					
						
							| 
									
										
										
										
											2023-10-18 23:30:15 +03:00
										 |  |  | 		// XXX revise...
 | 
					
						
							|  |  |  | 		// XXX tap support...
 | 
					
						
							|  |  |  | 		outline.addEventListener('mousedown',  | 
					
						
							|  |  |  | 			function(evt){ | 
					
						
							|  |  |  | 				var elem = evt.target | 
					
						
							|  |  |  | 				// correct offset in editor...
 | 
					
						
							| 
									
										
										
										
											2023-10-18 23:50:01 +03:00
										 |  |  | 				if(elem.classList.contains('code')  | 
					
						
							|  |  |  | 						&& document.activeElement !== elem){ | 
					
						
							|  |  |  | 					evt.preventDefault() | 
					
						
							| 
									
										
										
										
											2023-10-18 23:30:15 +03:00
										 |  |  | 					var view = that.get(elem).querySelector('.view') | 
					
						
							|  |  |  | 					var c = getCharOffset(view, evt.clientX, evt.clientY) | 
					
						
							| 
									
										
										
										
											2023-10-18 23:50:01 +03:00
										 |  |  | 					if(c == null){ | 
					
						
							|  |  |  | 						elem.focus() | 
					
						
							|  |  |  | 						elem.selectionStart = elem.value.length | 
					
						
							|  |  |  | 						elem.selectionEnd = elem.value.length  | 
					
						
							|  |  |  | 					} else { | 
					
						
							| 
									
										
										
										
											2023-10-18 23:30:15 +03:00
										 |  |  | 						var m = getMarkdownOffset(elem.value, view.innerText, c) | 
					
						
							|  |  |  | 						elem.focus() | 
					
						
							|  |  |  | 						elem.selectionStart = c + m | 
					
						
							|  |  |  | 						elem.selectionEnd = c + m } } }) | 
					
						
							| 
									
										
										
										
											2023-10-08 17:42:44 +03:00
										 |  |  | 		outline.addEventListener('click',  | 
					
						
							|  |  |  | 			function(evt){ | 
					
						
							|  |  |  | 				var elem = evt.target | 
					
						
							| 
									
										
										
										
											2023-10-09 19:37:08 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 				// prevent focusing parent by clicking between blocks...
 | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 				if(elem.classList.contains('children')){ | 
					
						
							|  |  |  | 					return } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | 				// empty outline -> create new eleemnt...
 | 
					
						
							|  |  |  | 				if(elem.classList.contains('outline') | 
					
						
							|  |  |  | 						&& elem.children.length == 0){ | 
					
						
							|  |  |  | 					// create new eleemnt and edit it...
 | 
					
						
							|  |  |  | 					var block = that.Block() | 
					
						
							|  |  |  | 					that.outline.append(block) | 
					
						
							|  |  |  | 					that.edit(block) | 
					
						
							|  |  |  | 					return } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-09 18:58:24 +03:00
										 |  |  | 				// expand/collapse
 | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 				if(elem.classList.contains('view')){ | 
					
						
							| 
									
										
										
										
											2023-10-09 19:37:08 +03:00
										 |  |  | 					// click: left of elem (outside)
 | 
					
						
							|  |  |  | 					if(evt.offsetX < 0){ | 
					
						
							|  |  |  | 						// XXX item menu?
 | 
					
						
							|  |  |  | 					 | 
					
						
							|  |  |  | 					// click: right of elem (outside)
 | 
					
						
							|  |  |  | 					} else if(elem.offsetWidth < evt.offsetX){ | 
					
						
							| 
									
										
										
										
											2023-10-12 03:24:34 +03:00
										 |  |  | 						that.toggleCollapse(that.get(elem)) | 
					
						
							| 
									
										
										
										
											2023-10-09 19:37:08 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 					// click inside element...
 | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						// XXX 
 | 
					
						
							| 
									
										
										
										
											2023-10-11 07:16:18 +03:00
										 |  |  | 					} } | 
					
						
							| 
									
										
										
										
											2023-10-09 19:37:08 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-11 07:16:18 +03:00
										 |  |  | 				// edit of focus...
 | 
					
						
							| 
									
										
										
										
											2023-10-18 23:30:15 +03:00
										 |  |  | 				// NOTE: this is useful if element text is hidden but the 
 | 
					
						
							| 
									
										
										
										
											2023-10-11 07:16:18 +03:00
										 |  |  | 				// 		frame is still visible...
 | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 				if(elem.classList.contains('block')){ | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 					elem.querySelector('.code').focus() } | 
					
						
							| 
									
										
										
										
											2023-10-11 07:16:18 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-14 02:51:20 +03:00
										 |  |  | 				that.runPlugins('__click__', evt, that, elem) }) | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | 		// keyboard handling...
 | 
					
						
							| 
									
										
										
										
											2023-10-03 16:12:19 +03:00
										 |  |  | 		outline.addEventListener('keydown',  | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 			function(evt){ | 
					
						
							| 
									
										
										
										
											2023-10-10 21:45:24 +03:00
										 |  |  | 				var elem = evt.target | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 				if(that.runPlugins('__keydown__', evt, that, evt.target) !== true){ | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | 					return } | 
					
						
							| 
									
										
										
										
											2023-10-10 21:45:24 +03:00
										 |  |  | 				// update element state...
 | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 				if(elem.classList.contains('code')){ | 
					
						
							| 
									
										
										
										
											2023-10-10 21:45:24 +03:00
										 |  |  | 					setTimeout(function(){ | 
					
						
							|  |  |  | 						that.update(elem.parentElement)  | 
					
						
							|  |  |  | 						elem.updateSize() }, 0) } | 
					
						
							|  |  |  | 				// handle keyboard...
 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 				evt.key in that.keyboard  | 
					
						
							|  |  |  | 					&& that.keyboard[evt.key].call(that, evt) }) | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | 		// update code block...
 | 
					
						
							|  |  |  | 		outline.addEventListener('keyup',  | 
					
						
							|  |  |  | 			function(evt){ | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 				that.runPlugins('__keyup__', evt, that, evt.target) }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 		// toggle view/code of nodes...
 | 
					
						
							| 
									
										
										
										
											2023-10-03 16:12:19 +03:00
										 |  |  | 		outline.addEventListener('focusin',  | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 			function(evt){ | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 				var elem = evt.target | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 				// ignore children container...
 | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 				if(elem.classList.contains('children')){ | 
					
						
							|  |  |  | 					return } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-03 16:12:19 +03:00
										 |  |  | 				// handle focus...
 | 
					
						
							|  |  |  | 				for(var e of [...that.dom.querySelectorAll('.focused')]){ | 
					
						
							|  |  |  | 					e.classList.remove('focused') } | 
					
						
							|  |  |  | 				that.get('focused')?.classList?.add('focused') | 
					
						
							|  |  |  | 				// textarea...
 | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 				if(elem.classList.contains('code')){ | 
					
						
							|  |  |  | 					elem.updateSize() }  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				/*/ scroll... | 
					
						
							|  |  |  | 				that.get(node).querySelector('view') | 
					
						
							|  |  |  | 					?.scrollIntoView({  | 
					
						
							|  |  |  | 						block: 'nearest',  | 
					
						
							|  |  |  | 						behavior: 'smooth', | 
					
						
							|  |  |  | 					}) | 
					
						
							|  |  |  | 				//*/
 | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				// XXX do we need this???
 | 
					
						
							|  |  |  | 				that.runPlugins('__focusin__', evt, that, elem) }) | 
					
						
							| 
									
										
										
										
											2023-10-03 16:12:19 +03:00
										 |  |  | 		outline.addEventListener('focusout',  | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 			function(evt){ | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 				var elem = evt.target | 
					
						
							|  |  |  | 				if(elem.classList.contains('code')){ | 
					
						
							|  |  |  | 					var block = elem.parentElement | 
					
						
							|  |  |  | 					that.update(block, { text: elem.value })  | 
					
						
							| 
									
										
										
										
											2023-10-18 14:58:08 +03:00
										 |  |  | 					// give the browser a chance to update the DOM...
 | 
					
						
							|  |  |  | 					// XXX revise...
 | 
					
						
							|  |  |  | 					setTimeout(function(){ | 
					
						
							|  |  |  | 						that.runPlugins('__editedcode__', evt, that, elem) }, 0) }  | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				that.runPlugins('__focusout__', evt, that, elem) }) | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 		// update .code...
 | 
					
						
							|  |  |  | 		var update_code_timeout | 
					
						
							|  |  |  | 		outline.addEventListener('change',  | 
					
						
							|  |  |  | 			function(evt){ | 
					
						
							|  |  |  | 				if(update_code_timeout){ | 
					
						
							|  |  |  | 					return } | 
					
						
							|  |  |  | 				update_code_timeout = setTimeout( | 
					
						
							|  |  |  | 					function(){ | 
					
						
							|  |  |  | 						update_code_timeout = undefined | 
					
						
							| 
									
										
										
										
											2023-10-14 22:49:02 +03:00
										 |  |  | 						that.sync()  | 
					
						
							|  |  |  | 						that.runPlugins('__change__', evt, that) },  | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 					that.code_update_interval || 5000) }) | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-04 15:40:29 +03:00
										 |  |  | 		// toolbar...
 | 
					
						
							|  |  |  | 		var toolbar = this.toolbar | 
					
						
							|  |  |  | 		if(toolbar){ | 
					
						
							|  |  |  | 			// handle return of focus when clicking toolbar...
 | 
					
						
							|  |  |  | 			var focus_textarea | 
					
						
							|  |  |  | 			var cahceNodeType = function(){ | 
					
						
							|  |  |  | 				// NOTE: for some reason .activeElement returns an element
 | 
					
						
							|  |  |  | 				// 		that is not in the DOM after the action is done...
 | 
					
						
							|  |  |  | 				focus_textarea = document.activeElement.nodeName == 'TEXTAREA' } | 
					
						
							|  |  |  | 			var refocusNode = function(){ | 
					
						
							|  |  |  | 				focus_textarea ? | 
					
						
							|  |  |  | 					editor.get().querySelector('textarea').focus()  | 
					
						
							| 
									
										
										
										
											2023-10-11 07:31:06 +03:00
										 |  |  | 					: editor.focus() | 
					
						
							| 
									
										
										
										
											2023-10-04 15:40:29 +03:00
										 |  |  | 				focus_textarea = undefined }  | 
					
						
							|  |  |  | 			// cache the focused node type before focus changes...
 | 
					
						
							|  |  |  | 			toolbar.addEventListener('mousedown', cahceNodeType) | 
					
						
							|  |  |  | 			// refocus the node after we are done...
 | 
					
						
							|  |  |  | 			toolbar.addEventListener('click', refocusNode) } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 		// code...
 | 
					
						
							|  |  |  | 		var code = this.code | 
					
						
							|  |  |  | 		if(code){ | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 			var t = Date.now() | 
					
						
							| 
									
										
										
										
											2023-10-11 03:29:20 +03:00
										 |  |  | 			this.load(code.innerHTML | 
					
						
							|  |  |  | 				.replace(/</g, '<') | 
					
						
							| 
									
										
										
										
											2023-10-12 19:45:12 +03:00
										 |  |  | 				.replace(/>/g, '>'))  | 
					
						
							|  |  |  | 			console.log(`Parse: ${Date.now() - t}ms`)} | 
					
						
							| 
									
										
										
										
											2023-10-07 17:06:54 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-14 02:42:29 +03:00
										 |  |  | 		this.runPlugins('__setup__', this) | 
					
						
							| 
									
										
										
										
											2023-10-13 22:14:36 +03:00
										 |  |  | 		 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:05:34 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /********************************************************************** | 
					
						
							|  |  |  | * vim:set ts=4 sw=4 :                                                */ |