「選択置換機能」の作成
ハイライト表示をした文字列を「←↑↓→」の方向キーで表示する「巡回」のコードは既に実装しています。 ヒットした「対象要素」を、順に表示範囲に呼び寄せるコードですが、このコードに手を加えて新たに「選択置換機能」を作りました。
以下がキーを受け取る部分で、太字の部分が追加したコードです。
これは、「スペース」キーを押して「対象要素」の「設定/解除」をする仕様です。
対象要素のテキスト(ノード)部は、最初は「検索文字」の状態です。「スペース」を押した時に、テキスト部が「検索文字」なら「置換文字」に入れ替えます。 もしテキスト部が「置換文字」なら、既に置換をした後なので「検索文字」に戻します。
以上の様に「置換えの設定」「置換えの解除」の編集をして、最終的にハイライト表示を取り除けば、「選択置換」が完了します。 また、この「選択置換」のプロセス時は「buffer」を保持しているので、「UNDO」で全ての操作をやりなおせます。
これまでの「一括置換」と「選択置換」は対になる処理なので、「OK」「UNDO」が「置換」の最終的な決定というデザインは、判り易いと思います。
「選択置換」のデザイン
通常は「一括置換」を必要とする場合が多いと考え、「置換チェック画面」に入ったデフォルトは、従来の通り「一括置換」にしています。 新たに「一括置換」「選択置換」の選択ボタンを配置し、これで「選択置換」を起動する様にしました。
上図は「一括置換」の状態で、仮に「置換」をした文書全体をチェック可能です。
ここで「選択置換」を押すと、置換前のハイライト表示に戻り、対象要素の「1番目」の位置へスクロールします。「選択置換」は「巡回」と同様の状態で、「スペース」キーで対象要素のひとつずつに「設定/解除」の選択が可能です。 全て「一括置換」してしまうのが不安な場合、この機能は有効でしょう。
「スペース」キーが置換の「設定 / 解除」となるのはこのツールの特例なので、「操作キーの説明」を表示しています。 これは単なる表示です。
現在の問題点
これは未解決ですが、「選択置換」を起動した最初の回は、正常に動作して「OK」「UNDO」が出来ます。 しかし、初回の「選択置換」以降は、全く「置換」操作が働かなくなります。 これは「選択置換」の部分だけで、「一括置換」は正常です。
スクリプトエラーは全くなく、逆に原因を探すのに苦労します。 新たに追加したコードが不完全なのは明らかで、今後に改善します。 でも、ツール完成は間近です。
「S-R in Editor」 ver. 1.4
以下の「S-R in Editor」は、「検索語入力」~「TEXTのヒット文字列の巡回チェック」~「HTML一括置換」「TEXT一括置換」と「各置換チェック」「UNDO」が可能な、制作過程のツールです。
Chrome版 / Firefox版の「Tampermonkey」の新規作成編集枠に、以下のスクリプトコードをコピー&ペーストする事で、このテストコードを試すことが出来ます。
〔コピー方法〕 軽量シンプルなツール「PreBox Button 」を使うと
コード枠内を「Ctrl+左Click」➔「Copy code 」を「左Click」
の操作で、掲載コードのコピーが可能になります。
〔 S-R in Editor 〕 ver. 1.4
// ==UserScript==
// @name S-R in Editor
// @namespace http://tampermonkey.net/
// @version 1.4
// @description 通常編集枠で実行できる 検索 / 置換 ツール
// @author Ameba Blog User
// @match https://blog.ameba.jp/ucs/entry/srventry*
// @grant none
// ==/UserScript==
window.addEventListener('load', function(){
let p_flag; // Process
let t_flag; // TEXT処理
let count_t;
let count_h;
let buffer;
let native_line;
let editor_iframe;
let iframe_doc;
let iframe_html;
let iframe_body;
let js_cover;
let search_box;
let search_word;
let search_word_es;
let replace_box;
let replace_word;
let result_box;
let s_1;
let s_2;
let s_3;
let s_4;
let s_5;
let cke_1_contents=document.querySelector('#cke_1_contents'); // 監視 target
let monitor=new MutationObserver(catch_key);
monitor.observe(cke_1_contents, {childList: true}); // ショートカット待受け開始
catch_key();
function catch_key(){
search_box=document.querySelector('#search_box');
editor_iframe=document.querySelector('.cke_wysiwyg_frame');
if(editor_iframe){ //「通常表示」の場合
add_m_style(); // mタグ用 styleをiframeに再設定
search_box=document.querySelector('#search_box');
if(search_box){
search_box.disabled=false; }
document.addEventListener("keydown", check_key); // documentは先に指定
iframe_doc=editor_iframe.contentWindow.document;
iframe_doc.addEventListener("keydown", check_key); // iframeは後に指定
function check_key(event){
if(event.ctrlKey==true){
if(event.keyCode==123){
event.preventDefault();
if(p_flag!=3){ // 置換チェック時でなければ「Ctrl+F12」でON/OFF
search_replace();
editor_iframe=document.querySelector('.cke_wysiwyg_frame');
if(!editor_iframe){
if_html(); }}}}
if(event.keyCode==9 || event.keyCode==16 || event.keyCode==17 ||
event.keyCode==18 ||event.keyCode==19 || event.keyCode==27){
if(p_flag==3){ // 置換チェック時に「Tab/Shift/Ctrl/Alt/Pause/Esc」を無効化
event.preventDefault();
out_p_flag3(); }
else if(event.keyCode==9 && p_flag==0){ // 編集枠から「Tab」で検索に戻る
event.preventDefault();
search_box.focus(); }}}}
else{ //「HTML表示」の場合
if_html(); }
function out_p_flag3(){ // 置換チェツクを解除する
js_cover.style.display='none';
cke_1_contents.style.zIndex='0';
if(iframe_body){
iframe_body.contentEditable='true'; } // 編集可能にする
iframe_body.innerHTML=buffer; // 置換処理をUNDO ⏹
search_box.disabled=false;
replace_box.disabled=false;
s_2.style.display='none';
s_3.style.display='none';
s_4.style.display='none';
s_5.style.display='none';
replace_box.focus();
if(t_flag==1 || t_flag==2){ //「buffer」を戻したので再度ハイライト表示
t_flag=1;
let rep_word='<m>'+ search_word +'</m>';
iframe_body.innerHTML=
iframe_body.innerHTML.replace(new RegExp(search_word_es, 'g'), rep_word); } // 🔳 RegExp
p_flag=2; } // 2=置換入力
function if_html(){ //「HTML表示」を開いた場合のクローズ処理
if(search_box){
search_box.disabled=true; }
result_box.textContent=' ';
s_1.style.display='none';
replace_box.style.display='none';
replace_box.value='';
s_2.style.display='none';
s_3.style.display='none';
s_4.style.display='none';
s_5.style.display='none';
t_flag=0;
p_flag=0; }
} // catch_key
function search_replace(){
let i_body=document.querySelector('body.l-body');
editor_iframe=document.querySelector('.cke_wysiwyg_frame');
if(editor_iframe){ //「通常表示」の場合
iframe_doc=editor_iframe.contentWindow.document;
iframe_html=iframe_doc.querySelector('html');
iframe_body=iframe_doc.querySelector('.cke_editable'); }
let css=
'#s_container {position: fixed; top: 12px; left: calc(50% - 490px); '+
'background: #fff; border: 1px solid #aaa; border-radius: 4px; '+
'padding: 6px 15px; min-width: 948px; z-index: 11;}'+
'#search_box {width: 215px;} #replace_box {width: 215px; display: none;}'+
'::placeholder {font-size: 15px; color: #bbb;}'+
'#s_container input:disabled {color: #000; background: #eef1f3;}'+
'#s_container input {font-size: 16px; padding: 2px 6px 0; -moz-appearance: none;}'+
'#result {display: inline-block; min-width: 50px; padding: 4px 6px 2px; '+
'margin-left: 5px; border: 1px solid #aaa; font-size: 16px;}'+
'.s_sw {display: inline-block; vertical-align: -9px; font-size: 15px; '+
'padding: 5px 8px 2px; border: 1px solid #aaa; overflow: hidden;}'+
'.s_1 {margin: 0 15px; min-width: 4em; display: none;}'+
'.s_2, .s_3, .s_4 {margin-left: 5px; color:#fff; background: #1e88e5; '+
'cursor: pointer; display: none;}'+
'.s_4 {margin-left: 15px; box-shadow: inset -45px 0 0 0 rgba(255, 255, 255, .7);}'+
'.s_5 {margin-left: 5px; position: fixed; top: 70px; right: calc(50% - 490px); '+
'padding: 7px 15px 4px; border-radius: 4px; background: #e3f2fd; display: none;}'+
'.js_cover {position: fixed; top: 0; width: 100%; height: 100%; '+
'background: rgba(0, 0, 0, .6); z-index: 10; display: none;}';
let style_tag=document.createElement("style"); // css設定styleタグ
style_tag.type="text/css";
style_tag.setAttribute("class", "ep");
style_tag.appendChild(iframe_doc.createTextNode(css));
if(i_body.querySelector('.ep')){
i_body.querySelector('.ep').remove(); }
i_body.appendChild(style_tag);
js_cover=document.createElement("div"); // クリック操作のブロックカバー
js_cover.setAttribute("class", "js_cover");
if(i_body.querySelector('.js_cover')){
i_body.querySelector('.js_cover').remove(); }
document.querySelector('#js-container').appendChild(js_cover);
let s_container=document.querySelector('#s_container');
if(!s_container){ //#s_containerが無い場合 生成して開始
let insert_node_d=document.createElement('div');
insert_node_d.setAttribute('id', 's_container');
i_body.appendChild(insert_node_d);
insert_node_d.innerHTML=
'<input id="search_box" placeholder=" 検索文字" autocomplete="off">'+
'<span id="result"> </span>'+
'<span class="s_sw s_1"> </span>'+
'<input id="replace_box" placeholder=" 置換文字" autocomplete="off">'+
'<span class="s_sw s_2">OK</span>'+
'<span class="s_sw s_3">UNDO</span>'+
'<span class="s_sw s_4">一括 選択</span>'+
'<span class="s_sw s_5">⇦⇧⇩⇨:移動<br>'+
'Space:設定 / 解除<br>OK:置換を確定する<br>UNDO:全て元に戻す</span>';
search_box=document.querySelector('#search_box');
result_box=document.querySelector('#result');
replace_box=document.querySelector('#replace_box');
s_1=document.querySelector('.s_1');
s_2=document.querySelector('.s_2');
s_3=document.querySelector('.s_3');
s_4=document.querySelector('.s_4');
s_5=document.querySelector('.s_5');
p_flag=0; // 0=検索文字 未確定
search_box.focus();
search_box.onkeydown=function(event){ // 🔽 検索ツール操作の開始点
if(event.keyCode==13){
if(p_flag==0){
event.preventDefault();
search_word=search_box.value; // 🟥 検索文字取得
get_search();
rbox_disp(); }
else if(p_flag==1){
event.preventDefault();
if(search_box.value==search_word){
replace_box.style.display='inline-block';
replace_box.focus();
p_flag=2; } // 巡回ループを抜けて 置換入力へ
else{
iframe_html.scrollTop=native_line; // 検索開始位置に戻る 🅿
iframe_body.innerHTML=buffer; // highlight を抜ける時はリセット ⏹
search_word=search_box.value; // 🟥 検索文字取得 変更
result_box.textContent='⏎';
s_1.style.display='none';
t_flag=0;
p_flag=0; // 巡回ループを抜けて 検索文字 未確定へ
search_box.dispatchEvent( new KeyboardEvent( "keydown", {keyCode: 13})); }}}
if(event.keyCode==9){ //「Tab」で置換入力へ
if(p_flag==0){
event.preventDefault(); } //「Tab」で入力枠外に出るのを抑止
else if(p_flag==1){
event.preventDefault();
replace_box.style.display='inline-block';
replace_box.focus();
p_flag=2; }} // 巡回ループを抜ける
if(event.keyCode==27 ){ //「Esc」
if(p_flag==1 && t_flag==1){
event.preventDefault();
result_box.textContent='T:'+count_t+'│-';
iframe_html.scrollTop=native_line; // 検索開始位置に戻る 🅿
iframe_body.innerHTML=buffer; // highlight を抜ける時はリセット ⏹
p_flag=0; }}}
search_box.onchange=function(){ //「Enter」を押さず移動した場合は検索語を再表示
if(search_box.value!==search_word){
search_box.style.outline='2px solid #2196f3';
search_box.style.outlineOffset='-3px';
setTimeout(()=>{
search_box.style.outline='none';
search_box.value=search_word; }, 500); }}
function get_search(){
search_word_es=escapeRegExp(search_word); // 🔳 RegExp
editor_iframe=document.querySelector('.cke_wysiwyg_frame'); // ここで取得
if(editor_iframe){ //「通常表示」が実行条件
iframe_doc=editor_iframe.contentWindow.document;
iframe_body=iframe_doc.querySelector('.cke_editable');
native_line=iframe_html.scrollTop; // 通常表示のスクロール位置を記録 🅿
buffer=iframe_body.innerHTML; // ハイライト表示のためソースコードを保存 🟦
let childs=searchNodes(iframe_body);
count_t=0; // テキストノードのヒット数
let result_t
for(let k=0; k<childs.length; k++){
if(childs[k].nodeType==3){
result_t=childs[k].textContent.match(new RegExp(search_word_es, 'g')); // 🔳 RegExp
if(result_t){
count_t+=result_t.length; }}}
count_h=0; // HTMLソース全体のヒット数
let result_h=iframe_body.innerHTML.match(new RegExp(search_word_es, 'g')); // 🔳 RegExp
if(result_h){
count_h=result_h.length; }}}
function searchNodes(root){ // 全子孫ノードリストを作成
var list=[];
var search=function (node){
while (node !=null){
list.push(node);
search(node.firstChild);
node=node.nextSibling; }}
search(root.firstChild);
return list; }
function rbox_disp(){
t_flag=0;
search_box.disabled=false;
replace_box.disabled=false;
s_1.style.display='inline-block';
s_2.style.display='none';
s_3.style.display='none';
replace_box.style.display='none';
replace_box.value='';
if(count_t!=0 && count_h-count_t==0){
s_1.textContent='TEXT処理';
s_1.style.color='#000';
t_flag=1; // TEXT処理
p_flag=1; // 1=検索文字確定 処理開始
highlight();
all_replace(); }
if(count_t!=0 && count_h-count_t!=0){
result_box.textContent='T:'+count_t+' H:'+(count_h-count_t);
s_1.textContent='処理不能';
s_1.style.color='red';
p_flag=0; } // 0=検索文字未確定 検索前
if(count_t==0 && count_h-count_t!=0){
result_box.textContent='H:'+(count_h-count_t);
s_1.textContent='HTML処理';
s_1.style.color='#000';
p_flag=1; // 1=検索文字確定 処理開始
highlight();
all_replace(); }
if(count_t==0 && count_h-count_t==0){
result_box.textContent='T:'+count_t+' H:'+(count_h-count_t);
s_1.textContent=' - - - ';
s_1.style.color='#000';
p_flag=0; } // 0=検索文字未確定 検索前
search_box.onblur=function(){ //「検索枠」が focusを無くしたらリセット
setTimeout( ()=>{
if(replace_box.style.display=='none'){ //「置換」へ移動は除外
stop_out(); }}, 10); }
replace_box.onblur=function(){ //「置換枠」が focusを無くしたらリセット
setTimeout( ()=>{
if(p_flag!=3){ //「置換確認画面」へ移行は除外
stop_out(); }}, 10); }
function stop_out(){
if(p_flag==1 || p_flag==2){ // 1=検索文字入力枠 2=置換文字入力枠
if(t_flag==1){
result_box.textContent='T:'+count_t+'│-'; }
else{
result_box.textContent='H:'+(count_h-count_t); }
s_1.style.display='none';
replace_box.style.display='none';
replace_box.value='';
native_line=iframe_html.scrollTop; // クリック位置を復帰位置に指定🅿
iframe_body.innerHTML=buffer; // highlight を抜ける時はリセット ⏹
p_flag=0; }}} // rbox_disp()
function highlight(){
if(t_flag==1){
let rep_word='<m>'+ search_word +'</m>';
iframe_body.innerHTML=
iframe_body.innerHTML.replace(new RegExp(search_word_es, 'g'), rep_word); // 🔳 RegExp
let mark=iframe_body.querySelectorAll('m');
let k;
for(k=0; k<mark.length; k++){ // 現在のスクロール位置側近のmark[k]を取得
let offsetY=mark[k].getBoundingClientRect().top;
if(offsetY>0){
break; }}
if(k==mark.length){ // 現在のスクロール位置より手前にしかmark[k]がない場合
k=mark.length -1; }
view(k);
result_box.textContent='T:'+count_t+'│'+(k+1);
while(k>=0 && k<mark.length && p_flag==1){ // p_flag=1=巡回
k=next(k); }
function next(k){
search_box.addEventListener("keydown", check_key);
function check_key(event){
if(event.keyCode==37 || event.keyCode==38){ //「←」「↑」
event.preventDefault();
if(p_flag==1 && t_flag==1){
if(k>0){
k-=1; }
else if(k==0){
k=0; }
result_box.textContent='T:'+count_t+'│'+(k+1);
view(k); }}
if(event.keyCode==39 || event.keyCode==40){ //「→」「↓」
event.preventDefault();
if(p_flag==1 && t_flag==1){
if(k<mark.length-1){
k+=1; }
else if(k==mark.length-1){
k=mark.length-1; }
result_box.textContent='T:'+count_t+'│'+(k+1);
view(k); }}}}
function view(k){
mark[k].scrollIntoView();
iframe_html.scrollBy(0, -4);
i_body.scrollIntoView(); }}} // highlight()
function all_replace(){ // 置換処理
replace_box.focus();
all_roop();
function all_roop(){
replace_box.onkeydown=function(event){ // 🔽 置換操作の開始点
if(event.keyCode==13){
event.preventDefault();
replace_word=replace_box.value; // 🟥 置換文字取得
iframe_body.innerHTML=
iframe_body.innerHTML.replace(new RegExp(search_word_es, 'g'), replace_word); // 🔳 RegExp
js_cover.style.display='block';
cke_1_contents.style.zIndex='11';
iframe_body.contentEditable='false'; // 編集不可にする
search_box.disabled=true;
replace_box.disabled=true;
s_2.style.display='inline-block';
s_3.style.display='inline-block';
if(t_flag==1){
s_4.style.display='inline-block';
s_4.style.boxShadow='inset -45px 0 0 0 rgba(255, 255, 255, .7)'; }
p_flag=3; } // 3=置換処理
if(event.keyCode==9 || event.keyCode==27){ //「Tab」「Esc」で処理前に戻る
event.preventDefault();
result_box.textContent=' ';
s_1.style.display='none';
replace_box.style.display='none';
replace_box.value='';
if(t_flag==1){
iframe_html.scrollTop=native_line; // 検索開始位置に戻る 🅿
iframe_body.innerHTML=buffer; } // 置換処理をUNDO ⏹
search_box.focus();
p_flag=0; }} // 0=検索文字 未確定
s_2.onclick=function(){ //「OK」ボタンで一括置換確定
cover_remove();
delete_m(); // mタグを削除
result_box.textContent=' '; // 検索結果は変更される
s_1.style.display='none';
replace_box.style.display='none';
replace_box.value='';
search_box.focus();
t_flag=0;
p_flag=0; } // 0=検索文字 未確定
s_3.onclick=function(){ //「UNDO」ボタン
cover_remove();
iframe_body.innerHTML=buffer; // 置換処理をUNDO ⏹
replace_box.focus();
if(t_flag==1 || t_flag==2){
t_flag=1;
let rep_word='<m>'+ search_word +'</m>';
iframe_body.innerHTML=
iframe_body.innerHTML.replace(new RegExp(search_word_es, 'g'), rep_word); } // 🔳 RegExp
p_flag=1; } // 1=検索文字確定 処理開始
s_4.onclick=function(){ //「一括・選択」ボタン
if(t_flag==1){
t_flag=2; //「選択置換」を指定
s_4.style.boxShadow='inset 45px 0 0 0 rgba(255, 255, 255, .7)';
s_5.style.display='inline-block';
select_replace(); }
else if(t_flag==2){
t_flag=1; //「一括置換」を指定
s_4.style.boxShadow='inset -45px 0 0 0 rgba(255, 255, 255, .7)';
s_5.style.display='none'; }}
function cover_remove(){
js_cover.style.display='none';
cke_1_contents.style.zIndex='0';
iframe_body.contentEditable='true'; // 編集可能にする
search_box.disabled=false;
replace_box.disabled=false;
s_2.style.display='none';
s_3.style.display='none';
s_4.style.display='none';
s_5.style.display='none'; }} // all_roop()
} // all_replace()
function select_replace(){
if(t_flag==2){
iframe_body.innerHTML=buffer; // 置換処理を一旦デフォルトに戻す ⏹
let rep_word='<m>'+ search_word +'</m>';
iframe_body.innerHTML=
iframe_body.innerHTML.replace(new RegExp(search_word_es, 'g'), rep_word); // 🔳 RegExp
let mark=iframe_body.querySelectorAll('m');
let k=0;
view(k);
result_box.textContent='T:'+count_t+'│'+(k+1);
while(k>=0 && k<mark.length && p_flag==3){ // p_flag=3=選択置換
k=next_replace(k); }
function next_replace(k){
document.addEventListener("keydown", check_key);
function check_key(event){
if(event.keyCode==37 || event.keyCode==38){ //「←」「↑」
event.preventDefault();
if(p_flag==3 && t_flag==2){
if(k>0){
k-=1; }
else if(k==0){
k=0; }
result_box.textContent='T:'+count_t+'│'+(k+1);
view(k); }}
if(event.keyCode==39 || event.keyCode==40){ //「→」「↓」
event.preventDefault();
if(p_flag==3 && t_flag==2){
if(k<mark.length-1){
k+=1; }
else if(k==mark.length-1){
k=mark.length-1; }
result_box.textContent='T:'+count_t+'│'+(k+1);
view(k); }}
if(event.keyCode==32){ //「Space」で個別に置換の設定/解除
event.stopImmediatePropagation();
if(p_flag==3 && t_flag==2){
if(mark[k].textContent==search_word){
mark[k].textContent=replace_word; }
else if(mark[k].textContent==replace_word){
mark[k].textContent=search_word; }}}}}
function view(k){
mark[k].scrollIntoView();
iframe_html.scrollBy(0, -4);
i_body.scrollIntoView(); }}} // select_replace()
} // #s_container が無い場合「Ctrl+F12」で開始
else{ // #s_container がある場合は「Ctrl+F12」で終了 「mタグを削除」
delete_m();
document.querySelector('#s_container').remove(); }
} // search_replace()
function add_m_style(){
editor_iframe=document.querySelector('.cke_wysiwyg_frame');
if(editor_iframe){ //「通常表示」の場合
iframe_doc=editor_iframe.contentWindow.document;
iframe_html=iframe_doc.querySelector('html');
let css_iframe='.cke_editable m {background: #ffcc00;}'; // mタグの背景色指定
let style_tag_iframe=iframe_doc.createElement("style");
style_tag_iframe.type="text/css";
style_tag_iframe.setAttribute("class", "ep");
style_tag_iframe.appendChild(document.createTextNode(css_iframe));
if(iframe_html.querySelector('.ep')){
iframe_html.querySelector('.ep').remove(); }
iframe_html.appendChild(style_tag_iframe); }}
function delete_m(){
editor_iframe=document.querySelector('.cke_wysiwyg_frame');
if(editor_iframe){ //「通常表示」の場合
iframe_doc=editor_iframe.contentWindow.document;
iframe_body=iframe_doc.querySelector('.cke_editable');
if(iframe_body){
let mark=iframe_body.querySelectorAll('m');
if(mark.length!=0){
iframe_body.innerHTML=
iframe_body.innerHTML.replace(new RegExp('<m>', 'g'), '' ); }}}} // 🔳 RegExp
function escapeRegExp(string){
let reRegExp=/[\\^$.*+?()[\]{}|]/g;
let reHasRegExp=new RegExp(reRegExp.source);
return (string && reHasRegExp.test(string))
? string.replace(reRegExp, '\\$&')
: string; }
});
「S-R in Editor ⭐」最新版について
旧いバージョンの JavaScriptツールは、アメーバのページ構成の変更で動作しない場合があり、導入する場合は最新バージョンをお勧めします。
●「S-R in Editor ⭐」の最新バージョンへのリンクは、以下のページのリンクリストから探せます。


