mirror of
				https://github.com/flynx/pWiki.git
				synced 2025-10-30 18:40: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... */ | ||||
| .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:not(:focus) { | ||||
| 	position: absolute; | ||||
|  | ||||
| @ -82,19 +82,6 @@ var Outline = { | ||||
| 		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('focused'[, <offset>])
 | ||||
| @ -287,28 +274,47 @@ var Outline = { | ||||
| 		return this }, | ||||
| 
 | ||||
| 	// block serialization...
 | ||||
| 	// XXX these should be symetrical...
 | ||||
| 	__code2html__: function(code){ | ||||
| 		return code  | ||||
| 			.replace(/\n\s*/g, '<br>') | ||||
| 		var elem = { | ||||
| 			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...
 | ||||
| 			.replace(/^# (.*)\s*$/g, '<h1>$1</h1>') | ||||
| 			.replace(/^## (.*)\s*$/g, '<h2>$1</h2>') | ||||
| 			.replace(/^### (.*)\s*$/g, '<h3>$1</h3>') | ||||
| 			.replace(/^#### (.*)\s*$/g, '<h4>$1</h4>') | ||||
| 			.replace(/^#\s*(.*)\s*(\n|$)/, '<h1>$1</h1>') | ||||
| 			.replace(/^##\s*(.*)\s*(\n|$)/, '<h2>$1</h2>') | ||||
| 			.replace(/^###\s*(.*)\s*(\n|$)/, '<h3>$1</h3>') | ||||
| 			.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, '<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){ | ||||
| 		return html  | ||||
| 			.replace(/<br>\s*/g, '\n') | ||||
| 			// XXX STUB...
 | ||||
| 			.replace(/^<h1>(.*)<\/h1>\s*$/g, '# $1') | ||||
| 			.replace(/^<h2>(.*)<\/h2>\s*$/g, '## $1') | ||||
| 			.replace(/^<h3>(.*)<\/h3>\s*$/g, '### $1') | ||||
| 			.replace(/^<h4>(.*)<\/h4>\s*$/g, '#### $1') | ||||
| 			.replace(/<hr>$/, '---') | ||||
| 			.replace(/<hr>/, '---\n') | ||||
| 			.replace(/^<h1>(.*)<\/h1>\s*(.*)$/g, '# $1\n$2') | ||||
| 			.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(/<s>(.*)<\/s>/g, '~$1~') | ||||
| 			.replace(/<i>(.*)<\/i>/g, '_$1_') }, | ||||
| 			.replace(/<i>(.*)<\/i>/g, '_$1_') | ||||
| 			.replace(/<br>\s*/g, '\n') }, | ||||
| 
 | ||||
| 	// serialization...
 | ||||
| 	json: function(node){ | ||||
| @ -341,23 +347,73 @@ var Outline = { | ||||
| 				+ elem.text | ||||
| 					.replace(/\n/g, '\n'+ level +'  ')  | ||||
| 				+'\n' | ||||
| 				+ (elem.collapsed ? | ||||
| 					level+'  ' + 'collapsed:: true\n' | ||||
| 					: '') | ||||
| 				+ this.text(elem.children || [], indent, level+indent) } | ||||
| 		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__(..)
 | ||||
| 	load: function(data){ | ||||
| 		// text...
 | ||||
| 		if(typeof(data) == 'string'){ | ||||
| 			// XXX
 | ||||
| 			data = data.split(/\n(\s*)- /g) | ||||
| 			var level = function(lst){ | ||||
| 				while(lst.length > 0){ | ||||
| 				} | ||||
| 			} | ||||
| 		}  | ||||
| 		// json...
 | ||||
| 		// XXX
 | ||||
| 
 | ||||
| 		data = typeof(data) == 'string' ? | ||||
| 			this.parse(data) | ||||
| 			: data | ||||
| 		// generate dom...
 | ||||
| 		// XXX
 | ||||
| 		return this }, | ||||
| @ -372,6 +428,8 @@ var Outline = { | ||||
| 			if(edited){ | ||||
| 				if(!atLine(edited, 0)){ | ||||
| 					return } | ||||
| 				/*/ | ||||
| 				//*/
 | ||||
| 				state = 'edited' } | ||||
| 			evt.preventDefault()  | ||||
| 			this.get(state, -1)?.focus() }, | ||||
| @ -381,16 +439,22 @@ var Outline = { | ||||
| 			if(edited){ | ||||
| 				if(!atLine(edited, -1)){ | ||||
| 					return } | ||||
| 				//window.getSelection()
 | ||||
| 				state = 'edited' } | ||||
| 			evt.preventDefault()  | ||||
| 			this.get(state, 1)?.focus() }, | ||||
| 
 | ||||
| 		// horizontal navigation / collapse...
 | ||||
| 		// XXX if at start/end of element move to prev/next...
 | ||||
| 		ArrowLeft: function(evt){ | ||||
| 			if(this.outline.querySelector('textarea:focus')){ | ||||
| 				// XXX if at end of element move to next...
 | ||||
| 			var edited = this.get('edited') | ||||
| 			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 } | ||||
| 			;((this.left_key_collapses  | ||||
| 						|| evt.shiftKey) | ||||
| @ -399,8 +463,16 @@ var Outline = { | ||||
| 				this.toggleCollapse(true) | ||||
| 				: this.get('parent')?.focus() }, | ||||
| 		ArrowRight: function(evt){ | ||||
| 			if(this.outline.querySelector('textarea:focus')){ | ||||
| 				// XXX if at end of element move to next...
 | ||||
| 			var edited = this.get('edited') | ||||
| 			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 } | ||||
| 			if(this.right_key_expands){ | ||||
| 				this.toggleCollapse(false)  | ||||
| @ -499,10 +571,13 @@ var Outline = { | ||||
| 				var node = evt.target | ||||
| 				if(node.nodeName == 'TEXTAREA'  | ||||
| 						&& node?.nextElementSibling?.nodeName == 'SPAN'){ | ||||
| 					node.nextElementSibling.innerHTML =  | ||||
| 						that.__code2html__ ? | ||||
| 							that.__code2html__(node.value) | ||||
| 							: node.value } }) | ||||
| 					if(that.__code2html__){ | ||||
| 						var data = that.__code2html__(node.value) | ||||
| 						node.nextElementSibling.innerHTML = data.text | ||||
| 						data.collapsed  | ||||
| 							&& node.parentElement.setAttribute('collapsed', '') | ||||
| 					} else { | ||||
| 						node.nextElementSibling.innerHTML = node.value } } }) | ||||
| 
 | ||||
| 		// toolbar...
 | ||||
| 		var toolbar = this.toolbar | ||||
|  | ||||
| @ -15,20 +15,52 @@ HTMLTextAreaElement.prototype.autoUpdateSize = function(){ | ||||
| 			that.updateSize() })  | ||||
| 	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', { | ||||
| 	enumerable: false, | ||||
| 	get: function(){ | ||||
| 		var offset = this.selectionStart | ||||
| 		console.log('---', this) | ||||
| 		return offset != null ? | ||||
| 			this.value | ||||
| 				.slice(0, offset) | ||||
| 				.split(/\n/g) | ||||
| 				.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 :                                                */ | ||||
|  | ||||
| @ -69,6 +69,10 @@ var setup = function(){ | ||||
| 
 | ||||
| <pre> | ||||
| 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) | ||||
| - loading from DOM -- fill textarea | ||||
| - Firefox compatibility -- remove ':has(..)' | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user