mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-11-03 04:40:10 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			396 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			396 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
/**********************************************************************
 | 
						|
* 
 | 
						|
*
 | 
						|
*
 | 
						|
**********************************************************************/
 | 
						|
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
 | 
						|
(function(require){ var module={} // make module AMD/node compatible...
 | 
						|
/*********************************************************************/
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/*********************************************************************/
 | 
						|
 | 
						|
var selectElemText =
 | 
						|
module.selectElemText = 
 | 
						|
function(elem){
 | 
						|
	var range = document.createRange()
 | 
						|
	range.selectNodeContents(elem)
 | 
						|
	var sel = window.getSelection()
 | 
						|
	sel.removeAllRanges()
 | 
						|
	sel.addRange(range)
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
// XXX make this global...
 | 
						|
var getCaretOffset = 
 | 
						|
module.getCaretOffset =
 | 
						|
function(elem){
 | 
						|
	var s = window.getSelection()
 | 
						|
	if(s.rangeCount == 0){
 | 
						|
		return -1
 | 
						|
	}
 | 
						|
	var r = s.getRangeAt(0)
 | 
						|
	var pre = r.cloneRange()
 | 
						|
	pre.selectNodeContents(elem)
 | 
						|
	pre.setEnd(r.endContainer, r.endOffset)
 | 
						|
	return pre.toString().length || 0
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
var selectionCollapsed =
 | 
						|
module.selectionCollapsed =
 | 
						|
function(elem){
 | 
						|
		var s = window.getSelection()
 | 
						|
		if(s.rangeCount == 0){
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		return s.getRangeAt(0).cloneRange().collapsed
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
//---------------------------------------------------------------------
 | 
						|
// XXX experiment
 | 
						|
if(typeof(jQuery) != typeof(undefined)){
 | 
						|
	jQuery.fn._drag = function(){
 | 
						|
		var dragging = false
 | 
						|
		var s, 
 | 
						|
			px, py
 | 
						|
 | 
						|
		var elem = $(this)
 | 
						|
			.on('mousedown touchstart', function(evt){
 | 
						|
				dragging = true
 | 
						|
				px = evt.clientX
 | 
						|
				px = evt.clientY
 | 
						|
 | 
						|
				s = elem.rscale()
 | 
						|
			})
 | 
						|
			.on('mousemove touchmove', function(evt){
 | 
						|
				if(!dragging){
 | 
						|
					return
 | 
						|
				}
 | 
						|
 | 
						|
				var x = evt.clientX 
 | 
						|
				var dx = px - x
 | 
						|
				px = x
 | 
						|
 | 
						|
				var y = evt.clientY 
 | 
						|
				var dy = py - y
 | 
						|
				py = y
 | 
						|
 | 
						|
				elem
 | 
						|
					.velocity('stop')
 | 
						|
					.velocity({
 | 
						|
						translateX: '-=' + (dx / s),
 | 
						|
						translateY: '-=' + (dy / s),
 | 
						|
					}, 0)
 | 
						|
			})
 | 
						|
			.on('mouseup touchend', function(evt){
 | 
						|
				dragging = false
 | 
						|
				elem.velocity('stop')
 | 
						|
			})
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	jQuery.fn.selectText = function(mode){
 | 
						|
		var range = document.createRange()
 | 
						|
 | 
						|
		this.each(function(){
 | 
						|
			range.selectNodeContents(this)
 | 
						|
		})
 | 
						|
 | 
						|
		var sel = window.getSelection()
 | 
						|
		sel.removeAllRanges()
 | 
						|
 | 
						|
		mode === null
 | 
						|
			|| sel.addRange(range)
 | 
						|
 | 
						|
		return this
 | 
						|
	}
 | 
						|
	jQuery.fn.deselectText = function(){ this.selectText(null) }
 | 
						|
	jQuery.fn.caretOffset = function(){ return getCaretOffset(this) }
 | 
						|
	jQuery.fn.selectionCollapsed = function(){ return selectionCollapsed(this) }
 | 
						|
 | 
						|
 | 
						|
	var keyboard = require('lib/keyboard')
 | 
						|
 | 
						|
	// Make element editable...
 | 
						|
	//
 | 
						|
	// Options format:
 | 
						|
	// 	{
 | 
						|
	// 		// activate (focus) element...
 | 
						|
	// 		//
 | 
						|
	// 		// NOTE: this will also select the element text...
 | 
						|
	// 		activate: false,
 | 
						|
	//
 | 
						|
	// 		// set multi-line edit mode...
 | 
						|
	// 		multiline: false,
 | 
						|
	//
 | 
						|
	// 		// if true in multi-line mode, accept filed on Enter, while
 | 
						|
	// 		// ctrl-Enter / meta-Enter insert a new line; otherwise
 | 
						|
	// 		// ctrl-Enter / meta-Enter will accept the edit.
 | 
						|
	// 		accept_on_enter: true,
 | 
						|
	//
 | 
						|
	// 		// clear element value on edit...
 | 
						|
	// 		clear_on_edit: false,
 | 
						|
	//
 | 
						|
	// 		// reset value on commit/abort...
 | 
						|
	// 		// XXX revise default...
 | 
						|
	// 		reset_on_commit: true,
 | 
						|
	// 		reset_on_abort: true,
 | 
						|
	//
 | 
						|
	// 		// blur element on commit/abort...
 | 
						|
	// 		blur_on_commit: false,
 | 
						|
	// 		blur_on_abort: false,
 | 
						|
	//
 | 
						|
	// 		// restore focus before disabling the editor...
 | 
						|
	// 		keep_focus_on_parent: true,
 | 
						|
	//
 | 
						|
	// 		// clear selection on commit/abort...
 | 
						|
	// 		clear_selection_on_commit: true,
 | 
						|
	// 		clear_selection_on_abort: true,
 | 
						|
	//
 | 
						|
	// 		// If false unhandled key events will not be propagated to 
 | 
						|
	// 		// parents...
 | 
						|
	// 		propagate_unhandled_keys: true,
 | 
						|
	//
 | 
						|
	// 		// If false the element editable state will not be reset to
 | 
						|
	// 		// the original when edit is done...
 | 
						|
	// 		reset_on_done: true,
 | 
						|
	//
 | 
						|
	// 		// Keys that will abort the edit...
 | 
						|
	// 		abort_keys: [
 | 
						|
	// 			'Esc',
 | 
						|
	// 		],
 | 
						|
	// 	}
 | 
						|
	//
 | 
						|
	// This listens to these events triggerable by user:
 | 
						|
	// 	'edit-commit'		- will commit changes, this is passed the 
 | 
						|
	// 							new text just edited.
 | 
						|
	// 	'edit-abort'		- will reset field, this is passed the 
 | 
						|
	// 							original text before the edit.
 | 
						|
	//
 | 
						|
	// These events get passed the relevant text, but the element is 
 | 
						|
	// likely to be already reset to a different state, to get the 
 | 
						|
	// element before any state change is started use one of the 
 | 
						|
	// following variants:
 | 
						|
	// 	'edit-committing'	- triggered within 'edit-commit' but before
 | 
						|
	// 							anything is changed, gets passed the final
 | 
						|
	// 							text (same as 'edit-commit')
 | 
						|
	// 	'edit-aborting'		- triggered within 'edit-abort' but before 
 | 
						|
	// 							anything is changed, gets passed the 
 | 
						|
	// 							original text value (same as 'edit-abort')
 | 
						|
	//
 | 
						|
	// This will try and preserve element content DOM when resetting.
 | 
						|
	//
 | 
						|
	//
 | 
						|
	// NOTE: removing tabindex will reset focus, so this will attempt to 
 | 
						|
	// 		focus the first [tabindex] element up the tree...
 | 
						|
	//
 | 
						|
	// XXX add option to select the element on start or just focus it...
 | 
						|
	// 		.activate: 'select' | true | false
 | 
						|
	// XXX should we just use form elements???
 | 
						|
	// 		...it's a trade-off, here we add editing functionality and fight
 | 
						|
	// 		a bit the original function, in an input we'll need to fight part
 | 
						|
	// 		of the editing functionality and add our own navigation...
 | 
						|
	// XXX move this to a more generic spot...
 | 
						|
	jQuery.fn.makeEditable = function(options){
 | 
						|
		var that = this
 | 
						|
 | 
						|
		if(options == false){
 | 
						|
			this
 | 
						|
				.removeAttr('contenteditable')
 | 
						|
				.removeAttr('tabindex')
 | 
						|
				.removeClass('editable-field')
 | 
						|
 | 
						|
			var events = this.data('editable-field-events')
 | 
						|
			for(var e in events){
 | 
						|
				this.off(e, events[e])
 | 
						|
			}
 | 
						|
			this.removeData('editable-field-events')
 | 
						|
 | 
						|
			return this
 | 
						|
		}
 | 
						|
 | 
						|
		options = Object.assign({
 | 
						|
			// defaults...
 | 
						|
			activate: false,
 | 
						|
			multiline: false,
 | 
						|
			accept_on_enter: true,
 | 
						|
			clear_on_edit: false,
 | 
						|
			reset_on_commit: true,
 | 
						|
			reset_on_abort: true,
 | 
						|
			blur_on_commit: false,
 | 
						|
			blur_on_abort: false,
 | 
						|
			keep_focus_on_parent: true,
 | 
						|
			clear_selection_on_commit: true,
 | 
						|
			clear_selection_on_abort: true,
 | 
						|
			propagate_unhandled_keys: true,
 | 
						|
			reset_on_done: true,
 | 
						|
			abort_keys: ['Esc'],
 | 
						|
		}, options || {})
 | 
						|
 | 
						|
		var original_text = this[0].innerText
 | 
						|
		var original_dom = document.createDocumentFragment()
 | 
						|
		this[0].childNodes
 | 
						|
			.forEach(function(node){ 
 | 
						|
				original_dom.appendChild(node.cloneNode(true)) })
 | 
						|
		var resetOriginal = function(){
 | 
						|
			//that.text(original_text)
 | 
						|
			that[0].innerHTML = ''
 | 
						|
			that[0].appendChild(original_dom.cloneNode(true))
 | 
						|
		}
 | 
						|
 | 
						|
		this.prop('contenteditable', true)
 | 
						|
 | 
						|
		options.activate 
 | 
						|
			&& options.clear_on_edit 
 | 
						|
			// XXX this for some reason breaks on click...
 | 
						|
			&& this.text('')
 | 
						|
 | 
						|
		// NOTE: this will also focus the element...
 | 
						|
		options.activate 
 | 
						|
			&& this.selectText()
 | 
						|
 | 
						|
		// do not setup handlers more than once...
 | 
						|
		if(!this.hasClass('editable-field')){
 | 
						|
			var events = {}
 | 
						|
			this
 | 
						|
				// make the element focusable and selectable...
 | 
						|
				.attr('tabindex', '0')
 | 
						|
				.addClass('editable-field')
 | 
						|
				.keydown(events.keydown = function(in_evt){ 
 | 
						|
					var evt = window.event || in_evt
 | 
						|
					if(!that.prop('contenteditable')){
 | 
						|
						return
 | 
						|
					}
 | 
						|
 | 
						|
					evt.stopPropagation() 
 | 
						|
 | 
						|
					var c = getCaretOffset(this)
 | 
						|
					var collapsed = selectionCollapsed(this) 
 | 
						|
					var n = keyboard.code2key(evt.keyCode)
 | 
						|
 | 
						|
					// abort...
 | 
						|
					if((options.abort_keys || []).indexOf(n) >= 0){
 | 
						|
						that.trigger('edit-abort', original_text)
 | 
						|
 | 
						|
					// done -- single line...
 | 
						|
					} else if(n == 'Enter' 
 | 
						|
							&& !options.multiline){
 | 
						|
						evt.preventDefault()
 | 
						|
						that.trigger('edit-commit', 
 | 
						|
							that.length == 1 ? 
 | 
						|
								that[0].innerText 
 | 
						|
								: that.toArray().map(function(e){ return e.innerText }))
 | 
						|
 | 
						|
					// done -- multi-line...
 | 
						|
					} else if(options.multiline 
 | 
						|
							&& n == 'Enter' 
 | 
						|
							&& (options.accept_on_enter ?
 | 
						|
								!(evt.ctrlKey || evt.shiftKey || evt.metaKey) 
 | 
						|
								: (evt.ctrlKey || evt.shiftKey || evt.metaKey)) ){
 | 
						|
						evt.preventDefault()
 | 
						|
						that.trigger('edit-commit', 
 | 
						|
							that.length == 1 ? 
 | 
						|
								that[0].innerText 
 | 
						|
								: that.toArray().map(function(e){ return e.innerText }))
 | 
						|
 | 
						|
					// multi-line keep keys...
 | 
						|
					} else if(options.multiline 
 | 
						|
							&& options.accept_on_enter ? 
 | 
						|
								(n == 'Enter' 
 | 
						|
								 	&& (evt.ctrlKey || evt.shiftKey || evt.metaKey))
 | 
						|
								: n == 'Enter'){
 | 
						|
						return
 | 
						|
 | 
						|
					// multi-line arrow keys -- keep key iff not at first/last position...
 | 
						|
					} else if(options.multiline
 | 
						|
							&& n == 'Up'
 | 
						|
							&& (c > 0 || !collapsed)){
 | 
						|
						return
 | 
						|
					} else if(options.multiline
 | 
						|
							&& n == 'Down'
 | 
						|
							&& (c < $(this).text().length || !collapsed)){
 | 
						|
						return
 | 
						|
					} else if(n == 'Up' || n == 'Down'){
 | 
						|
						evt.preventDefault()
 | 
						|
						that.trigger('edit-commit', 
 | 
						|
							that.length == 1 ? 
 | 
						|
								that[0].innerText 
 | 
						|
								: that.toArray().map(function(e){ return e.innerText }))
 | 
						|
 | 
						|
					// continue handling...
 | 
						|
					} else if(options.propagate_unhandled_keys){
 | 
						|
						// NOTE: jQuery can't reuse browser events, this 
 | 
						|
						// 		we need to pass a jq event/proxy here...
 | 
						|
						$(this).parent().trigger(in_evt || evt)
 | 
						|
					}
 | 
						|
				})
 | 
						|
				.blur(events.blur = function(){
 | 
						|
					window.getSelection().removeAllRanges()
 | 
						|
				})
 | 
						|
				.on('focus click', events['focus click'] = function(evt){
 | 
						|
					evt.stopPropagation()
 | 
						|
					options.clear_on_edit 
 | 
						|
						&& $(this)
 | 
						|
							.text('')
 | 
						|
							.selectText()
 | 
						|
				})
 | 
						|
				// user triggerable events...
 | 
						|
				.on('edit-abort', events['edit-abort'] = function(evt, text){
 | 
						|
					that.trigger('edit-aborting', text)
 | 
						|
 | 
						|
					options.clear_selection_on_abort
 | 
						|
						&& window.getSelection().removeAllRanges()
 | 
						|
 | 
						|
					// reset original value...
 | 
						|
					options.reset_on_abort
 | 
						|
						&& resetOriginal() 
 | 
						|
					options.blur_on_abort
 | 
						|
						&& this.blur() 
 | 
						|
 | 
						|
					// restore focus on parent...
 | 
						|
					options.keep_focus_on_parent
 | 
						|
						&& that.parents('[tabindex]').first().focus()
 | 
						|
 | 
						|
					options.reset_on_done
 | 
						|
						&& that.makeEditable(false)
 | 
						|
				})
 | 
						|
				.on('edit-commit', events['edit-commit'] = function(evt, text){
 | 
						|
					that.trigger('edit-committing', text)
 | 
						|
 | 
						|
					options.clear_selection_on_commit
 | 
						|
						&& window.getSelection().removeAllRanges()
 | 
						|
 | 
						|
					// reset original value...
 | 
						|
					options.reset_on_commit
 | 
						|
						&& resetOriginal() 
 | 
						|
					options.blur_on_commit
 | 
						|
						&& this.blur() 
 | 
						|
 | 
						|
					// restore focus on parent...
 | 
						|
					options.keep_focus_on_parent
 | 
						|
						&& that.parents('[tabindex]').first().focus()
 | 
						|
 | 
						|
					options.reset_on_done
 | 
						|
						&& that.makeEditable(false)
 | 
						|
				})
 | 
						|
 | 
						|
			this.data('editable-field-events', events)
 | 
						|
		}
 | 
						|
 | 
						|
		return this
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/**********************************************************************
 | 
						|
* vim:set ts=4 sw=4 :                               */ return module })
 |