mirror of
				https://github.com/flynx/pWiki.git
				synced 2025-10-30 18:40:08 +00:00 
			
		
		
		
	moved to textarea + added support for code/view processing (currently html <-> text, not FF compatible)
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
		
							parent
							
								
									2172b83e93
								
							
						
					
					
						commit
						e01d5b54cb
					
				| @ -7,14 +7,42 @@ | |||||||
| 	font-size: 5mm; | 	font-size: 5mm; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .editor div div { | .editor [tabindex] { | ||||||
|  | 	position: relative; | ||||||
|  | } | ||||||
|  | .editor div [tabindex] { | ||||||
| 	margin-left: 2em; | 	margin-left: 2em; | ||||||
| } | } | ||||||
| .editor div span { | .editor [tabindex]>span, | ||||||
| 	display: block; | .editor [tabindex]>textarea { | ||||||
| 	padding: 0.2em; | 	--padding: 0.2em; | ||||||
| 
 | 
 | ||||||
|  | 	display: block; | ||||||
|  | 	width: 100%; | ||||||
|  | 	padding: var(--padding); | ||||||
|  | 	margin: 0; | ||||||
|  | 
 | ||||||
|  | 	font-family: sans-serif; | ||||||
|  | 	font-size: 5mm; | ||||||
| 	white-space: pre; | 	white-space: pre; | ||||||
|  | 
 | ||||||
|  | 	outline: none; | ||||||
|  | 	border: none; | ||||||
|  | } | ||||||
|  | .editor [tabindex]>textarea { | ||||||
|  | 	height: calc(2 * var(--padding) + 1em); | ||||||
|  | 	overflow: hidden; | ||||||
|  | 	resize: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* show/hide node's view/code... */ | ||||||
|  | .editor [tabindex]>span+textarea:not(:focus), | ||||||
|  | /* XXX not sure how to do this without :has(..)... */ | ||||||
|  | .editor [tabindex]:has(>span+textarea:focus)>span:has(+textarea), | ||||||
|  | .editor [tabindex]:focus>span+textarea { | ||||||
|  | 	position: absolute; | ||||||
|  | 	opacity: 0; | ||||||
|  | 	top: 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .editor div[collapsed] { | .editor div[collapsed] { | ||||||
| @ -28,15 +56,23 @@ | |||||||
| 	/*outline: solid 0.2em silver;*/ | 	/*outline: solid 0.2em silver;*/ | ||||||
| 	outline: none; | 	outline: none; | ||||||
| } | } | ||||||
| .editor div:focus>span { | .editor div:focus>span, | ||||||
|  | .editor div:focus>textarea { | ||||||
| 	background: silver; | 	background: silver; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| </style> | </style> | ||||||
| <script> | <script> | ||||||
|  | var updateTextareaSize = function(elem){ | ||||||
|  | 	elem.style.height = '' | ||||||
|  | 	elem.style.height = elem.scrollHeight + 'px' | ||||||
|  | 	return elem } | ||||||
| 
 | 
 | ||||||
| var getFocused = function(offset=0, selector='[tabindex]'){ | var getFocused = function(offset=0, selector='[tabindex]'){ | ||||||
| 	var focused = document.querySelector(`.editor ${selector}:focus`) | 	var focused = document.querySelector(`.editor ${selector}:focus`) | ||||||
|  | 		|| (selector != 'textarea' ?  | ||||||
|  | 			getEditable()?.parentElement | ||||||
|  | 			: null) | ||||||
| 	if(offset == 0){ | 	if(offset == 0){ | ||||||
| 		return focused } | 		return focused } | ||||||
| 
 | 
 | ||||||
| @ -88,7 +124,7 @@ var getFocused = function(offset=0, selector='[tabindex]'){ | |||||||
| // XXX would also be nice to make the move only if at first/last line/char | // XXX would also be nice to make the move only if at first/last line/char | ||||||
| // XXX would be nice to keep the cursor at roughly the same left offset... | // XXX would be nice to keep the cursor at roughly the same left offset... | ||||||
| var getEditable = function(offset){ | var getEditable = function(offset){ | ||||||
| 	return getFocused(offset, '[contenteditable]') } | 	return getFocused(offset, 'textarea') } | ||||||
| 
 | 
 | ||||||
| var indentNode = function(indent=true){ | var indentNode = function(indent=true){ | ||||||
| 	var cur = getFocused()  | 	var cur = getFocused()  | ||||||
| @ -127,69 +163,80 @@ var toggleCollapse = function(node, state='next'){ | |||||||
| 	state = state == 'next' ? | 	state = state == 'next' ? | ||||||
| 		!node.getAttribute('collapsed') | 		!node.getAttribute('collapsed') | ||||||
| 		: state | 		: state | ||||||
| 	state ? | 	if(state){ | ||||||
| 		node.setAttribute('collapsed', '') | 		node.setAttribute('collapsed', '') | ||||||
| 		: node.removeAttribute('collapsed') | 	} else { | ||||||
|  | 		node.removeAttribute('collapsed') | ||||||
|  | 		for(var elem of [...node.querySelectorAll('textarea')]){ | ||||||
|  | 			updateTextareaSize(elem) } | ||||||
|  | 	} | ||||||
| 	return node } | 	return node } | ||||||
| 
 | 
 | ||||||
|  | // XXX add reference node... | ||||||
|  | var createBlock = function(place=none){ | ||||||
|  | 	var block = document.createElement('div') | ||||||
|  | 	block.setAttribute('tabindex', '0') | ||||||
|  | 	block.innerHTML = `<span></span><textarea></textarea>` | ||||||
|  | 	var cur = getFocused() | ||||||
|  | 		|| getEditable()?.parentElement | ||||||
|  | 	place && cur | ||||||
|  | 		&& cur[place](block) | ||||||
|  | 	return block } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // XXX do a caret api... | ||||||
| 
 | 
 | ||||||
| // XXX this works only on the current text node... | // XXX this works only on the current text node... | ||||||
|  | // XXX only for text areas... | ||||||
| var atLine = function(index){ | var atLine = function(index){ | ||||||
| 	// XXX get line |  | ||||||
| 	var sel = window.getSelection() |  | ||||||
| 	// XXX add support for range... | 	// XXX add support for range... | ||||||
| 	if(sel.type == 'Caret'){ | 	var elem = getEditable() | ||||||
| 		var text = getEditable().innerText | 	var text = elem.value | ||||||
| 		var lines = text.split(/\n/g).length | 	var lines = text.split(/\n/g).length | ||||||
| 		var offset = sel.focusOffset | 	var offset = elem.selectionStart | ||||||
| 		var line = text.slice(0, offset).split(/\n/g).length | 	var line = text.slice(0, offset).split(/\n/g).length | ||||||
| 
 | 
 | ||||||
| 		console.log('---', line, 'of', lines, '---', offset) | 	//console.log('---', line, 'of', lines, '---', offset, sel) | ||||||
| 
 | 
 | ||||||
| 		// XXX STUB index handling... | 	// XXX STUB index handling... | ||||||
| 		// XXX need to account for multiple elements withn the block... | 	if(index == -1 && line == lines){ | ||||||
| 		//		...this breaks if we insert an element into the text,  | 		return true | ||||||
| 		//		e.g. something like <i>..</i>... | 	} else if(index == 0 && line == 1){ | ||||||
| 		if(index == -1 && line == lines){ | 		return true | ||||||
| 			return true |  | ||||||
| 		} else if(index == 0 && line == 1){ |  | ||||||
| 			return true |  | ||||||
| 		} |  | ||||||
| 		return false |  | ||||||
| 	} | 	} | ||||||
| 	return true | 	return false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var LEFT_COLLAPSE = false | var LEFT_COLLAPSE = false | ||||||
| var RIGHT_EXPAND = true | var RIGHT_EXPAND = true | ||||||
| 
 | 
 | ||||||
|  | // XXX add scrollIntoView(..) to nav... | ||||||
| var keyboard = { | var keyboard = { | ||||||
| 	// vertical navigation... | 	// vertical navigation... | ||||||
| 	ArrowDown: function(evt, offset=1){ |  | ||||||
| 		var action = getFocused |  | ||||||
| 		var edited = document.querySelector('.editor [contenteditable]:focus') |  | ||||||
| 		if(edited){ |  | ||||||
| 			if(!atLine(-1)){ |  | ||||||
| 				return } |  | ||||||
| 			evt.preventDefault()  |  | ||||||
| 			//window.getSelection() |  | ||||||
| 			action = getEditable } |  | ||||||
| 		action(1)?.focus() }, |  | ||||||
| 	ArrowUp: function(evt){ | 	ArrowUp: function(evt){ | ||||||
| 		var action = getFocused | 		var action = getFocused | ||||||
| 		var edited = document.querySelector('.editor [contenteditable]:focus') | 		var edited = document.querySelector('.editor textarea:focus') | ||||||
| 		if(edited){ | 		if(edited){ | ||||||
| 			if(!atLine(0)){ | 			if(!atLine(0)){ | ||||||
| 				return } | 				return } | ||||||
| 			evt.preventDefault()  |  | ||||||
| 			action = getEditable } | 			action = getEditable } | ||||||
| 		if(action === getEditable){ | 		evt.preventDefault()  | ||||||
| 			evt.preventDefault() } |  | ||||||
| 		action(-1)?.focus() }, | 		action(-1)?.focus() }, | ||||||
|  | 	ArrowDown: function(evt, offset=1){ | ||||||
|  | 		var action = getFocused | ||||||
|  | 		var edited = document.querySelector('.editor textarea:focus') | ||||||
|  | 		if(edited){ | ||||||
|  | 			if(!atLine(-1)){ | ||||||
|  | 				return } | ||||||
|  | 			//window.getSelection() | ||||||
|  | 			action = getEditable } | ||||||
|  | 		evt.preventDefault()  | ||||||
|  | 		action(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(document.querySelector('.editor [contenteditable]:focus')){ | 		if(document.querySelector('.editor textarea:focus')){ | ||||||
| 			// XXX if at end of element move to next... | 			// XXX if at end of element move to next... | ||||||
| 			return } | 			return } | ||||||
| 		if(LEFT_COLLAPSE){ | 		if(LEFT_COLLAPSE){ | ||||||
| @ -200,7 +247,7 @@ var keyboard = { | |||||||
| 				toggleCollapse(true) | 				toggleCollapse(true) | ||||||
| 				: getFocused('parent')?.focus() } }, | 				: getFocused('parent')?.focus() } }, | ||||||
| 	ArrowRight: function(evt){ | 	ArrowRight: function(evt){ | ||||||
| 		if(document.querySelector('.editor [contenteditable]:focus')){ | 		if(document.querySelector('.editor textarea:focus')){ | ||||||
| 			// XXX if at end of element move to next... | 			// XXX if at end of element move to next... | ||||||
| 			return } | 			return } | ||||||
| 		if(RIGHT_EXPAND){ | 		if(RIGHT_EXPAND){ | ||||||
| @ -217,35 +264,88 @@ var keyboard = { | |||||||
| 	// indent... | 	// indent... | ||||||
| 	Tab: function(evt){ | 	Tab: function(evt){ | ||||||
| 		evt.preventDefault() | 		evt.preventDefault() | ||||||
| 		indentNode(!evt.shiftKey)?.focus() }, | 		var editable = getEditable() | ||||||
|  | 		var node = indentNode(!evt.shiftKey) | ||||||
|  | 		;(editable ? | ||||||
|  | 			editable | ||||||
|  | 			: node)?.focus() }, | ||||||
| 
 | 
 | ||||||
| 	// edit mode... | 	// edit mode... | ||||||
|  | 	O: function(evt){ | ||||||
|  | 		if(evt.target.nodeName != 'TEXTAREA'){ | ||||||
|  | 			evt.preventDefault() | ||||||
|  | 			createBlock('before')?.querySelector('textarea')?.focus() } }, | ||||||
|  | 	o: function(evt){ | ||||||
|  | 		if(evt.target.nodeName != 'TEXTAREA'){ | ||||||
|  | 			evt.preventDefault() | ||||||
|  | 			createBlock('after')?.querySelector('textarea')?.focus() } }, | ||||||
| 	Enter: function(evt){ | 	Enter: function(evt){ | ||||||
| 		evt.shiftKey | 		/*if(evt.target.isContentEditable){ | ||||||
| 			|| evt.preventDefault() | 			// XXX create new node... | ||||||
| 		getFocused()?.querySelector('[contenteditable]')?.focus() }, | 			return } | ||||||
|  | 		//*/ | ||||||
|  | 		if(evt.ctrlKey | ||||||
|  | 				|| evt.shiftKey){ | ||||||
|  | 			return } | ||||||
|  | 		evt.preventDefault() | ||||||
|  | 		evt.target.nodeName == 'TEXTAREA' ? | ||||||
|  | 			createBlock('after')?.querySelector('textarea')?.focus() | ||||||
|  | 			: getFocused()?.querySelector('textarea')?.focus() }, | ||||||
| 	Escape: function(evt){ | 	Escape: function(evt){ | ||||||
| 		document.querySelector('[contenteditable]:focus')?.parentElement?.focus() }, | 		document.querySelector('textarea:focus')?.parentElement?.focus() }, | ||||||
|  | 	Delete: function(evt){ | ||||||
|  | 		if(evt.target.isContentEditable){ | ||||||
|  | 			return } | ||||||
|  | 		var next = getFocused(1) | ||||||
|  | 		getFocused()?.remove()  | ||||||
|  | 		next?.focus() }, | ||||||
| } | } | ||||||
| document.addEventListener('keydown',  | document.addEventListener('keydown',  | ||||||
| 	function(evt){ | 	function(evt){ | ||||||
| 		evt.key in keyboard  | 		evt.key in keyboard  | ||||||
| 			&& keyboard[evt.key](evt) }) | 			&& keyboard[evt.key](evt) }) | ||||||
| 
 | 
 | ||||||
|  | document.addEventListener('input',  | ||||||
|  | 	function(evt){ | ||||||
|  | 		updateTextareaSize(evt.target) }) | ||||||
|  | 
 | ||||||
|  | // XXX add support for markup handlers... | ||||||
|  | document.addEventListener('focusin',  | ||||||
|  | 	function(evt){ | ||||||
|  | 		var node = evt.target | ||||||
|  | 		if(node.nodeName == 'TEXTAREA'  | ||||||
|  | 				&& node?.previousElementSibling?.nodeName == 'SPAN'){ | ||||||
|  | 			node.value = node.previousElementSibling.innerHTML  | ||||||
|  | 			updateTextareaSize(node) } }) | ||||||
|  | document.addEventListener('focusout',  | ||||||
|  | 	function(evt){ | ||||||
|  | 		var node = evt.target | ||||||
|  | 		if(node.nodeName == 'TEXTAREA'  | ||||||
|  | 				&& node?.previousElementSibling?.nodeName == 'SPAN'){ | ||||||
|  | 			node.previousElementSibling.innerHTML = node.value } }) | ||||||
|  | 
 | ||||||
|  | var setup = function(){ | ||||||
|  | 	for(var elem of [...document.querySelectorAll('.editor textarea')]){ | ||||||
|  | 		updateTextareaSize(elem) } } | ||||||
| 
 | 
 | ||||||
| </script> | </script> | ||||||
| </head> | </head> | ||||||
| <body> | <body onload="setup()"> | ||||||
| <pre> | <pre> | ||||||
| TODO: | TODO: | ||||||
| - <s>navigation</s> | - <s>navigation</s> | ||||||
| - <s>expand/collapse subtree</s> | - <s>expand/collapse subtree</s> | ||||||
| - <s>shift subtree up/down</s> | - <s>shift subtree up/down</s> | ||||||
| - edit node | - <s>create node</s> | ||||||
|  | - <s>edit node</s> | ||||||
|  | - undo delete node | ||||||
|  | - copy/paste nodes/trees | ||||||
|  | - shifting nodes up/down | ||||||
| - multiple node selection | - multiple node selection | ||||||
| - create node |  | ||||||
| - mouse controls | - mouse controls | ||||||
| - touch controls | - touch controls | ||||||
|  | - serialize/deserialize | ||||||
|  | - add optional styling to nodes | ||||||
| 
 | 
 | ||||||
| Controls: | Controls: | ||||||
| 	up         - focus node above | 	up         - focus node above | ||||||
| @ -256,31 +356,36 @@ Controls: | |||||||
| 	s-tab      - deindent node | 	s-tab      - deindent node | ||||||
| 	s-left     - collapse node | 	s-left     - collapse node | ||||||
| 	s-right    - expand node | 	s-right    - expand node | ||||||
|  | 	enter      - normal mode: edit node | ||||||
|  | 	           - edit mode: create node below | ||||||
|  | 	esc        - exit edit mode  | ||||||
| </pre> | </pre> | ||||||
| 
 | 
 | ||||||
| <hr> | <hr> | ||||||
| 
 | 
 | ||||||
| <div class="editor"> | <div class="editor"> | ||||||
| 	<div tabindex=0><span contenteditable="true">root</span> | 	<div tabindex=0> | ||||||
| 		<div tabindex=0 collapsed><span contenteditable="true">A</span> | 		<span><i>root</i></span><textarea></textarea> | ||||||
| 			<div tabindex=0><span contenteditable="true">a</span> | 		<div tabindex=0 collapsed> | ||||||
|  | 			<span>A</span><textarea></textarea> | ||||||
|  | 			<div tabindex=0><span>a</span><textarea></textarea> | ||||||
| 			</div> | 			</div> | ||||||
| 			<div tabindex=0><span contenteditable="true">b</span> | 			<div tabindex=0><span>b</span><textarea></textarea> | ||||||
| 			</div> | 			</div> | ||||||
| 			<div tabindex=0><span contenteditable="true">c</span> | 			<div tabindex=0><span>c</span><textarea></textarea> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div tabindex=0><span contenteditable="true">B</span> | 		<div tabindex=0><span>B</span><textarea></textarea> | ||||||
| 			<div tabindex=0><span contenteditable="true">d</span> | 			<div tabindex=0><span>d</span><textarea></textarea> | ||||||
| 			</div> | 			</div> | ||||||
| 			<div tabindex=0><span contenteditable="true">e</span> | 			<div tabindex=0><span>e</span><textarea></textarea> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div tabindex=0><span contenteditable="true">C</span> | 		<div tabindex=0><span>C</span><textarea></textarea> | ||||||
| 			<div tabindex=0><span contenteditable="true">This is a line of text</span> | 			<div tabindex=0><span>This is a line of text</span><textarea></textarea> | ||||||
| 			</div> | 			</div> | ||||||
| 			<div tabindex=0><span contenteditable="true">This is a set | 			<div tabindex=0><span>This is a set | ||||||
| text lines</span> | text lines</span><textarea></textarea> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user