mirror of
				https://github.com/flynx/pWiki.git
				synced 2025-10-31 02:50:08 +00:00 
			
		
		
		
	refactoring...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
		
							parent
							
								
									7a5663e044
								
							
						
					
					
						commit
						1e2c4b01c2
					
				| @ -688,6 +688,64 @@ | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | /* XXX EXPERIMENTAL...  | ||||||
|  | *	- need to handle nested blocks somehow... | ||||||
|  | */ | ||||||
|  | .editor .outline .block.table-2 { | ||||||
|  | 
 | ||||||
|  | 	&>.view { | ||||||
|  | 		font-size: small; | ||||||
|  | 		color: rgba(0,0,0,0.4); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	&.focused { | ||||||
|  | 		background: rgba(0,0,0,0.05); | ||||||
|  | 	} | ||||||
|  | 	&:focus { | ||||||
|  | 		background: rgba(0,0,0,0.07); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	&>.children { | ||||||
|  | 		display: table !important; | ||||||
|  | 		width: 100%; | ||||||
|  | 
 | ||||||
|  | 		&>.block { | ||||||
|  | 			display: table-row; | ||||||
|  | 
 | ||||||
|  | 			&:first-child>.view td { | ||||||
|  | 				font-weight: bold; | ||||||
|  | 				border-bottom: solid 0.1rem silver; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			&:nth-child(even) { | ||||||
|  | 				background: rgba(0,0,0,0.03); | ||||||
|  | 			} | ||||||
|  | 			&:not(:first-child) { | ||||||
|  | 				&>.view td { | ||||||
|  | 					font-weight: normal !important; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			&.focused { | ||||||
|  | 				background: rgba(0,0,0,0.03); | ||||||
|  | 			} | ||||||
|  | 			&:focus { | ||||||
|  | 				background: rgba(0,0,0,0.07); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.block>.children, | ||||||
|  | 	.view, | ||||||
|  | 	table, | ||||||
|  | 	tbody, | ||||||
|  | 	tr { | ||||||
|  | 		display: contents !important; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| /********************************************************* Testing ***/ | /********************************************************* Testing ***/ | ||||||
| 
 | 
 | ||||||
| :host.show-click-zones .outline .block, | :host.show-click-zones .outline .block, | ||||||
| @ -710,5 +768,6 @@ | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| /********************************************************************** | /********************************************************************** | ||||||
| * vim:set ts=4 sw=4 : */ | * vim:set ts=4 sw=4 : */ | ||||||
|  | |||||||
| @ -102,9 +102,16 @@ var getCharOffset = function(elem, x, y, data){ | |||||||
| 				return data.c - 1 } | 				return data.c - 1 } | ||||||
| 
 | 
 | ||||||
| 			// count "virtual" newlines between text and block elements... 
 | 			// count "virtual" newlines between text and block elements... 
 | ||||||
| 			var block = ['block', 'table', 'flex', 'grid'] | 			var type = getComputedStyle(e).display | ||||||
| 					.includes( | 			var block = [ | ||||||
| 						getComputedStyle(e).display) | 				'block',  | ||||||
|  | 				// XXX these do not add up yet...
 | ||||||
|  | 				//'table', 
 | ||||||
|  | 				//'table-row', 
 | ||||||
|  | 				//'table-cell', 
 | ||||||
|  | 				'flex',  | ||||||
|  | 				'grid', | ||||||
|  | 			].includes(type) | ||||||
| 			if(block  | 			if(block  | ||||||
| 					&& data.prev_elem | 					&& data.prev_elem | ||||||
| 					&& data.prev_elem != 'block'){ | 					&& data.prev_elem != 'block'){ | ||||||
| @ -115,6 +122,13 @@ var getCharOffset = function(elem, x, y, data){ | |||||||
| 
 | 
 | ||||||
| 			// handle the node...
 | 			// handle the node...
 | ||||||
| 			data = getCharOffset(e, x, y, data) | 			data = getCharOffset(e, x, y, data) | ||||||
|  | 
 | ||||||
|  | 			// compensate for table stuff...
 | ||||||
|  | 			if(type == 'table-row'){ | ||||||
|  | 				data.c -= 1 } | ||||||
|  | 			if(type == 'table-cell'){ | ||||||
|  | 				data.c += 1 } | ||||||
|  | 
 | ||||||
| 			if(typeof(data) != 'object'){ | 			if(typeof(data) != 'object'){ | ||||||
| 				return data } } } | 				return data } } } | ||||||
| 	return arguments.length > 3 ? | 	return arguments.length > 3 ? | ||||||
| @ -200,8 +214,11 @@ var plugin = { | |||||||
| 		return function(_, text){ | 		return function(_, text){ | ||||||
| 			elem.style ??= [] | 			elem.style ??= [] | ||||||
| 			elem.style.push(...style) | 			elem.style.push(...style) | ||||||
| 			return code  | 			return typeof(code) == 'function' ? | ||||||
| 				?? text } }, | 					code(...arguments) | ||||||
|  | 				: code != null ? | ||||||
|  | 					code | ||||||
|  | 				: text } }, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -632,15 +649,23 @@ var syntax = { | |||||||
| var tables = { | var tables = { | ||||||
| 	__proto__: plugin, | 	__proto__: plugin, | ||||||
| 
 | 
 | ||||||
|  | 	// XXX EXPERIMENTAL
 | ||||||
|  | 	__pre_parse__: function(text, editor, elem){ | ||||||
|  | 		return text | ||||||
|  | 			.replace(/^(--table--)$/m, this.style(editor, elem, 'table-2')) }, | ||||||
|  | 
 | ||||||
| 	__parse__: function(text, editor, elem){ | 	__parse__: function(text, editor, elem){ | ||||||
| 		return text | 		return text | ||||||
| 			.replace(/^\s*(?<!\\)\|\s*((.|\n)*)\s*\|\s*$/,  | 			.replace(/^\s*(?<!\\)\|\s*((.|\n)*)\s*\|\s*$/,  | ||||||
| 				function(_, body){ | 				this.style(editor, elem,  | ||||||
| 					return `<table><tr><td>${ | 					'table', | ||||||
| 						body | 					function(_, body){ | ||||||
| 							.replace(/\s*\|\s*\n\s*\|\s*/gm, '</td></tr>\n<tr><td>') | 						return `<table><tr><td>${ | ||||||
| 							.replace(/\s*\|\s*/gm, '</td><td>') | 							body | ||||||
| 					}</td></td></table>` }) }, | 								.trim() | ||||||
|  | 								.replace(/\s*\|\s*\n\s*\|\s*/gm, '</td></tr>\n<tr><td>') | ||||||
|  | 								.replace(/\s*\|\s*/gm, '</td><td>') | ||||||
|  | 						}</td></td></table>` })) }, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -732,17 +757,6 @@ var escaping = { | |||||||
| //---------------------------------------------------------------------
 | //---------------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| var JSONOutline = { | var JSONOutline = { | ||||||
| 	// Format:
 |  | ||||||
| 	// 	<json> ::= [
 |  | ||||||
| 	// 			{
 |  | ||||||
| 	// 				text: <text>,
 |  | ||||||
| 	// 				children: <json>,
 |  | ||||||
| 	// 				...
 |  | ||||||
| 	// 			},
 |  | ||||||
| 	// 			...
 |  | ||||||
| 	// 		]
 |  | ||||||
| 	json: undefined, |  | ||||||
| 
 |  | ||||||
| 	// format:
 | 	// format:
 | ||||||
| 	// 	{
 | 	// 	{
 | ||||||
| 	// 		<id>: <node>,
 | 	// 		<id>: <node>,
 | ||||||
| @ -777,7 +791,7 @@ var JSONOutline = { | |||||||
| 		var i = 0 | 		var i = 0 | ||||||
| 		// all nodes..
 | 		// all nodes..
 | ||||||
| 		if(node == null || node == 'all' || node == 'visible'){ | 		if(node == null || node == 'all' || node == 'visible'){ | ||||||
| 			for(var e of this.json){ | 			for(var e of this.json()){ | ||||||
| 				yield* this.__iter(e, [i++], node) }  | 				yield* this.__iter(e, [i++], node) }  | ||||||
| 		// single node...
 | 		// single node...
 | ||||||
| 		} else { | 		} else { | ||||||
| @ -789,7 +803,7 @@ var JSONOutline = { | |||||||
| 				this.get(...args),  | 				this.get(...args),  | ||||||
| 				mode) } }, | 				mode) } }, | ||||||
| 	[Symbol.iterator]: function*(mode='all'){ | 	[Symbol.iterator]: function*(mode='all'){ | ||||||
| 		for(var node of this.json){ | 		for(var node of this.json()){ | ||||||
| 			for(var [_, n] of this.__iter(node, mode)){ | 			for(var [_, n] of this.__iter(node, mode)){ | ||||||
| 				yield n } } }, | 				yield n } } }, | ||||||
| 	iter: function*(node, mode){ | 	iter: function*(node, mode){ | ||||||
| @ -817,12 +831,285 @@ var JSONOutline = { | |||||||
| 	crop: function(){}, | 	crop: function(){}, | ||||||
| 	uncrop: function(){}, | 	uncrop: function(){}, | ||||||
| 
 | 
 | ||||||
| 	parseBlockAttrs: function(){}, | 	// NOTE: this is auto-populated by plugin.style(..)...
 | ||||||
| 	parse: function(){}, | 	__styles: undefined, | ||||||
|  | 
 | ||||||
|  | 	// block render...
 | ||||||
|  | 	__code2html__: function(code, elem={}){ | ||||||
|  | 		var that = this | ||||||
|  | 
 | ||||||
|  | 		// only whitespace -> keep element blank...
 | ||||||
|  | 		if(code.trim() == ''){ | ||||||
|  | 			elem.text = code | ||||||
|  | 			return elem } | ||||||
|  | 
 | ||||||
|  | 		// helpers...
 | ||||||
|  | 		var run = function(stage, text){ | ||||||
|  | 			var meth = { | ||||||
|  | 				pre: '__pre_parse__', | ||||||
|  | 				main: '__parse__', | ||||||
|  | 				post: '__post_parse__', | ||||||
|  | 			}[stage] | ||||||
|  | 			return that.threadPlugins(meth, text, that, elem) } | ||||||
|  | 
 | ||||||
|  | 		elem = this.parseBlockAttrs(code, elem) | ||||||
|  | 		code = elem.text | ||||||
|  | 
 | ||||||
|  | 		// stage: pre...
 | ||||||
|  | 		var text = run('pre',  | ||||||
|  | 			// pre-sanitize...
 | ||||||
|  | 			code.replace(/\x00/g, '')) | ||||||
|  | 		// split text into parsable and non-parsable sections...
 | ||||||
|  | 		var sections = text | ||||||
|  | 			// split fomat:
 | ||||||
|  | 			// 	[ text <match> <type> <body>, ... ]
 | ||||||
|  | 			.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)  | ||||||
|  | 
 | ||||||
|  | 		return elem }, | ||||||
|  | 
 | ||||||
|  | 	// output format...
 | ||||||
|  | 	__code2text__: function(code){ | ||||||
|  | 		return code  | ||||||
|  | 			.replace(/(\n\s*)-/g, '$1\\-') }, | ||||||
|  | 	__text2code__: function(text){ | ||||||
|  | 		text = text  | ||||||
|  | 			.replace(/(\n\s*)\\-/g, '$1-')  | ||||||
|  | 		return this.trim_block_text ? | ||||||
|  | 			text.trim() | ||||||
|  | 			: text }, | ||||||
|  | 
 | ||||||
|  | 	__block_attrs__: { | ||||||
|  | 		id: 'attr', | ||||||
|  | 		collapsed: 'attr', | ||||||
|  | 		focused: 'cls', | ||||||
|  | 	}, | ||||||
|  | 	//
 | ||||||
|  | 	//	Parse attrs...
 | ||||||
|  | 	//	.parseBlockAttrs(<text>[, <elem>])
 | ||||||
|  | 	//		-> <elem>
 | ||||||
|  | 	//
 | ||||||
|  | 	//	Parse attrs keeping non-system attrs in .text...
 | ||||||
|  | 	//	.parseBlockAttrs(<text>, true[, <elem>])
 | ||||||
|  | 	//		-> <elem>
 | ||||||
|  | 	//
 | ||||||
|  | 	//	Parse attrs keeping all attrs in .text...
 | ||||||
|  | 	//	.parseBlockAttrs(<text>, 'all'[, <elem>])
 | ||||||
|  | 	//		-> <elem>
 | ||||||
|  | 	//
 | ||||||
|  | 	parseBlockAttrs: function(text, keep=false, elem={}){ | ||||||
|  | 		if(typeof(keep) == 'object'){ | ||||||
|  | 			elem = keep | ||||||
|  | 			keep = typeof(elem) == 'boolean' ? | ||||||
|  | 				elem | ||||||
|  | 				: false } | ||||||
|  | 		var system = this.__block_attrs__ | ||||||
|  | 		var clean = text | ||||||
|  | 			// XXX for some reason changing the first group into (?<= .. )
 | ||||||
|  | 			// 		still eats up the whitespace...
 | ||||||
|  | 			// 		...putting the same pattern in a normal group and 
 | ||||||
|  | 			// 		returning it works fine...
 | ||||||
|  | 			//.replace(/(?<=[\n\h]*)(?:(?:\n|^)\s*\w*\s*::\s*[^\n]*\s*)*$/, 
 | ||||||
|  | 			.replace(/([\n\t ]*)(?:(?:\n|^)[\t ]*\w+[\t ]*::[\t ]*[^\n]+[\t ]*)+$/,  | ||||||
|  | 				function(match, ws){ | ||||||
|  | 					var attrs = match | ||||||
|  | 						.trim() | ||||||
|  | 						.split(/(?:[\t ]*::[\t ]*|[\t ]*\n[\t ]*)/g) | ||||||
|  | 					while(attrs.length > 0){ | ||||||
|  | 						var [name, val] = attrs.splice(0, 2) | ||||||
|  | 						elem[name] =  | ||||||
|  | 							val == 'true' ? | ||||||
|  | 				   				true | ||||||
|  | 							: val == 'false' ? | ||||||
|  | 								false | ||||||
|  | 							: val  | ||||||
|  | 						// keep non-system attrs...
 | ||||||
|  | 						if(keep  | ||||||
|  | 								&& !(name in system)){ | ||||||
|  | 							ws += `\n${name}::${val}` } }  | ||||||
|  | 					return ws }) | ||||||
|  | 		elem.text = keep == 'all' ?  | ||||||
|  | 			text  | ||||||
|  | 			: clean | ||||||
|  | 		return elem }, | ||||||
|  | 	parse: function(text){ | ||||||
|  | 		var that = this | ||||||
|  | 		text = text | ||||||
|  | 			.replace(/^[ \t]*\n/, '') | ||||||
|  | 		text = ('\n' + text) | ||||||
|  | 			.split(/\n([ \t]*)(?:- |-\s*$)/gm) | ||||||
|  | 			.slice(1) | ||||||
|  | 		var tab = ' '.repeat(this.tab_size || 8) | ||||||
|  | 		var level = function(lst, prev_sep=undefined, parent=[]){ | ||||||
|  | 			while(lst.length > 0){ | ||||||
|  | 				sep = lst[0].replace(/\t/gm, tab) | ||||||
|  | 				// 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 attrs = that.parseBlockAttrs(block) | ||||||
|  | 					attrs.text = that.__text2code__(attrs.text | ||||||
|  | 						// normalize indent...
 | ||||||
|  | 						.split(new RegExp('\n'+sep+'  ', 'g')) | ||||||
|  | 						.join('\n')) | ||||||
|  | 					parent.push({  | ||||||
|  | 						collapsed: false, | ||||||
|  | 						focused: false, | ||||||
|  | 						...attrs, | ||||||
|  | 						children: [], | ||||||
|  | 					}) | ||||||
|  | 				// indent...
 | ||||||
|  | 				} else { | ||||||
|  | 					parent.at(-1).children = level(lst, sep) } } | ||||||
|  | 			return parent } | ||||||
|  | 		return level(text) }, | ||||||
| 
 | 
 | ||||||
| 	data: function(){}, | 	data: function(){}, | ||||||
| 	load: function(){}, | 	load: function(){}, | ||||||
| 	text: function(){}, | 
 | ||||||
|  | 	// Format:
 | ||||||
|  | 	// 	<json> ::= [
 | ||||||
|  | 	// 			{
 | ||||||
|  | 	// 				text: <text>,
 | ||||||
|  | 	// 				children: <json>,
 | ||||||
|  | 	// 				...
 | ||||||
|  | 	// 			},
 | ||||||
|  | 	// 			...
 | ||||||
|  | 	// 		]
 | ||||||
|  | 	// XXX
 | ||||||
|  | 	json: function(){}, | ||||||
|  | 
 | ||||||
|  | 	// XXX add option to customize indent size...
 | ||||||
|  | 	text: function(node, indent, level){ | ||||||
|  | 		// .text(<indent>, <level>)
 | ||||||
|  | 		if(typeof(node) == 'string'){ | ||||||
|  | 			;[node, indent='  ', level=''] = [undefined, ...arguments] } | ||||||
|  | 		node ??= this.json(node) | ||||||
|  | 		indent ??= '  ' | ||||||
|  | 		level ??= '' | ||||||
|  | 		var text = [] | ||||||
|  | 		for(var elem of node){ | ||||||
|  | 			text.push(  | ||||||
|  | 				level +'- ' | ||||||
|  | 					+ this.__code2text__(elem.text) | ||||||
|  | 						.replace(/\n/g, '\n'+ level +'  ')  | ||||||
|  | 					// attrs... 
 | ||||||
|  | 					+ (Object.keys(elem) | ||||||
|  | 						.reduce(function(res, attr){ | ||||||
|  | 							return (attr == 'text'  | ||||||
|  | 									|| attr == 'children') ? | ||||||
|  | 								res | ||||||
|  | 								: res  | ||||||
|  | 									+ (elem[attr] ? | ||||||
|  | 										'\n'+level+'  ' + `${ attr }:: ${ elem[attr] }` | ||||||
|  | 										: '') }, '')), | ||||||
|  | 				(elem.children  | ||||||
|  | 						&& elem.children.length > 0) ? | ||||||
|  | 					this.text(elem.children || [], indent, level+indent)  | ||||||
|  | 					: [] ) } | ||||||
|  | 		return text | ||||||
|  | 			.flat() | ||||||
|  | 			.join('\n') }, | ||||||
|  | 
 | ||||||
|  | 	// XXX add read-only option...
 | ||||||
|  | 	htmlBlock: function(data, options={}){ | ||||||
|  | 		var that = this | ||||||
|  | 
 | ||||||
|  | 		var parsed = this.__code2html__(data.text, {...data})  | ||||||
|  | 
 | ||||||
|  | 		var cls = parsed.style ?? [] | ||||||
|  | 		delete parsed.style | ||||||
|  | 
 | ||||||
|  | 		var attrs = [] | ||||||
|  | 
 | ||||||
|  | 		for(var [attr, value] of Object.entries({...data, ...parsed})){ | ||||||
|  | 			if(attr == 'children' || attr == 'text'){ | ||||||
|  | 				continue } | ||||||
|  | 			var i | ||||||
|  | 			var type = this.__block_attrs__[attr] | ||||||
|  | 			if(type == 'cls'){ | ||||||
|  | 				value ? | ||||||
|  | 						cls.push(attr) | ||||||
|  | 					: (i = cls.indexOf(attr)) >= 0 ? | ||||||
|  | 						cls.splice(i, 1) | ||||||
|  | 					: undefined | ||||||
|  | 			} else if(type == 'attr'  | ||||||
|  | 					|| type == undefined){ | ||||||
|  | 				typeof(value) == 'boolean'? | ||||||
|  | 						(value ? | ||||||
|  | 							attrs.push(attr) | ||||||
|  | 							: (i = attrs.indexOf(attr)) >= 0 ? | ||||||
|  | 								attrs.splice(i, 1) | ||||||
|  | 							: undefined) | ||||||
|  | 					: value != null ? | ||||||
|  | 						attrs.push(`${attr}="${value}"`) | ||||||
|  | 					: (i = attrs.indexOf(attr)) >= 0 ? | ||||||
|  | 						attrs.splice(i, 1) | ||||||
|  | 					: undefined } } | ||||||
|  | 
 | ||||||
|  | 		var children = data.children | ||||||
|  | 			.map(function(data){ | ||||||
|  | 				return that.htmlBlock(data) }) | ||||||
|  | 			.join('') | ||||||
|  | 		return ( | ||||||
|  | `<div class="block ${ cls.join(' ') }" tabindex="0" ${ attrs.join(' ') }>\
 | ||||||
|  | <textarea class="code text" value="${ data.text }"></textarea>\ | ||||||
|  | <span class="view text">${ parsed.text }</span>\ | ||||||
|  | <div class="children">${ children }</div>\ | ||||||
|  | </div>`) }, | ||||||
|  | 	html: function(data, options=false){ | ||||||
|  | 		var that = this | ||||||
|  | 		if(typeof(data) == 'boolean'){ | ||||||
|  | 			options = data | ||||||
|  | 			data = undefined } | ||||||
|  | 		data = data == null ? | ||||||
|  | 				this.json() | ||||||
|  | 			: typeof(data) == 'string' ? | ||||||
|  | 				this.parse(data) | ||||||
|  | 			: data instanceof Array ? | ||||||
|  | 				data | ||||||
|  | 			: [data] | ||||||
|  | 		options =  | ||||||
|  | 			typeof(options) == 'boolean' ? | ||||||
|  | 				{full: options} | ||||||
|  | 				: (options  | ||||||
|  | 					?? {}) | ||||||
|  | 
 | ||||||
|  | 		var nodes = data | ||||||
|  | 			.map(function(data){ | ||||||
|  | 				return that.htmlBlock(data) }) | ||||||
|  | 			.join('')  | ||||||
|  | 
 | ||||||
|  | 		return !options.full ? | ||||||
|  | 			nodes | ||||||
|  | 			: ( | ||||||
|  | `<div class="editor" autofocus>\
 | ||||||
|  | <div class="header"></div>\ | ||||||
|  | <textarea class="code"></textarea>\ | ||||||
|  | <div class="outline" tabindex="0">${ nodes }</div>\ | ||||||
|  | </div>`) }, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -830,6 +1117,8 @@ var JSONOutline = { | |||||||
| // XXX experiment with a concatinative model...
 | // XXX experiment with a concatinative model...
 | ||||||
| // 		.get(..) -> Outline (view)
 | // 		.get(..) -> Outline (view)
 | ||||||
| var Outline = { | var Outline = { | ||||||
|  | 	__proto__: JSONOutline, | ||||||
|  | 
 | ||||||
| 	dom: undefined, | 	dom: undefined, | ||||||
| 
 | 
 | ||||||
| 	// config...
 | 	// config...
 | ||||||
| @ -1039,18 +1328,22 @@ var Outline = { | |||||||
| 			: offset == 'visible' ? | 			: offset == 'visible' ? | ||||||
| 				[...node.querySelectorAll('.block')]  | 				[...node.querySelectorAll('.block')]  | ||||||
| 					.filter(function(e){ | 					.filter(function(e){ | ||||||
| 						return e.querySelector('.view').offsetParent != null }) | 						//return e.querySelector('.view').offsetParent != null })
 | ||||||
|  | 						return e.offsetParent != null }) | ||||||
| 			: offset == 'viewport' ? | 			: offset == 'viewport' ? | ||||||
| 				[...node.querySelectorAll('.block')]  | 				[...node.querySelectorAll('.block')]  | ||||||
| 					.filter(function(e){ | 					.filter(function(e){ | ||||||
| 						return e.querySelector('.view').offsetParent != null  | 						//return e.querySelector('.view').offsetParent != null 
 | ||||||
| 							&& e.querySelector('.code').visibleInViewport() }) | 						//	&& e.querySelector('.code').visibleInViewport() })
 | ||||||
|  | 						return e.offsetParent != null  | ||||||
|  | 							&& e.visibleInViewport() }) | ||||||
| 			: offset == 'editable' ? | 			: offset == 'editable' ? | ||||||
| 				[...node.querySelectorAll('.block>.code')]  | 				[...node.querySelectorAll('.block>.code')]  | ||||||
| 			: offset == 'selected' ? | 			: offset == 'selected' ? | ||||||
| 				[...node.querySelectorAll('.block[selected]')]  | 				[...node.querySelectorAll('.block[selected]')]  | ||||||
| 					.filter(function(e){ | 					.filter(function(e){ | ||||||
| 						return e.querySelector('.view').offsetParent != null })  | 						//return e.querySelector('.view').offsetParent != null }) 
 | ||||||
|  | 						return e.offsetParent != null })  | ||||||
| 			: offset == 'children' ? | 			: offset == 'children' ? | ||||||
| 				children(node) | 				children(node) | ||||||
| 			: offset == 'siblings' ? | 			: offset == 'siblings' ? | ||||||
| @ -1141,13 +1434,15 @@ var Outline = { | |||||||
| 	}, | 	}, | ||||||
| 	// NOTE: this does not internally handle undo as it would be too 
 | 	// NOTE: this does not internally handle undo as it would be too 
 | ||||||
| 	// 		granular...
 | 	// 		granular...
 | ||||||
|  | 	_updateTextareaSize: function(elem){ | ||||||
|  | 		elem.style.height = getComputedStyle(elem.nextSibling).height }, | ||||||
| 	update: function(node='focused', data){ | 	update: function(node='focused', data){ | ||||||
| 		var node = this.get(node) | 		var node = this.get(node) | ||||||
| 		data ??= this.data(node, false) | 		data ??= this.data(node, false) | ||||||
| 
 | 
 | ||||||
| 		var parsed = {} | 		var parsed = {} | ||||||
| 		if('text' in data){ | 		if('text' in data){ | ||||||
| 			var text = node.querySelector('.code') | 			var code = node.querySelector('.code') | ||||||
| 			var html = node.querySelector('.view') | 			var html = node.querySelector('.view') | ||||||
| 			if(this.__code2html__){ | 			if(this.__code2html__){ | ||||||
| 				// NOTE: we are ignoring the .collapsed attr here 
 | 				// NOTE: we are ignoring the .collapsed attr here 
 | ||||||
| @ -1171,8 +1466,10 @@ var Outline = { | |||||||
| 						// NOTE: adding a space here is done to prevent the browser 
 | 						// NOTE: adding a space here is done to prevent the browser 
 | ||||||
| 						// 		from hiding the last newline...
 | 						// 		from hiding the last newline...
 | ||||||
| 						: data.text + ' ' } | 						: data.text + ' ' } | ||||||
| 			text.value = data.text  | 			code.value = data.text  | ||||||
| 			text.updateSize() } | 			code.updateSize()  | ||||||
|  | 			// NOTE: this will have no effect if the element is not attached...
 | ||||||
|  | 			this._updateTextareaSize(code) } | ||||||
| 
 | 
 | ||||||
| 		for(var [attr, value] of Object.entries({...data, ...parsed})){ | 		for(var [attr, value] of Object.entries({...data, ...parsed})){ | ||||||
| 			if(attr == 'children' || attr == 'text'){ | 			if(attr == 'children' || attr == 'text'){ | ||||||
| @ -1425,70 +1722,6 @@ var Outline = { | |||||||
| 		;[this.__redo_stack] = this.__undo(this.__redo_stack) | 		;[this.__redo_stack] = this.__undo(this.__redo_stack) | ||||||
| 		return this }, | 		return this }, | ||||||
| 
 | 
 | ||||||
| 	// block render...
 |  | ||||||
| 	// NOTE: this is auto-populated by .__code2html__(..)
 |  | ||||||
| 	__styles: undefined, |  | ||||||
| 	__code2html__: function(code, elem={}){ |  | ||||||
| 		var that = this |  | ||||||
| 
 |  | ||||||
| 		// only whitespace -> keep element blank...
 |  | ||||||
| 		if(code.trim() == ''){ |  | ||||||
| 			elem.text = code |  | ||||||
| 			return elem } |  | ||||||
| 
 |  | ||||||
| 		// helpers...
 |  | ||||||
| 		var run = function(stage, text){ |  | ||||||
| 			var meth = { |  | ||||||
| 				pre: '__pre_parse__', |  | ||||||
| 				main: '__parse__', |  | ||||||
| 				post: '__post_parse__', |  | ||||||
| 			}[stage] |  | ||||||
| 			return that.threadPlugins(meth, text, that, elem) } |  | ||||||
| 
 |  | ||||||
| 		elem = this.parseBlockAttrs(code, elem) |  | ||||||
| 		code = elem.text |  | ||||||
| 
 |  | ||||||
| 		// stage: pre...
 |  | ||||||
| 		var text = run('pre',  |  | ||||||
| 			// pre-sanitize...
 |  | ||||||
| 			code.replace(/\x00/g, '')) |  | ||||||
| 		// split text into parsable and non-parsable sections...
 |  | ||||||
| 		var sections = text |  | ||||||
| 			// split fomat:
 |  | ||||||
| 			// 	[ text <match> <type> <body>, ... ]
 |  | ||||||
| 			.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)  |  | ||||||
| 
 |  | ||||||
| 		return elem }, |  | ||||||
| 	// output format...
 |  | ||||||
| 	__code2text__: function(code){ |  | ||||||
| 		return code  |  | ||||||
| 			.replace(/(\n\s*)-/g, '$1\\-') }, |  | ||||||
| 	__text2code__: function(text){ |  | ||||||
| 		text = text  |  | ||||||
| 			.replace(/(\n\s*)\\-/g, '$1-')  |  | ||||||
| 		return this.trim_block_text ? |  | ||||||
| 			text.trim() |  | ||||||
| 			: text }, |  | ||||||
| 
 |  | ||||||
| 	// serialization...
 | 	// serialization...
 | ||||||
| 	data: function(elem, deep=true){ | 	data: function(elem, deep=true){ | ||||||
| 		elem = this.get(elem)	 | 		elem = this.get(elem)	 | ||||||
| @ -1521,121 +1754,6 @@ var Outline = { | |||||||
| 		return children | 		return children | ||||||
| 			.map(function(elem){ | 			.map(function(elem){ | ||||||
| 				return that.data(elem) }) }, | 				return that.data(elem) }) }, | ||||||
| 	// XXX add option to customize indent size...
 |  | ||||||
| 	text: function(node, indent, level){ |  | ||||||
| 		// .text(<indent>, <level>)
 |  | ||||||
| 		if(typeof(node) == 'string'){ |  | ||||||
| 			;[node, indent='  ', level=''] = [undefined, ...arguments] } |  | ||||||
| 		node ??= this.json(node) |  | ||||||
| 		indent ??= '  ' |  | ||||||
| 		level ??= '' |  | ||||||
| 		var text = [] |  | ||||||
| 		for(var elem of node){ |  | ||||||
| 			text.push(  |  | ||||||
| 				level +'- ' |  | ||||||
| 					+ this.__code2text__(elem.text) |  | ||||||
| 						.replace(/\n/g, '\n'+ level +'  ')  |  | ||||||
| 					// attrs... 
 |  | ||||||
| 					+ (Object.keys(elem) |  | ||||||
| 						.reduce(function(res, attr){ |  | ||||||
| 							return (attr == 'text'  |  | ||||||
| 									|| attr == 'children') ? |  | ||||||
| 								res |  | ||||||
| 								: res  |  | ||||||
| 									+ (elem[attr] ? |  | ||||||
| 										'\n'+level+'  ' + `${ attr }:: ${ elem[attr] }` |  | ||||||
| 										: '') }, '')), |  | ||||||
| 				(elem.children  |  | ||||||
| 						&& elem.children.length > 0) ? |  | ||||||
| 					this.text(elem.children || [], indent, level+indent)  |  | ||||||
| 					: [] ) } |  | ||||||
| 		return text |  | ||||||
| 			.flat() |  | ||||||
| 			.join('\n') }, |  | ||||||
| 
 |  | ||||||
| 	//
 |  | ||||||
| 	//	Parse attrs...
 |  | ||||||
| 	//	.parseBlockAttrs(<text>[, <elem>])
 |  | ||||||
| 	//		-> <elem>
 |  | ||||||
| 	//
 |  | ||||||
| 	//	Parse attrs keeping non-system attrs in .text...
 |  | ||||||
| 	//	.parseBlockAttrs(<text>, true[, <elem>])
 |  | ||||||
| 	//		-> <elem>
 |  | ||||||
| 	//
 |  | ||||||
| 	//	Parse attrs keeping all attrs in .text...
 |  | ||||||
| 	//	.parseBlockAttrs(<text>, 'all'[, <elem>])
 |  | ||||||
| 	//		-> <elem>
 |  | ||||||
| 	//
 |  | ||||||
| 	parseBlockAttrs: function(text, keep=false, elem={}){ |  | ||||||
| 		if(typeof(keep) == 'object'){ |  | ||||||
| 			elem = keep |  | ||||||
| 			keep = typeof(elem) == 'boolean' ? |  | ||||||
| 				elem |  | ||||||
| 				: false } |  | ||||||
| 		var system = this.__block_attrs__ |  | ||||||
| 		var clean = text |  | ||||||
| 			// XXX for some reason changing the first group into (?<= .. )
 |  | ||||||
| 			// 		still eats up the whitespace...
 |  | ||||||
| 			// 		...putting the same pattern in a normal group and 
 |  | ||||||
| 			// 		returning it works fine...
 |  | ||||||
| 			//.replace(/(?<=[\n\h]*)(?:(?:\n|^)\s*\w*\s*::\s*[^\n]*\s*)*$/, 
 |  | ||||||
| 			.replace(/([\n\t ]*)(?:(?:\n|^)[\t ]*\w+[\t ]*::[\t ]*[^\n]+[\t ]*)+$/,  |  | ||||||
| 				function(match, ws){ |  | ||||||
| 					var attrs = match |  | ||||||
| 						.trim() |  | ||||||
| 						.split(/(?:[\t ]*::[\t ]*|[\t ]*\n[\t ]*)/g) |  | ||||||
| 					while(attrs.length > 0){ |  | ||||||
| 						var [name, val] = attrs.splice(0, 2) |  | ||||||
| 						elem[name] =  |  | ||||||
| 							val == 'true' ? |  | ||||||
| 				   				true |  | ||||||
| 							: val == 'false' ? |  | ||||||
| 								false |  | ||||||
| 							: val  |  | ||||||
| 						// keep non-system attrs...
 |  | ||||||
| 						if(keep  |  | ||||||
| 								&& !(name in system)){ |  | ||||||
| 							ws += `\n${name}::${val}` } }  |  | ||||||
| 					return ws }) |  | ||||||
| 		elem.text = keep == 'all' ?  |  | ||||||
| 			text  |  | ||||||
| 			: clean |  | ||||||
| 		return elem }, |  | ||||||
| 	parse: function(text){ |  | ||||||
| 		var that = this |  | ||||||
| 		text = text |  | ||||||
| 			.replace(/^[ \t]*\n/, '') |  | ||||||
| 		text = ('\n' + text) |  | ||||||
| 			.split(/\n([ \t]*)(?:- |-\s*$)/gm) |  | ||||||
| 			.slice(1) |  | ||||||
| 		var tab = ' '.repeat(this.tab_size || 8) |  | ||||||
| 		var level = function(lst, prev_sep=undefined, parent=[]){ |  | ||||||
| 			while(lst.length > 0){ |  | ||||||
| 				sep = lst[0].replace(/\t/gm, tab) |  | ||||||
| 				// 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 attrs = that.parseBlockAttrs(block) |  | ||||||
| 					attrs.text = that.__text2code__(attrs.text |  | ||||||
| 						// normalize indent...
 |  | ||||||
| 						.split(new RegExp('\n'+sep+'  ', 'g')) |  | ||||||
| 						.join('\n')) |  | ||||||
| 					parent.push({  |  | ||||||
| 						collapsed: false, |  | ||||||
| 						focused: false, |  | ||||||
| 						...attrs, |  | ||||||
| 						children: [], |  | ||||||
| 					}) |  | ||||||
| 				// indent...
 |  | ||||||
| 				} else { |  | ||||||
| 					parent.at(-1).children = level(lst, sep) } } |  | ||||||
| 			return parent } |  | ||||||
| 		return level(text) }, |  | ||||||
| 
 | 
 | ||||||
| 	// XXX should this handle children???
 | 	// XXX should this handle children???
 | ||||||
| 	// XXX revise name...
 | 	// XXX revise name...
 | ||||||
| @ -1699,6 +1817,8 @@ var Outline = { | |||||||
| 				cur[place](block) | 				cur[place](block) | ||||||
| 			: undefined  | 			: undefined  | ||||||
| 
 | 
 | ||||||
|  | 			this._updateTextareaSize(code) | ||||||
|  | 
 | ||||||
| 			this.setUndo(this.path(cur), 'remove', [this.path(block)]) } | 			this.setUndo(this.path(cur), 'remove', [this.path(block)]) } | ||||||
| 		return block }, | 		return block }, | ||||||
| 	// XXX see inside...
 | 	// XXX see inside...
 | ||||||
| @ -1728,8 +1848,9 @@ var Outline = { | |||||||
| 		// 		...this is done by expanding the textarea to the element 
 | 		// 		...this is done by expanding the textarea to the element 
 | ||||||
| 		// 		size and enabling it to intercept clicks correctly...
 | 		// 		size and enabling it to intercept clicks correctly...
 | ||||||
| 		setTimeout(function(){ | 		setTimeout(function(){ | ||||||
|  | 			var f = that._updateTextareaSize | ||||||
| 			for(var e of [...that.outline.querySelectorAll('textarea')]){ | 			for(var e of [...that.outline.querySelectorAll('textarea')]){ | ||||||
| 				e.updateSize() } }, 0) | 				f(e) } }, 0) | ||||||
| 		// restore focus...
 | 		// restore focus...
 | ||||||
| 		this.focus() | 		this.focus() | ||||||
| 		return this }, | 		return this }, | ||||||
|  | |||||||
| @ -70,6 +70,10 @@ var setup = function(){ | |||||||
|             (BUG also the above line is not italic -- can't reproduce) |             (BUG also the above line is not italic -- can't reproduce) | ||||||
|       - clicking right of the last line places cursor wrong |       - clicking right of the last line places cursor wrong | ||||||
|         - _this is a problem with the new version of `getMarkdownOffset(..)`_ |         - _this is a problem with the new version of `getMarkdownOffset(..)`_ | ||||||
|  |       - DONE text	text	text | ||||||
|  |       - DONE text text text | ||||||
|  |         text text text | ||||||
|  |         text text text | ||||||
|       - DONE M |       - DONE M | ||||||
|         M can't place cursor before first char |         M can't place cursor before first char | ||||||
|         M |         M | ||||||
| @ -87,6 +91,9 @@ var setup = function(){ | |||||||
|         text text text |         text text text | ||||||
|         ``` |         ``` | ||||||
|         text text text |         text text text | ||||||
|  |       - DONE |text|text|text| | ||||||
|  |         |text|text|text| | ||||||
|  |         |text|text|text| | ||||||
|   - BUG: parser: code blocks do not ignore single back-quotes... |   - BUG: parser: code blocks do not ignore single back-quotes... | ||||||
|     - ``` |     - ``` | ||||||
|       x = `moo` |       x = `moo` | ||||||
| @ -103,8 +110,14 @@ var setup = function(){ | |||||||
|       - side margins are a bit too large (account for toolbat to the right) |       - side margins are a bit too large (account for toolbat to the right) | ||||||
|   - |   - | ||||||
| - ## ToDo: | - ## ToDo: | ||||||
|  |   - Q: should we use `HTMLTextAreaElement.autoUpdateSize(..)` or handle it globally in setup??? | ||||||
|  |     - _...I'm leaning towards the later..._ | ||||||
|   - Q: can we place a cursor in a table correctly??? |   - Q: can we place a cursor in a table correctly??? | ||||||
|   - Q: should tables be text-based markdown or higher-level? |   - Q: should tables be text-based markdown or higher-level? | ||||||
|  |     - for reference a normal table | ||||||
|  |       - | col 1 | col 2 | col 3 | | ||||||
|  |         | moo | foo | boo | | ||||||
|  |         | 1 | 2 | 3 | | ||||||
|     - block-based -- adjacent blocks in table format (a-la markdown) are treated as rows of one table... |     - block-based -- adjacent blocks in table format (a-la markdown) are treated as rows of one table... | ||||||
|       - here is an example |       - here is an example | ||||||
|       - | col 1 | col 2 | col 3 | |       - | col 1 | col 2 | col 3 | | ||||||
| @ -114,17 +127,18 @@ var setup = function(){ | |||||||
|       - not yet sure how are we going to allign columns (CSS preffered) |       - not yet sure how are we going to allign columns (CSS preffered) | ||||||
|     - block-children -- similar to how lists are done now |     - block-children -- similar to how lists are done now | ||||||
|       - a demo |       - a demo | ||||||
|       - table: |       - --table-- | ||||||
|         - | A | B | B | |         - | A | B | C | | ||||||
|         - | 1 | 2 | 3 | |         - | 1 | 2 | 3 | | ||||||
|         - | X | y | Z | |         - | moo | foo | boo | | ||||||
|       - |       - | ||||||
|       - the header may be used as:: |       - the header may be used as:: | ||||||
|         - header row |         - header row | ||||||
|         - caption text |         - caption text | ||||||
|         - both? |         - both? | ||||||
|       - see CSS grids + 'display: contents' (might help hide non-grid elemnts... |       - Q: how do we handle indenting a table row? | ||||||
|     -  |       - Q: how do we handle unmarked text? | ||||||
|  |       -  | ||||||
|     - might be fun to make the general syntax (with "=" removed) to be compatible with markdown...  |     - might be fun to make the general syntax (with "=" removed) to be compatible with markdown...  | ||||||
|     - might also be fun to auto-generat (template) new blocks within a table... |     - might also be fun to auto-generat (template) new blocks within a table... | ||||||
|     - this would greatly simplify table navigation and creation |     - this would greatly simplify table navigation and creation | ||||||
| @ -374,13 +388,13 @@ var setup = function(){ | |||||||
|       | a-s        | toggle status                | |       | a-s        | toggle status                | | ||||||
|       | a-x        | toggle status DONE           | |       | a-x        | toggle status DONE           | | ||||||
|       | a-r        | toggle status REJECT         | |       | a-r        | toggle status REJECT         | | ||||||
| 	  | c-z        | normal: undo                 | |       | c-z        | normal: undo                 | | ||||||
| 	  | c-s-z      | normal: redo                 | |       | c-s-z      | normal: redo                 | | ||||||
| 	  | c          | normal: crop current node    | |       | c          | normal: crop current node    | | ||||||
|       | enter      | normal: edit node            | |       | enter      | normal: edit node            | | ||||||
|       |            | edit: create node below      | |       |            | edit: create node below      | | ||||||
|       | esc        | crop: exit crop              | |       | esc        | crop: exit crop              | | ||||||
| 	  |            | edit: exit edit mode         |  |       |            | edit: exit edit mode         |  | ||||||
|   - ### Formatting |   - ### Formatting | ||||||
|     - The formatting mostly adheres to the markdown spec with a few minor differences |     - The formatting mostly adheres to the markdown spec with a few minor differences | ||||||
|     - |     - | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user