Rizzoma Extended

Розширення додає поле для пошуку по сторінці на сервісі rizzoma.com

目前为 2017-08-17 提交的版本。查看 最新版本

// Rizzoma Extended by Yura Babak
// https://github.com/Inversion-des/Rizzoma-Extended

// ==UserScript==
// @name				Rizzoma Extended
// @description		Розширення додає поле для пошуку по сторінці на сервісі rizzoma.com
// @namespace		Rizzoma
// @version        	0.8
// @include			https://rizzoma.com/*
// @run-at				document-body
// @supportURL	    https://github.com/Inversion-des/Rizzoma-Extended/issues
// ==/UserScript==

"use strict";
!function(win) {
	if (window != window.top) return
	var doc = win.document
	var $ = win.jQuery
	var $win = $(win)
	
	// data
	var data = $.extend(true, {}, win.getWaveWithBlipsResults)
	var block_by_id_H = {}
	var index_blocks = function() {
		// get first wave data
		for (var k in data) {
			var wave = data[k]
			break
		}
		// кожен blip — це є окремий незалежний блок, який може редагуватися
		// спочатку проходимося, робимо базовий індекс
		$.each(wave.data.blips, function(i, blip) {
			//. skip root container
			if (blip.snapshot.isContainer) return true
			var block = {
				id: blip.docId,
				children_ids: [],
				parents: []
			}
			block_by_id_H[block.id] = block
		})
		// тепер аналізуємо контент кожного блока
		$.each(wave.data.blips, function(i, blip) {
			//. skip root container
			if (blip.snapshot.isContainer) return true
			
			var block = block_by_id_H[blip.docId]
			
			var text_parts = []
			$.each(blip.snapshot.content, function(i, part) {
				if (part.params.__TYPE == "TEXT") {
					text_parts.push(part.t)
				}
				else if (part.params.__TYPE == "BLIP") {
					text_parts.push('(+)')
					var id = part.params.__ID
					block.children_ids.push(id)
					var child_block = block_by_id_H[id]
					child_block.thread_id = part.params.__THREAD_ID
					child_block.parents = block.parents.concat(block)
				}
			})
			block.text = text_parts.join("\n").toLocaleLowerCase()
			block_by_id_H[block.id] = block
		})
	}
	setTimeout(index_blocks, 500)
	
	/////// ------- tree ------- ///////////////////////////////////////////////////////////////////////////////
	var tree = {}
	tree.nodes_with_hl = []
	tree.blips_with_matched = []
	tree.all_unfolded = []
	
	// tree.fold_all({in:cont})
	tree.fold_all = function(o) {
		o = o || {}
		var cont = o.in || $('.root-blip')
		
		// if folding all, not in container — clear unfolded markers
		if (!o.in) {
			$.each(tree.all_unfolded, function(i, plus) {
				delete plus._f_unfolded
			})
			tree.all_unfolded = []
		}
		
		cont.find('span.blip-thread:not(.folded)').each(function() { 
			var plus = this.rzBlipThread
			// skip (+) els wich were unfolded during unfold_all_to_target
			if (!plus._f_unfolded) plus.fold()
		})
	}
	
	// tree.unfold_all_to_target({id:'0_b_9bl0_83je6'})
	tree.unfold_all_to_target = function(o) {
		var id = o.id
		var next_block, res={}
		var target_block = block_by_id_H[id]
		var cur_container = $('.root-blip').parent()
		// process all the parents + target_block
		$.each(target_block.parents, function(i, block) {
			next_block = target_block.parents[i+1] || target_block
			
			// find and unfold proper (+) in the current container
			cur_container.find('span.blip-thread').each(function() {
				var plus = this.rzBlipThread
				var blips_cont = $(this).closest('.blips-container')
				// skip if it is not a child
				if (!blips_cont.is(cur_container[0])) return true
				
				if (plus._threadId == next_block.thread_id) {
					plus.unfold()
					// mark unfolded
					plus._f_unfolded = true
					tree.all_unfolded.push(plus)
					
					// mark parent container
					var blip = $(this).closest('.blip-container')
					blip[0]._f_has_matched = true
					blip.removeClass('RExt_blip_shaded')
					tree.blips_with_matched.push(blip)
					
					// shade all sibling blips
					cur_container.children('.blip-container').each(function(i, blip) {
						if (!blip._f_has_matched) {
							blip = $(blip)
							blip.addClass('RExt_blip_shaded')
						}
					})
					
					//. change current container
					cur_container = $(plus._blipsContainer)
					tree.fold_all({in:cur_container})
					
					// break
					return false
				}
			})
		})
		
		return {last_container:cur_container}
	}
	
	// tree.search_text({text:'__'})
	tree.search_text = function(o) {
		var text = o.text.toLocaleLowerCase()
		var res
		var rx = new RegExp('('+RegExp.escape(text)+')', 'ig')
		tree.fold_all()
		tree.clear_hl()
		var results_count = 0
		
		// for each block
		$.each(block_by_id_H, function(id, block) {
			if (block.text.indexOf(text)>-1) {
				res = tree.unfold_all_to_target({id:block.id})
				
				// process all the blips in container
				res.last_container.children('.blip-container').each(function(i, blip) {
					blip = $(blip)
					// (<!) process only target blip
					if (blip[0].__rizzoma_data_key.params.__ID != block.id) {
						if (!blip[0]._f_has_matched) blip.addClass('RExt_blip_shaded')
						return true
					}
					
					blip[0]._f_has_matched = true
					blip.removeClass('RExt_blip_shaded')
					tree.blips_with_matched.push(blip)
					
					var children = blip.find('> .js-editor-container > .js-editor').children('div, li').children(':not(.blip-thread)')
					children.each(function(i, node) {
						if (node._fv_ori_text) return true
						node = $(node)
						var node_text = node.html()
						if (node_text.toLocaleLowerCase().indexOf(text)<0) return true
						
						node[0]._fv_ori_text = node_text
						var node_text_with_hl = node_text.replace(rx, '<b class="RExt_text_hl">$1</b>')
						node.html(node_text_with_hl)
						
						// this data for text nodes used in api
						node.find('b')[0].data = text
						
						tree.nodes_with_hl.push(node)
					})
				})
			}
		})
	}
	tree.clear_node_hl = function(node) {
		node.html(node[0]._fv_ori_text)
		delete node[0]._fv_ori_text
	}
	tree.clear_hl = function(o) {
		$.each(tree.nodes_with_hl, function(i, node) {
			tree.clear_node_hl(node)
		})
		tree.nodes_with_hl = []
		
		$.each(tree.blips_with_matched, function(i, blip) {
			delete blip[0]._f_has_matched
		})
		tree.blips_with_matched = []
		
		$('.root-blip .RExt_blip_shaded').removeClass('RExt_blip_shaded')
		
		$win.trigger('tree.clear_hl')
	}
	/////// ------- /tree ------- ///////////////////////////////////////////////////////////////////////////////

	// export
	win.RExt_tree = tree
	win.RExt_block_by_id_H = block_by_id_H

	// -styles
	$('<style>\
		.RExt_search_cont {display: inline-block; position: absolute; top: 5px; right: 50px;}\
		.RExt_search_cont input {width:330px;background-color:#F6F6F6;padding:10px;padding-right:32px; border-radius: 5px; border: 1px solid #859099;}\
			.RExt_search_cont input:focus {background-color:#FFF;}\
				.RExt_search_cont input:focus::placeholder {opacity:0.2}\
					.RExt_search_cont input:focus::-moz-placeholder {opacity:0.2}\
					.RExt_search_cont input:focus::-webkit-input-placeholder {opacity:0.2}\
					.RExt_search_cont input:focus:-ms-input-placeholder {opacity:0.2}\
		.RExt_search_cont__x {display:none;position:absolute;top:0;right:0;padding:10px;cursor:pointer;font-size: 24px;line-height: 19px;}\
			.RExt_search_cont__x:hover {color:#BD2929;}\
		.RExt_text_hl {font-weight:normal;background-color:#FFFF00;}\
		.RExt_search_cont._showing_results input {background-color:#FFFFC7;}\
		.RExt_search_cont__results {display:none;position:absolute;left:-36px;width:30px;text-align:right;top:7px;color:#FFF;}\
		.RExt_blip_shaded {padding:0px;height:15px;opacity:0.4;min-height:0px;overflow:hidden;cursor:pointer;}\
		.RExt_blip_shaded * {cursor:pointer;}\
	</style>').appendTo('head')

	
	// DOM ready
	$(function() {
		
		// wait for header
		var header
		var check_dom = function() {
			header = $('.js-wave-header')
			header[0] ? $win.trigger('RExt_head_ready') : setTimeout(check_dom, 100)
		}
		check_dom()
		
		// on header ready
		$win.on('RExt_head_ready', function() {

			// search
			var search = {}
			search.cont = $('\
				<div class="RExt_search_cont">\
					<div class="RExt_search_cont__results">\
						<div class="RExt_search_cont__results__count"></div>\
					</div>\
					<input type="text" value="" placeholder="Пошук по сторінці" title="Hotkey: /" />\
					<div class="RExt_search_cont__x" title="Hotkey: Esc">×</div>\
				</div>\
			').hide().insertBefore(header.find('.js-settings-container')).delay(1000).fadeIn('slow')
			
			// input
			search.input = search.cont.find('input')
			var t_delayed_search = null
			search.input.on('input', function() {
				var val = search.input.val()
				search.x.toggle(!!val)
				clearTimeout(t_delayed_search)
				val = $.trim(val)
				if (val.length>1) {
					t_delayed_search = setTimeout(function() {
						// do search
						tree.search_text({text:val})
						search.cont.addClass('_showing_results')
						search.results.count.text(tree.nodes_with_hl.length)
						search.results.stop().fadeIn()
					}, 500)
				}
				else {
					tree.clear_hl()
				}
			})
			
			// X — clear search
			search.x = search.cont.find('.RExt_search_cont__x')
			search.x.on('click', function() {
				tree.clear_hl()
				search.input.val('').focus().triggerHandler('input')
			})
			
			// results
			search.results = search.cont.find('.RExt_search_cont__results')
			search.results.count = search.results.find('.RExt_search_cont__results__count')
			
			// hot keys
			$win.on('keydown.RExt', function(e) {
				switch (e.which) {
					case $.key.Esc: 
						search.x.click()
						break
					case $.key.Slash:
					case $.key.Slash_cyr:
						e.preventDefault()
						search.input.focus()
						break
				}
			})
			
			// for shared blips
			// on click — unshade
			$('.js-wave-content').on('mousedown', '.RExt_blip_shaded', function(e) {
				var blip = $(this)
				blip.removeClass('RExt_blip_shaded')
				return false
			})
			// on edit blip — clear any hl in nodes
			var cur_blip = $()
			$('.js-wave-content').on('mousedown', '.blip-container', function(e) {
				if (!e._f_blip_saved) cur_blip = $(this)
				e._f_blip_saved = true
			})
			setInterval(function() {
				if (cur_blip.is('.edit-mode')) {
					cur_blip.find('.RExt_text_hl').each(function() {
						var node = $(this).parent()
						tree.clear_node_hl(node)
						tree.nodes_with_hl.pull(node)
					})
				}
			}, 100)
			
			// other events
			$win.on('tree.clear_hl', function() {
				search.cont.removeClass('_showing_results')
				search.results.fadeOut()
			})
			
		}) // on header ready
		
	})	// DOM ready
	
	
	// helpers
	RegExp.escape = function(str) {
		return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1')
	}

	$.key = {
		Esc: 27,
		Slash: 191,
		Slash_cyr: 190,
		Slash_num: 111,
		Down: 40,
		Up: 38,
		Left: 37,
		Right: 39,
		Tab: 9,
		Space: 32,
		PageUp: 33,
		PageDown: 34,
		Home: 36,
		End: 35,
		J: 74,
		K: 75,
		S: 83,
		Shift: 16,
		Ctrl: 17,
		Del: 46,
	}
	
	Array.prototype.pull = function() {
		var arg, args, index, j, len, output;
		args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
		output = [];
		for (j = 0, len = args.length; j < len; j++) {
			arg = args[j];
			index = this.indexOf(arg);
			if (index !== -1) {
				output.push(this.splice(index, 1)[0]);
			}
		}
		if (args.length === 1) {
			output = output[0];
		}
		return output;
	};
	

}(typeof unsafeWindow == 'undefined' ? window : unsafeWindow)