mirror of
				https://github.com/flynx/pWiki.git
				synced 2025-10-31 02:50:08 +00:00 
			
		
		
		
	logseq-like format parsing + markdown (stub) + ff support...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
		
							parent
							
								
									697a9d8347
								
							
						
					
					
						commit
						af3f10e35b
					
				| @ -52,13 +52,6 @@ | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* show/hide node's view/code... */ | /* show/hide node's view/code... */ | ||||||
| .editor .outline [tabindex]>span+textarea:not(:focus), |  | ||||||
| .editor .outline [tabindex]:has(>span+textarea:focus)>span:first-child { |  | ||||||
| 	position: absolute; |  | ||||||
| 	opacity: 0; |  | ||||||
| 	top: 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .editor .outline [tabindex]>textarea:focus+span, | .editor .outline [tabindex]>textarea:focus+span, | ||||||
| .editor .outline [tabindex]>textarea:not(:focus) { | .editor .outline [tabindex]>textarea:not(:focus) { | ||||||
| 	position: absolute; | 	position: absolute; | ||||||
|  | |||||||
| @ -82,19 +82,6 @@ var Outline = { | |||||||
| 		return this.dom.querySelector('.toolbar') }, | 		return this.dom.querySelector('.toolbar') }, | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 	// XXX revise name...
 |  | ||||||
| 	Block: function(place=none){ |  | ||||||
| 		var block = document.createElement('div') |  | ||||||
| 		block.setAttribute('tabindex', '0') |  | ||||||
| 		block.append( |  | ||||||
| 			document.createElement('textarea') |  | ||||||
| 				.autoUpdateSize(), |  | ||||||
| 			document.createElement('span')) |  | ||||||
| 		var cur = this.get() |  | ||||||
| 		place && cur |  | ||||||
| 			&& cur[place](block) |  | ||||||
| 		return block }, |  | ||||||
| 
 |  | ||||||
| 	//
 | 	//
 | ||||||
| 	// 	.get([<offset>])
 | 	// 	.get([<offset>])
 | ||||||
| 	// 	.get('focused'[, <offset>])
 | 	// 	.get('focused'[, <offset>])
 | ||||||
| @ -287,28 +274,47 @@ var Outline = { | |||||||
| 		return this }, | 		return this }, | ||||||
| 
 | 
 | ||||||
| 	// block serialization...
 | 	// block serialization...
 | ||||||
|  | 	// XXX these should be symetrical...
 | ||||||
| 	__code2html__: function(code){ | 	__code2html__: function(code){ | ||||||
| 		return code  | 		var elem = { | ||||||
| 			.replace(/\n\s*/g, '<br>') | 			collapsed: false, | ||||||
|  | 		} | ||||||
|  | 		elem.text = code  | ||||||
|  | 			// attributes...
 | ||||||
|  | 			// XXX make this generic...
 | ||||||
|  | 			.replace(/\n\s*collapsed::\s*(.*)\s*$/,  | ||||||
|  | 				function(_, value){ | ||||||
|  | 					elem.collapsed = value.trim() == 'true' | ||||||
|  | 					return '' }) | ||||||
|  | 			// markdown...
 | ||||||
| 			// XXX STUB...
 | 			// XXX STUB...
 | ||||||
| 			.replace(/^# (.*)\s*$/g, '<h1>$1</h1>') | 			.replace(/^#\s*(.*)\s*(\n|$)/, '<h1>$1</h1>') | ||||||
| 			.replace(/^## (.*)\s*$/g, '<h2>$1</h2>') | 			.replace(/^##\s*(.*)\s*(\n|$)/, '<h2>$1</h2>') | ||||||
| 			.replace(/^### (.*)\s*$/g, '<h3>$1</h3>') | 			.replace(/^###\s*(.*)\s*(\n|$)/, '<h3>$1</h3>') | ||||||
| 			.replace(/^#### (.*)\s*$/g, '<h4>$1</h4>') | 			.replace(/^####\s*(.*)\s*(\n|$)/, '<h4>$1</h4>') | ||||||
|  | 			.replace(/^#####\s*(.*)\s*(\n|$)/, '<h5>$1</h5>') | ||||||
|  | 			.replace(/^######\s*(.*)\s*(\n|$)/, '<h6>$1</h6>') | ||||||
| 			.replace(/\*(.*)\*/g, '<b>$1</b>') | 			.replace(/\*(.*)\*/g, '<b>$1</b>') | ||||||
| 			.replace(/~([^~]*)~/g, '<s>$1</s>') | 			.replace(/~([^~]*)~/g, '<s>$1</s>') | ||||||
| 			.replace(/_([^_]*)_/g, '<i>$1</i>') }, | 			.replace(/_([^_]*)_/g, '<i>$1</i>')  | ||||||
|  | 			.replace(/(\n|^)---*\h*(\n|$)/, '$1<hr>') | ||||||
|  | 			.replace(/\n/g, '<br>\n') | ||||||
|  | 		return elem }, | ||||||
| 	__html2code__: function(html){ | 	__html2code__: function(html){ | ||||||
| 		return html  | 		return html  | ||||||
| 			.replace(/<br>\s*/g, '\n') |  | ||||||
| 			// XXX STUB...
 | 			// XXX STUB...
 | ||||||
| 			.replace(/^<h1>(.*)<\/h1>\s*$/g, '# $1') | 			.replace(/<hr>$/, '---') | ||||||
| 			.replace(/^<h2>(.*)<\/h2>\s*$/g, '## $1') | 			.replace(/<hr>/, '---\n') | ||||||
| 			.replace(/^<h3>(.*)<\/h3>\s*$/g, '### $1') | 			.replace(/^<h1>(.*)<\/h1>\s*(.*)$/g, '# $1\n$2') | ||||||
| 			.replace(/^<h4>(.*)<\/h4>\s*$/g, '#### $1') | 			.replace(/^<h2>(.*)<\/h2>\s*(.*)$/g, '## $1\n$2') | ||||||
|  | 			.replace(/^<h3>(.*)<\/h3>\s*(.*)$/g, '### $1\n$2') | ||||||
|  | 			.replace(/^<h4>(.*)<\/h4>\s*(.*)$/g, '#### $1\n$2') | ||||||
|  | 			.replace(/^<h5>(.*)<\/h5>\s*(.*)$/g, '##### $1\n$2') | ||||||
|  | 			.replace(/^<h6>(.*)<\/h6>\s*(.*)$/g, '###### $1\n$2') | ||||||
| 			.replace(/<b>(.*)<\/b>/g, '*$1*') | 			.replace(/<b>(.*)<\/b>/g, '*$1*') | ||||||
| 			.replace(/<s>(.*)<\/s>/g, '~$1~') | 			.replace(/<s>(.*)<\/s>/g, '~$1~') | ||||||
| 			.replace(/<i>(.*)<\/i>/g, '_$1_') }, | 			.replace(/<i>(.*)<\/i>/g, '_$1_') | ||||||
|  | 			.replace(/<br>\s*/g, '\n') }, | ||||||
| 
 | 
 | ||||||
| 	// serialization...
 | 	// serialization...
 | ||||||
| 	json: function(node){ | 	json: function(node){ | ||||||
| @ -341,23 +347,73 @@ var Outline = { | |||||||
| 				+ elem.text | 				+ elem.text | ||||||
| 					.replace(/\n/g, '\n'+ level +'  ')  | 					.replace(/\n/g, '\n'+ level +'  ')  | ||||||
| 				+'\n' | 				+'\n' | ||||||
|  | 				+ (elem.collapsed ? | ||||||
|  | 					level+'  ' + 'collapsed:: true\n' | ||||||
|  | 					: '') | ||||||
| 				+ this.text(elem.children || [], indent, level+indent) } | 				+ this.text(elem.children || [], indent, level+indent) } | ||||||
| 		return text }, | 		return text }, | ||||||
| 
 | 
 | ||||||
|  | 	parse: function(text){ | ||||||
|  | 		text = ('\n' + text) | ||||||
|  | 			.split(/\n(\s*)- /g) | ||||||
|  | 			.slice(1) | ||||||
|  | 		var level = function(lst, prev_sep=undefined, parent=[]){ | ||||||
|  | 			while(lst.length > 0){ | ||||||
|  | 				sep = lst[0] | ||||||
|  | 				// 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({  | ||||||
|  | 						text: block, | ||||||
|  | 						collapsed, | ||||||
|  | 						children: [], | ||||||
|  | 					}) | ||||||
|  | 				// indent...
 | ||||||
|  | 				} else { | ||||||
|  | 					parent.at(-1).children = level(lst, sep) } } | ||||||
|  | 			return parent } | ||||||
|  | 		return level(text) }, | ||||||
|  | 
 | ||||||
|  | 	// XXX revise name...
 | ||||||
|  | 	Block: function(data={}, place=null){ | ||||||
|  | 		if(typeof(data) != 'object'){ | ||||||
|  | 			place = data | ||||||
|  | 			data = {} } | ||||||
|  | 		var block = document.createElement('div') | ||||||
|  | 		block.setAttribute('tabindex', '0') | ||||||
|  | 		data.collapsed | ||||||
|  | 			&& block.setAttribute('collapsed', '') | ||||||
|  | 		var text | ||||||
|  | 		var html | ||||||
|  | 		block.append( | ||||||
|  | 			text = document.createElement('textarea') | ||||||
|  | 				.autoUpdateSize(), | ||||||
|  | 			html = document.createElement('span')) | ||||||
|  | 		if(data.text){ | ||||||
|  | 			text.value = data.text | ||||||
|  | 			html.innerHTML = this.__code2html__ ? | ||||||
|  | 				this.__code2html__(data.text) | ||||||
|  | 				: data.text } | ||||||
|  | 		var cur = this.get() | ||||||
|  | 		place && cur | ||||||
|  | 			&& cur[place](block) | ||||||
|  | 		return block }, | ||||||
| 	// XXX use .__code2html__(..)
 | 	// XXX use .__code2html__(..)
 | ||||||
| 	load: function(data){ | 	load: function(data){ | ||||||
| 		// text...
 | 		data = typeof(data) == 'string' ? | ||||||
| 		if(typeof(data) == 'string'){ | 			this.parse(data) | ||||||
| 			// XXX
 | 			: data | ||||||
| 			data = data.split(/\n(\s*)- /g) |  | ||||||
| 			var level = function(lst){ |  | ||||||
| 				while(lst.length > 0){ |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		}  |  | ||||||
| 		// json...
 |  | ||||||
| 		// XXX
 |  | ||||||
| 
 |  | ||||||
| 		// generate dom...
 | 		// generate dom...
 | ||||||
| 		// XXX
 | 		// XXX
 | ||||||
| 		return this }, | 		return this }, | ||||||
| @ -372,6 +428,8 @@ var Outline = { | |||||||
| 			if(edited){ | 			if(edited){ | ||||||
| 				if(!atLine(edited, 0)){ | 				if(!atLine(edited, 0)){ | ||||||
| 					return } | 					return } | ||||||
|  | 				/*/ | ||||||
|  | 				//*/
 | ||||||
| 				state = 'edited' } | 				state = 'edited' } | ||||||
| 			evt.preventDefault()  | 			evt.preventDefault()  | ||||||
| 			this.get(state, -1)?.focus() }, | 			this.get(state, -1)?.focus() }, | ||||||
| @ -381,16 +439,22 @@ var Outline = { | |||||||
| 			if(edited){ | 			if(edited){ | ||||||
| 				if(!atLine(edited, -1)){ | 				if(!atLine(edited, -1)){ | ||||||
| 					return } | 					return } | ||||||
| 				//window.getSelection()
 |  | ||||||
| 				state = 'edited' } | 				state = 'edited' } | ||||||
| 			evt.preventDefault()  | 			evt.preventDefault()  | ||||||
| 			this.get(state, 1)?.focus() }, | 			this.get(state, 1)?.focus() }, | ||||||
| 
 | 
 | ||||||
| 		// horizontal navigation / collapse...
 | 		// horizontal navigation / collapse...
 | ||||||
| 		// XXX if at start/end of element move to prev/next...
 |  | ||||||
| 		ArrowLeft: function(evt){ | 		ArrowLeft: function(evt){ | ||||||
| 			if(this.outline.querySelector('textarea:focus')){ | 			var edited = this.get('edited') | ||||||
| 				// XXX if at end of element move to next...
 | 			if(edited){ | ||||||
|  | 				// move caret to prev element...
 | ||||||
|  | 				if(edited.selectionStart == edited.selectionEnd | ||||||
|  | 						&& edited.selectionStart == 0){ | ||||||
|  | 					evt.preventDefault() | ||||||
|  | 					edited = this.get('edited', 'prev')  | ||||||
|  | 					edited.focus()  | ||||||
|  | 					edited.selectionStart =  | ||||||
|  | 						edited.selectionEnd = edited.value.length + 1 } | ||||||
| 				return } | 				return } | ||||||
| 			;((this.left_key_collapses  | 			;((this.left_key_collapses  | ||||||
| 						|| evt.shiftKey) | 						|| evt.shiftKey) | ||||||
| @ -399,8 +463,16 @@ var Outline = { | |||||||
| 				this.toggleCollapse(true) | 				this.toggleCollapse(true) | ||||||
| 				: this.get('parent')?.focus() }, | 				: this.get('parent')?.focus() }, | ||||||
| 		ArrowRight: function(evt){ | 		ArrowRight: function(evt){ | ||||||
| 			if(this.outline.querySelector('textarea:focus')){ | 			var edited = this.get('edited') | ||||||
| 				// XXX if at end of element move to next...
 | 			if(edited){ | ||||||
|  | 				// move caret to next element...
 | ||||||
|  | 				if(edited.selectionStart == edited.selectionEnd | ||||||
|  | 						&& edited.selectionStart == edited.value.length){ | ||||||
|  | 					evt.preventDefault() | ||||||
|  | 					edited = this.get('edited', 'next')  | ||||||
|  | 					edited.focus()  | ||||||
|  | 					edited.selectionStart =  | ||||||
|  | 						edited.selectionEnd = 0 } | ||||||
| 				return } | 				return } | ||||||
| 			if(this.right_key_expands){ | 			if(this.right_key_expands){ | ||||||
| 				this.toggleCollapse(false)  | 				this.toggleCollapse(false)  | ||||||
| @ -499,10 +571,13 @@ var Outline = { | |||||||
| 				var node = evt.target | 				var node = evt.target | ||||||
| 				if(node.nodeName == 'TEXTAREA'  | 				if(node.nodeName == 'TEXTAREA'  | ||||||
| 						&& node?.nextElementSibling?.nodeName == 'SPAN'){ | 						&& node?.nextElementSibling?.nodeName == 'SPAN'){ | ||||||
| 					node.nextElementSibling.innerHTML =  | 					if(that.__code2html__){ | ||||||
| 						that.__code2html__ ? | 						var data = that.__code2html__(node.value) | ||||||
| 							that.__code2html__(node.value) | 						node.nextElementSibling.innerHTML = data.text | ||||||
| 							: node.value } }) | 						data.collapsed  | ||||||
|  | 							&& node.parentElement.setAttribute('collapsed', '') | ||||||
|  | 					} else { | ||||||
|  | 						node.nextElementSibling.innerHTML = node.value } } }) | ||||||
| 
 | 
 | ||||||
| 		// toolbar...
 | 		// toolbar...
 | ||||||
| 		var toolbar = this.toolbar | 		var toolbar = this.toolbar | ||||||
|  | |||||||
| @ -15,20 +15,52 @@ HTMLTextAreaElement.prototype.autoUpdateSize = function(){ | |||||||
| 			that.updateSize() })  | 			that.updateSize() })  | ||||||
| 	return this } | 	return this } | ||||||
| 
 | 
 | ||||||
|  | // calculate number of lines in text area (both wrapped and actual lines)
 | ||||||
|  | Object.defineProperty(HTMLTextAreaElement.prototype, 'heightLines', { | ||||||
|  | 	enumerable: false, | ||||||
|  | 	get: function(){ | ||||||
|  | 		var style = getComputedStyle(this) | ||||||
|  | 		return Math.floor( | ||||||
|  | 			(this.scrollHeight  | ||||||
|  | 				- parseFloat(style.paddingTop) | ||||||
|  | 				- parseFloat(style.paddingBottom))  | ||||||
|  | 			/ (parseFloat(style.lineHeight)  | ||||||
|  | 				|| parseFloat(style.fontSize))) }, }) | ||||||
|  | Object.defineProperty(HTMLTextAreaElement.prototype, 'lines', { | ||||||
|  | 	enumerable: false, | ||||||
|  | 	get: function(){ | ||||||
|  | 		return this.value | ||||||
|  | 			.split(/\n/g) | ||||||
|  | 			.length }, }) | ||||||
|  | // XXX this does not account for wrapping...
 | ||||||
| Object.defineProperty(HTMLTextAreaElement.prototype, 'caretLine', { | Object.defineProperty(HTMLTextAreaElement.prototype, 'caretLine', { | ||||||
| 	enumerable: false, | 	enumerable: false, | ||||||
| 	get: function(){ | 	get: function(){ | ||||||
| 		var offset = this.selectionStart | 		var offset = this.selectionStart | ||||||
| 		console.log('---', this) |  | ||||||
| 		return offset != null ? | 		return offset != null ? | ||||||
| 			this.value | 			this.value | ||||||
| 				.slice(0, offset) | 				.slice(0, offset) | ||||||
| 				.split(/\n/g) | 				.split(/\n/g) | ||||||
| 				.length | 				.length | ||||||
| 			: undefined }, | 			: undefined }, }) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Object.defineProperty(HTMLTextAreaElement.prototype, 'caretOffset', { | ||||||
|  | 	enumerable: false, | ||||||
|  | 	get: function(){ | ||||||
|  | 		var offset = this.selectionStart | ||||||
|  | 		var r = document.createRange() | ||||||
|  | 		r.setStart(this, offset) | ||||||
|  | 		r.setEnd(this, offset) | ||||||
|  | 		var rect = r.getBoundingClientRect() | ||||||
|  | 		return { | ||||||
|  | 			top: rect.top,  | ||||||
|  | 			left: rect.left, | ||||||
|  | 		} }, | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| /********************************************************************** | /********************************************************************** | ||||||
| * vim:set ts=4 sw=4 :                                                */ | * vim:set ts=4 sw=4 :                                                */ | ||||||
|  | |||||||
| @ -69,6 +69,10 @@ var setup = function(){ | |||||||
| 
 | 
 | ||||||
| <pre> | <pre> | ||||||
| TODO: | TODO: | ||||||
|  | - caret | ||||||
|  |   - <s>go to next/prev element's start/end when moving off last/first char</s> | ||||||
|  |   - handle up/down on wrapped blocks | ||||||
|  |     <i>...can't seem to get caret line in a non-hacky way</i> | ||||||
| - persistent empty first/last node (a button to create a new node) | - persistent empty first/last node (a button to create a new node) | ||||||
| - loading from DOM -- fill textarea | - loading from DOM -- fill textarea | ||||||
| - Firefox compatibility -- remove ':has(..)' | - Firefox compatibility -- remove ':has(..)' | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user