mirror of
				https://github.com/flynx/pWiki.git
				synced 2025-10-30 02:20:08 +00:00 
			
		
		
		
	added inline checkboxes (request by XYZ) + lots of tweaks and fixes...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
		
							parent
							
								
									69f294cec3
								
							
						
					
					
						commit
						21c386f3dc
					
				| @ -1,6 +1,7 @@ | ||||
| 
 | ||||
| :root { | ||||
| 	--font-size: 5mm; | ||||
| 	--item-padding: 0.2em; | ||||
| 	--button-size: 2em; | ||||
| 
 | ||||
| 	font-family: sans-serif; | ||||
| @ -36,14 +37,16 @@ | ||||
| } | ||||
| .editor .outline [tabindex]>span, | ||||
| .editor .outline [tabindex]>textarea { | ||||
| 	--padding: 0.2em; | ||||
| 
 | ||||
| 	display: block; | ||||
| 	width: 100%; | ||||
| 	/* XXX this is a tiny bit off and using textarea's height here is off too... */ | ||||
| 	min-height: 1em; | ||||
| 	padding: var(--padding); | ||||
| 	padding-top: var(--item-padding); | ||||
| 	padding-bottom: var(--item-padding); | ||||
| 	padding-left: 0; | ||||
| 	padding-right: 0; | ||||
| 	margin: 0; | ||||
| 	box-sizing: border-box; | ||||
| 
 | ||||
| 	font-family: sans-serif; | ||||
| 	font-size: var(--font-size); | ||||
| @ -53,7 +56,7 @@ | ||||
| 	border: none; | ||||
| } | ||||
| .editor .outline [tabindex]>textarea { | ||||
| 	height: calc(2 * var(--padding) + 1em); | ||||
| 	height: calc(2 * var(--item-padding) + 1em); | ||||
| 	overflow: hidden; | ||||
| 	resize: none; | ||||
| } | ||||
| @ -130,6 +133,15 @@ | ||||
| .editor .outline .heading-6>textarea { | ||||
| 	font-weight: bold; | ||||
| } | ||||
| .editor .outline .heading-1>span, | ||||
| .editor .outline .heading-1>textarea, | ||||
| .editor .outline .heading-2>span, | ||||
| .editor .outline .heading-2>textarea, | ||||
| .editor .outline .heading-3>span, | ||||
| .editor .outline .heading-3>textarea { | ||||
| 	border-bottom: solid 1px rgba(0,0,0,0.05); | ||||
| } | ||||
| 
 | ||||
| .editor .outline .heading-1>span, | ||||
| .editor .outline .heading-1>textarea { | ||||
| 	font-size: 2.5em; | ||||
| @ -155,16 +167,20 @@ | ||||
| 	font-size: 1em; | ||||
| } | ||||
| 
 | ||||
| /* XXX EXPERIMENTAL -- not sure about this... */ | ||||
| /* XXX needs to be in the middle of the first span but with universal size... */ | ||||
| .editor .outline .list-item:before, | ||||
| .editor .outline .list>[tabindex]:before { | ||||
| .editor .outline .list>[tabindex]>span:before { | ||||
| 	--size: 0.5rem; | ||||
| 
 | ||||
| 	display: inline-block; | ||||
| 	position: absolute; | ||||
| 	content: ""; | ||||
| 	top: 0.6em; | ||||
| 	left: -0.8em; | ||||
| 	width: 0.5em; | ||||
| 	height: 0.5em; | ||||
| 	top: calc(0.6em + var(--item-padding)); | ||||
| 	left: calc(var(--size) * -2); | ||||
| 	width: var(--size); | ||||
| 	height: var(--size); | ||||
| 	margin-top: calc(var(--size) / -2); | ||||
| 
 | ||||
| 	background: silver; | ||||
| } | ||||
| 
 | ||||
| @ -172,15 +188,31 @@ | ||||
| 	background: yellow; | ||||
| } | ||||
| 
 | ||||
| .editor.hide-comments .outline .comment { | ||||
| 	display: none; | ||||
| } | ||||
| .editor .outline .comment>span { | ||||
| 	color: silver; | ||||
| } | ||||
| 
 | ||||
| .editor .outline [tabindex]>span>input[type=checkbox] { | ||||
| 	--width: 3em; | ||||
| .editor .outline [tabindex]>span>input[type=checkbox].check, | ||||
| .editor .outline [tabindex]>span>input[type=checkbox].todo { | ||||
| 	--size: 1.5rem; | ||||
| 	--height: calc(var(--font-size) + 2 * var(--item-padding)); | ||||
| 
 | ||||
| 	height: 1em; | ||||
| 	width: var(--width); | ||||
| 	margin-left: calc(-1 * var(--width)); | ||||
| 	top: calc(0.6em + var(--item-padding)); | ||||
| 	height: var(--size); | ||||
| 	width: var(--size); | ||||
| 	margin-top: calc(var(--size) / -2); | ||||
| 
 | ||||
| 	/* NOTE: this appears to be needed for the em sizes above to work correctly */ | ||||
| 	font-size: 1em; | ||||
| } | ||||
| .editor .outline [tabindex]>span>input[type=checkbox].todo { | ||||
| 	position: absolute; | ||||
| 	margin-left: calc(-1 * var(--size) - var(--item-padding)); | ||||
| } | ||||
| .editor .outline [tabindex]>span>input[type=checkbox].check { | ||||
| 	transform: translateY(calc(2 * var(--item-padding))); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -27,46 +27,8 @@ var atLine = function(elem, index){ | ||||
| 
 | ||||
| //---------------------------------------------------------------------
 | ||||
| 
 | ||||
| var Node = { | ||||
| 	dom: undefined, | ||||
| 	document: undefined, | ||||
| 
 | ||||
| 	get: function(){}, | ||||
| 
 | ||||
| 	get root(){}, | ||||
| 	get parent(){}, | ||||
| 	get children(){}, | ||||
| 	get next(){}, | ||||
| 	get prev(){}, | ||||
| 
 | ||||
| 	focus: function(){}, | ||||
| 	edit: function(){}, | ||||
| 
 | ||||
| 	indent: function(){ }, | ||||
| 	deindent: function(){ }, | ||||
| 	toggleCollapse: function(){ }, | ||||
| 
 | ||||
| 	remove: function(){}, | ||||
| 
 | ||||
| 	json: function(){}, | ||||
| 	text: function(){}, | ||||
| 
 | ||||
| 	load: function(){}, | ||||
| } | ||||
| 
 | ||||
| var NodeGroup = { | ||||
| 	__proto__: Node, | ||||
| } | ||||
| 
 | ||||
| // XXX should this be Page or root??
 | ||||
| var Root = { | ||||
| 	__proto__: NodeGroup, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | ||||
| 
 | ||||
| // XXX might be a good idea to do a view-action model...
 | ||||
| // XXX experiment with a concatinative model...
 | ||||
| // 		.get(..) -> Outline (view)
 | ||||
| var Outline = { | ||||
| 	dom: undefined, | ||||
| 
 | ||||
| @ -219,9 +181,10 @@ var Outline = { | ||||
| 	// XXX should this handle children???
 | ||||
| 	update: function(node='focused', data){ | ||||
| 		var node = this.get(node) | ||||
| 		data.collapsed ? | ||||
| 		typeof(data.collapsed) == 'boolean' | ||||
| 			&& (data.collapsed ? | ||||
| 				node.setAttribute('collapsed', '') | ||||
| 			: node.removeAttribute('collapsed') | ||||
| 				: node.removeAttribute('collapsed')) | ||||
| 		if(data.text){ | ||||
| 			var text = node.querySelector('textarea') | ||||
| 			var html = node.querySelector('span') | ||||
| @ -231,7 +194,7 @@ var Outline = { | ||||
| 				html.innerHTML = parsed.text | ||||
| 				// heading...
 | ||||
| 				parsed.style ? | ||||
| 					node.classList.add(parsed.style) | ||||
| 					node.classList.add(...parsed.style) | ||||
| 					: node.classList.remove(...this.__styles__) | ||||
| 			} else { | ||||
| 				html.innerHTML = data.text } | ||||
| @ -306,9 +269,10 @@ var Outline = { | ||||
| 		return this }, | ||||
| 
 | ||||
| 	// block serialization...
 | ||||
| 	// XXX STUB...
 | ||||
| 	// XXX shouild we support headings + other formatting per block???
 | ||||
| 	// XXX these should be symetrical -- now one returns text the other an object...
 | ||||
| 	// XXX split this up into a generic handler + plugins...
 | ||||
| 	// XXX need a way to filter input text...
 | ||||
| 	// 		use-case: hidden attributes...
 | ||||
| 	__styles__: [ | ||||
| 		'heading-1', | ||||
| 		'heading-2', | ||||
| @ -324,11 +288,13 @@ var Outline = { | ||||
| 		} | ||||
| 		var heading = function(level){ | ||||
| 			return function(_, text){ | ||||
| 				elem.style = 'heading-'+level | ||||
| 				elem.style ??= [] | ||||
| 				elem.style.push('heading-'+level) | ||||
| 				return text } } | ||||
| 		var style = function(style){ | ||||
| 			return function(_, text){ | ||||
| 				elem.style = style  | ||||
| 				elem.style ??= [] | ||||
| 				elem.style.push(style) | ||||
| 				return text } } | ||||
| 		elem.text = code  | ||||
| 			// hidden attributes...
 | ||||
| @ -342,33 +308,37 @@ var Outline = { | ||||
| 			// id...
 | ||||
| 			.replace(/(\n|^)\s*id::\s*(.*)\s*(\n|$)/,  | ||||
| 				function(_, value){ | ||||
| 					elem.collapsed = value.trim() == 'true' | ||||
| 					elem.id = value.trim() | ||||
| 					return '' }) | ||||
| 			// markdown...
 | ||||
| 			// ToDo...
 | ||||
| 			.replace(/^TODO\s*(.*)$/, '<input type="checkbox"> $1') | ||||
| 			.replace(/^DONE\s*(.*)$/, '<input type="checkbox" checked> $1') | ||||
| 			// style: headings...
 | ||||
| 			.replace(/^######\s*(.*)$/, style('heading-6')) | ||||
| 			.replace(/^#####\s*(.*)$/, style('heading-5')) | ||||
| 			.replace(/^####\s*(.*)$/, style('heading-4')) | ||||
| 			.replace(/^###\s*(.*)$/, style('heading-3')) | ||||
| 			.replace(/^##\s*(.*)$/, style('heading-2')) | ||||
| 			.replace(/^#\s*(.*)$/, style('heading-1')) | ||||
| 			.replace(/^######\s*(.*)$/m, style('heading-6')) | ||||
| 			.replace(/^#####\s*(.*)$/m, style('heading-5')) | ||||
| 			.replace(/^####\s*(.*)$/m, style('heading-4')) | ||||
| 			.replace(/^###\s*(.*)$/m, style('heading-3')) | ||||
| 			.replace(/^##\s*(.*)$/m, style('heading-2')) | ||||
| 			.replace(/^#\s*(.*)$/m, style('heading-1')) | ||||
| 			// style: list...
 | ||||
| 			.replace(/^[-\*]\s+(.*)$/, style('list-item')) | ||||
| 			.replace(/^\s*(.*):\s*$/, style('list')) | ||||
| 			.replace(/^[-\*]\s+(.*)$/m, style('list-item')) | ||||
| 			.replace(/^\s*(.*):\s*$/m, style('list')) | ||||
| 			// style: misc...
 | ||||
| 			.replace(/^((\/\/|;)\s+.*)$/, style('comment')) | ||||
| 			.replace(/^XXX\s+(.*)$/, style('XXX')) | ||||
| 			.replace(/^(.*)\s*XXX$/, style('XXX')) | ||||
| 			.replace(/^((\/\/|;)\s+.*)$/m, style('comment')) | ||||
| 			.replace(/^XXX\s+(.*)$/m, style('XXX')) | ||||
| 			.replace(/^(.*)\s*XXX$/m, style('XXX')) | ||||
| 			// basic styling...
 | ||||
| 			// XXX these are quite naive...
 | ||||
| 			.replace(/\*(.*)\*/g, '<b>$1</b>') | ||||
| 			.replace(/~([^~]*)~/g, '<s>$1</s>') | ||||
| 			.replace(/_([^_]*)_/g, '<i>$1</i>')  | ||||
| 			.replace(/\*(.*)\*/gm, '<b>$1</b>') | ||||
| 			.replace(/~([^~]*)~/gm, '<s>$1</s>') | ||||
| 			.replace(/_([^_]*)_/gm, '<i>$1</i>')  | ||||
| 			// elements...
 | ||||
| 			.replace(/(\n|^)---*\h*(\n|$)/, '$1<hr>') | ||||
| 			.replace(/(\n|^)---*\h*(\n|$)/m, '$1<hr>') | ||||
| 			// ToDo...
 | ||||
| 			.replace(/^TODO\s*(.*)$/m, '<input class="todo" type="checkbox">$1') | ||||
| 			.replace(/^DONE\s*(.*)$/m, '<input class="todo" type="checkbox" checked>$1') | ||||
| 			// checkboxes...
 | ||||
| 			// XXX these can not be clicked (yet)...
 | ||||
| 			.replace(/\[ \]/gm, '<input class="check" type="checkbox">') | ||||
| 			.replace(/\[[X]\]/gm, '<input class="check" type="checkbox" checked>') | ||||
| 		return elem }, | ||||
| 
 | ||||
| 	// serialization...
 | ||||
| @ -460,9 +430,19 @@ var Outline = { | ||||
| 		var html = document.createElement('span') | ||||
| 		block.append(text, html) | ||||
| 		this.update(block, data) | ||||
| 		// place...
 | ||||
| 		var cur = this.get() | ||||
| 		place && cur | ||||
| 			&& cur[place](block) | ||||
| 		if(place && cur){ | ||||
| 			place = place == 'prev' ? | ||||
| 				'before' | ||||
| 				: place | ||||
| 			;(place == 'next'  | ||||
| 					&& (cur.querySelector('[tabindex]')  | ||||
| 						|| cur.nextElementSibling)) ? | ||||
| 				this.get(place).before(block) | ||||
| 			: (place == 'before' || place == 'after') ? | ||||
| 				cur[place](block) | ||||
| 			: undefined } | ||||
| 		return block }, | ||||
| 	load: function(data){ | ||||
| 		var that = this | ||||
| @ -481,6 +461,9 @@ var Outline = { | ||||
| 			.clear() | ||||
| 			.outline | ||||
| 				.append(...level(data)) | ||||
| 		// update sizes of all the textareas (transparent)...
 | ||||
| 		for(var e of [...this.outline.querySelectorAll('textarea')]){ | ||||
| 			e.updateSize() } | ||||
| 		return this }, | ||||
| 
 | ||||
| 	sync: function(){ | ||||
| @ -569,25 +552,31 @@ var Outline = { | ||||
| 		O: function(evt){ | ||||
| 			if(evt.target.nodeName != 'TEXTAREA'){ | ||||
| 				evt.preventDefault() | ||||
| 				this.Block('before')?.querySelector('textarea')?.focus() } }, | ||||
| 				this.Block('before') | ||||
| 					?.querySelector('textarea') | ||||
| 					?.focus() } }, | ||||
| 		o: function(evt){ | ||||
| 			if(evt.target.nodeName != 'TEXTAREA'){ | ||||
| 				evt.preventDefault() | ||||
| 				this.Block('after')?.querySelector('textarea')?.focus() } }, | ||||
| 				this.Block('next') | ||||
| 					?.querySelector('textarea') | ||||
| 					?.focus() } }, | ||||
| 		Enter: function(evt){ | ||||
| 			/*if(evt.target.isContentEditable){ | ||||
| 				// XXX create new node...
 | ||||
| 				return } | ||||
| 			//*/
 | ||||
| 			if(evt.ctrlKey | ||||
| 					|| evt.shiftKey){ | ||||
| 				return } | ||||
| 			evt.preventDefault() | ||||
| 			evt.target.nodeName == 'TEXTAREA' ? | ||||
| 				this.Block('after')?.querySelector('textarea')?.focus() | ||||
| 				: this.get()?.querySelector('textarea')?.focus() }, | ||||
| 				this.Block('next') | ||||
| 					?.querySelector('textarea') | ||||
| 					?.focus() | ||||
| 				: this.get() | ||||
| 					?.querySelector('textarea') | ||||
| 					?.focus() }, | ||||
| 		Escape: function(evt){ | ||||
| 			this.outline.querySelector('textarea:focus')?.parentElement?.focus() }, | ||||
| 			this.outline.querySelector('textarea:focus') | ||||
| 				?.parentElement | ||||
| 				?.focus() }, | ||||
| 		Delete: function(evt){ | ||||
| 			if(this.get('edited')){ | ||||
| 				return } | ||||
| @ -620,14 +609,27 @@ var Outline = { | ||||
| 		outline.addEventListener('click',  | ||||
| 			function(evt){ | ||||
| 				var elem = evt.target | ||||
| 				// toggle checkbox...
 | ||||
| 				if(elem.nodeName == 'INPUT' && elem.type == 'checkbox'){ | ||||
| 				// todo: toggle checkbox...
 | ||||
| 				if(elem.classList.contains('todo')){ | ||||
| 					var node = elem.parentElement.parentElement | ||||
| 					var text = node.querySelector('textarea') | ||||
| 					text.value =  | ||||
| 						elem.checked ? | ||||
| 							text.value.replace(/^\s*TODO(\s*)/, 'DONE$1') | ||||
| 							: text.value.replace(/^\s*DONE(\s*)/, 'TODO$1') } }) | ||||
| 							: text.value.replace(/^\s*DONE(\s*)/, 'TODO$1') }  | ||||
| 				// check: toggle checkbox...
 | ||||
| 				if(elem.classList.contains('check')){ | ||||
| 					var node = elem.parentElement.parentElement | ||||
| 					var text = node.querySelector('textarea') | ||||
| 					var i = [...node.querySelectorAll('.check')].indexOf(elem) | ||||
| 					var to = elem.checked ? | ||||
| 						'[X]' | ||||
| 						: '[ ]' | ||||
| 					var toggle = function(m){ | ||||
| 						return i-- == 0 ? | ||||
| 							to | ||||
| 							: m } | ||||
| 					text.value = text.value.replace(/\[[X ]\]/g, toggle) } }) | ||||
| 		// heboard handling...
 | ||||
| 		outline.addEventListener('keydown',  | ||||
| 			function(evt){ | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <head> | ||||
| <link href="editor.css" rel="stylesheet"/> | ||||
| @ -27,8 +28,7 @@ var setup = function(){ | ||||
| 	<!-- code --> | ||||
| 	<div class="code"> | ||||
| - # Outline editor prototype | ||||
| - ## TODO | ||||
|   - editor: enter on an expanded parent node should create child (currently next sibling) | ||||
| - ## ToDo | ||||
|   - editor: bksapce/del at start/end of a block should join it with prev/next | ||||
|   - editor: pressing enter in text edit mode should split text into two blocks | ||||
|   - editor: caret | ||||
| @ -51,6 +51,7 @@ var setup = function(){ | ||||
|     - indent/deindent | ||||
|     - edit node | ||||
|   - copy/paste nodes/trees | ||||
|   - markdown tables | ||||
|   - ~serialize~/deserialize | ||||
|   - ~add optional text styling to nodes~ | ||||
|   -  | ||||
| @ -71,12 +72,14 @@ var setup = function(){ | ||||
| 	  - // C-style comment | ||||
| 	  - ; ASM-style comment | ||||
| 	  - XXX Highlight | ||||
| 	- line | ||||
| 	- Line | ||||
| 		- --- | ||||
|     - basic inline *bold*, _italic_ and ~striked~ | ||||
|     - Basic inline *bold*, _italic_ and ~striked~ | ||||
| 	- To do items | ||||
| 	  - TODO undone item | ||||
| 	  - DONE done item | ||||
|         _(clicking the checkbox updates the item)_ | ||||
|     - Inline [X] checkboxes [ ] | ||||
|   - A | ||||
|     collapsed:: true | ||||
|     - a | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user