您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Розширення додає поле для пошуку по сторінці на сервісі rizzoma.com
当前为
// Rizzoma Extended by Yura Babak // https://github.com/Inversion-des/Rizzoma-Extended // ==UserScript== // @name Rizzoma Extended // @description Розширення додає поле для пошуку по сторінці на сервісі rizzoma.com // @author Yura Babak // @namespace Rizzoma // @version 0.9.1 // @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 }) // detect big document tree.f_doc_is_big = Object.keys(block_by_id_H).length > 1000 } 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-thread') // 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.hl_el = node.find('b') node.hl_el[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-thread .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:300px;background-color:#F6F6F6;padding:10px;padding-right:60px; 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_search_cont__search_btn {display:none;position:absolute;top:1px;right:1px;background-color:#EFEBAB;padding:10px;cursor:pointer;font-size:12px;line-height:15px;border-radius: 0 5px 5px 0;border-left:1px solid #e6dbae;}\ .RExt_search_cont__search_btn:hover {background-color:#F5EE8B;}\ .RExt_text_hl {font-weight:normal;background-color:#FFFF00;outline:10px solid transparent;outline-offset:10px;}\ .RExt_text_hl._hl_first {border-top:2px solid transparent;}\ .RExt_text_hl._hl_last {border-bottom:2px solid transparent;}\ .RExt_text_hl._hl_focused {outline: 5px solid rgba(255, 224, 102, 0.6);outline-offset:0px;transition: all 0.3s ease;}\ .RExt_text_hl._hl_focused._hl_first {border-top:2px solid #F27316;}\ .RExt_text_hl._hl_focused._hl_last {border-bottom:2px solid #F27316;}\ .RExt_search_cont._showing_results input {background-color:#FFFFC7;}\ .RExt_search_cont__results {display:none;position:absolute;left:-56px;width:50px;text-align:right;top:7px;color:#FFF;}\ .RExt_search_cont__results div {display:inline;}\ .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() { var scroll_cont = $('.js-wave-blips') // search var search = {} search.cont = $('\ <div class="RExt_search_cont">\ <div class="RExt_search_cont__results">\ <div class="RExt_search_cont__results__cur_index"></div>\ <div class="RExt_search_cont__results__count"></div>\ </div>\ <input type="text" value="" placeholder="Пошук по сторінці" title="Hotkey: /" />\ <div class="RExt_search_cont__search_btn" title="Hotkey: Enter">Пошук</div>\ <div class="RExt_search_cont__x" title="Hotkey: Esc">×</div>\ </div>\ ').hide().insertBefore(header.find('.js-settings-container')).delay(1000).fadeIn('slow') // do search search.last_search_text = null search.do_search = function() { var val = search.input.clear_val() // (<!) ignore same text if (val == search.last_search_text) return; search.last_search_text = val search.search_btn.hide() tree.search_text({text:val}) search.cont.addClass('_showing_results') search.x.show() // results search.results.count.text(tree.nodes_with_hl.length) search.results.cur_index.text('') search.results.stop().fadeIn() // sort nodes by y position on the page tree.nodes_with_hl.sort(function(a, b) { a.c_top = a.c_top || a.offset().top b.c_top = b.c_top || b.offset().top return a.c_top - b.c_top }) // mark first and last if (tree.nodes_with_hl[0]) { tree.nodes_with_hl[0].hl_el.addClass('_hl_first') tree.nodes_with_hl[tree.nodes_with_hl.length-1].hl_el.addClass('_hl_last') } // go to the first result search.cur_result_index = 0 search.go_to_cur_result() } // go to search.cur_result_index = 0 search.last_focused_hl = $() search.go_to_cur_result = function() { if (!tree.nodes_with_hl.length) return; // index correction if (search.cur_result_index < 0) search.cur_result_index = 0 if (search.cur_result_index > tree.nodes_with_hl.length-1) search.cur_result_index = tree.nodes_with_hl.length-1 // show index in results (only not 1) search.results.cur_index.text( (search.cur_result_index || search.results.cur_index.text()) ? search.cur_result_index+1+' /' : '') var hl_node = tree.nodes_with_hl[search.cur_result_index] var pos_top = hl_node.offset().top var delta_y = pos_top - (scroll_cont.height()/2) + 50 var action = function() { search.last_focused_hl.removeClass('_hl_focused') hl_node.hl_el.addClass('_hl_focused') search.last_focused_hl = hl_node.hl_el } var f_at_the_edge = delta_y <0 && scroll_cont.scrollTop() == 0 || delta_y >0 && scroll_cont.scrollTop() == scroll_cont.getScrollTopMax() if (f_at_the_edge) { action() } else { scroll_cont.stop(true, true).animate({scrollTop: '+='+delta_y}, Math.abs(delta_y), action) } } //-- input search.input = search.cont.find('input') // on type var t_delayed_search = null search.input.on('input', function() { var val = search.input.val() search.last_search_text = false tree.clear_hl() search.x.hide() search.search_btn.toggle(!!val) // (<!) do not auto-search for big docs if (tree.f_doc_is_big) return; var val = search.input.clear_val() clearTimeout(t_delayed_search) if (val.length>3) { t_delayed_search = setTimeout(function() { search.do_search() }, 500) } }) search.input.clear_val = function() { return $.trim(this.val()) } // Search btn (Enter) search.search_btn = search.cont.find('.RExt_search_cont__search_btn') search.search_btn.on('mousedown', function() { search.do_search() }) // 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.cur_index = search.results.find('.RExt_search_cont__results__cur_index') search.results.count = search.results.find('.RExt_search_cont__results__count') // -hot keys (-hotkeys) $win.on('keydown.RExt', function(e) { switch (e.which) { case $.key.Enter: search.do_search() break case $.key.Esc: search.x.click() break case $.key.Slash: case $.key.Slash_cyr: if ($(doc.activeElement).is('input')) return true e.preventDefault() search.input.focus() break case $.key.Down: case $.key.Up: // only in search.input if ($(e.target).is(search.input[0])) { search.cur_result_index += ( e.which == $.key.Down ? 1 : -1 ) search.go_to_cur_result() } 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 (-edit, -clear) var cur_blip = $() // change cur_blip on click inside every blip $('.js-wave-content').on('mousedown', '.blip-container', function(e) { // *e will bubble up, but cur_blip will be changed only on target blip if (!e._f_blip_saved) cur_blip = $(this) e._f_blip_saved = true }) // returns all hl nodes inside some node var find_hl_nodes = function(in_node) { var res = $() in_node.children().each(function() { var node = $(this) if (node.is('.RExt_text_hl')) { res = res.add(node) } // *we should skip all the nested blips here, otherwise when you edit some root blip — all the highlighted search results in nested blips will be removed else if (node.is(':not(.blip-thread)')) { // recursively search in child node res = res.add( find_hl_nodes(node) ) } }) return res } // detect if blip in edit mode — clear all hl inside it setInterval(function() { // *for root-blip condition is different if (cur_blip.is('.edit-mode') || cur_blip.is('.root-blip') && cur_blip.find('> .js-editor-container > .js-editor').prop('contenteditable') == 'true') { find_hl_nodes(cur_blip).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 = { Enter: 13, 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; }; $.fn.getScrollTopMax = function() { var el = this[0] return el.scrollTopMax || el.scrollHeight - el.clientHeight } }(typeof unsafeWindow == 'undefined' ? window : unsafeWindow)