「削除リスト」の編集機能の見直し

「削除リスト」の編集機能のプログラムで、実はひとつの問題に出会いました。 これは、余り多くの情報がありませんが、JavaScript のファイル読込み機能は、「1度読込んだファイル情報(ファイル名など)が記憶され、次回に全く同じファイルを読込む事が出来ない」という仕様です。

 

「一旦、別ファイルを読込む」「読込み操作を途中でキャンセルする」といった操作で、前回の読込み情報はリセットされるので、再び同じファイルを読める様になり、実際は余り問題にはなりません。

 

以下のページに、この問題に関しての詳しい対策が書かれていて、それ以外の方法を見つける事は出来ませんでした。 少し手間な対策コードです。

 

   JavaScriptで同じローカルファイルをもう一度読み込む 

 

しかし、これは「削除リスト」のバックアップファイルの管理をテストしていると、とても気になります。 読込んだファイル名は に表示され、「差分追加OFF」ならに、その内容が選択ごとに表示されます。

 

削除リスト編集機能のファイル選択画面

 

通常はこれが正常に機能し、たまたま同じファイルを2度指定しても、「読込みが行われない」仕様で問題がありません。 しかし、の内容を編集して、その編集をやりなおしたい場合に、同じファイルを読込む操作になり、それが出来ないと気付きます。 気付いたのは未だ良いのですが、ユーザーが「読込んだ」と思う表示で、実は何もしていないのは間違いの元です。

 

最初の対策は苦労して問題を増やした

元々、時間のかかるリロードを減らす方針でしたが、この再読込みが出来ない対策では、の内容を編集したらページをリロードさせ、ファイルの読込みプログラムをリセットするしか考えが及びませんでした。

 

しかし、何かの行を削除したら普通の「ついたペタ」のリスト表示に戻ると判り難いので、上と同じ「削除リスト編集」の状態に戻る様にしたのが、前回のコードです。

 

スクリプトはリロードすると「全部忘れてしまい」、「ついたペタ」の画面に戻って来ます。「削除リスト編集」に戻すには、その状態を示すフラグをローカルストレージに記録してからリロードさせます。 リロード後に再びスクリプトが起動したら、その最初でストレージのフラグを読み、フラグがONなら「削除リスト編集」の状態を構成する様にしています。 ウインドウズのセットアップ等でよくある、「再起動後に~します」というのと、同じ方法ですね。

 

しかし、これは一瞬「ついたペタ」のページを本来の状態で開いてしまうので、「削除リスト」の編集途中であっても、ペタの削除作業が行われてしまいます。

 

削除リスト編集モードと実行

 

このインターフェイスは余り好ましくありません。「削除リスト編集」は、リスト解除だけでなく、他のバックアップファイルからのリスト追加も有り得るので、意図せずに削除動作になるのは避けたいのです。

 

この「削除リスト編集」の仕様を改善して、「×閉じる」で「ファイルパレット」を閉じた時に、初めて「削除」が実行される様に改めました。 通常の「ついたペタ」のリスト表示で削除が実行され、「削除リスト」のリスト内容表示では削除が実行されないという、判り易い仕様です。

 

改善した対策コード

リロードをせずに、ファイル読込みプログラムをリセットする方法は、上記のリンクの方法も考えました。「this.value=null」のコードは有効で、読込みコードの最後に付け加えると同ファイルの再読込みが可能になります。 しかし、同時に「読込んだファイル名」が常時リセットされて「選択されていません」になります。

 

ファイル選択画面 peta_mute (2).json

 

ファイル名と内容を比較できないと、余計に使い難くなってしまいます。

 

そこで、「削除リスト」を編集(リストからの解除)を行った場合は、「ファイルパレット」を差換える方法を考えました。「ファイルパレット」自体は、パレット上の全てのボタン表示とそれで起動されるコード、その下の「削除リスト内容表示」を含みますが、「mode_backup()」という関数に纏まっています。 これを一旦削除して再び実行する事で、ファイル読込みプログラムのリセットを実現しました。

 

「バックアップファイル」のファイル読込み、別ファイルへの変更などでは、常に読込んだファイル名が表示され、下の内容表示のリスト行を削除するとファイル名は非表示になります。 リロードなしで、より理想的なファイル編集環境になりました。

 

 

「削除」の実行コードの改善

実際に削除実行は限られた回数なので、それ以外は削除の代わりに非表示指定するテストコードで、かなりスマートに「削除」できる様になって来ました。 削除ボタンを押し、小さな確認表示を更にクリックしますが、確認表示を見えない様にしながら、クリックするコードにしています。

 

popup[1].style.zIndex='-1'; 

 

上の部分で確認表示を「z-index: -1」で非表示にしていますが、「opacity:0」等も使えますが「display: none」はスクリプトでクリックが出来なくなるはずです。

 

また、実際は不要な気がしていますが、以下のコードでクリック後にリスト行をこちらのスクリプトで非表示にしています。(今後コードを削除するかも知れません)

 

peta_entry[n].style.display='none';

 

現在の「削除」を実行する関数は以下の状態です。

 

function blocker(){
    let k;
    let peta_entry=[];
    let user_href=[];
    let delete_button=[];

    peta_entry=document.querySelectorAll('#mainContentsLi>ul>li>ul>li');
    for(k=0; k<peta_entry.length;k++){
        let n=k;
        if(peta_entry[n].querySelector('h3 > a')){
            user_href[n]=peta_entry[n].querySelector('h3 > a').getAttribute('href');
            delete_button[n]=peta_entry[n].querySelector('.btnSmallDelete'); }

        if(block_regex_id.test(user_href[n])==true){
            setTimeout( function(){
                delete_button[n].click();
                setTimeout( function(){
                    let popup=document.querySelectorAll('.minimumPopup');
                    if(popup[1]){
                        popup[1].style.zIndex='-1'; }
                    let select=document.querySelectorAll('.minimumApplyButton a:first-child');
                    if(select[1]){
                        select[1].click();
                    }}, 5); // 2回目クリックのタイミング
                setTimeout( function(){
                    peta_entry[n].style.display='none';
                }, 50); // 表示削除のタイミング(2回目クリック後)
            }, 10); }}} // 1回目クリック(ループ全体のタイミング)

 

「MutationObserver」を外す

この「blocker()」という関数は、前コードでは「MutationObserver」を使って、常に発火し易い状態で待機させていました。 これは、主要な「forループ」が同時に重なって実行され、ビジーウェイト状態でどのボタンも押せなくなる傾向がありました。

 

これを避けるため、前回のコードは上記の「10msec」を「400msec」とした苦しい調整をしていました。 しかし、実際に「ペタ管理」ページを開くと、削除は実行されるものの、その後に何秒かのリロードが続くという変な状態になります。

 

改善の方法は「MutationObserver」を使わないことでした。「MutationObserver」は、発火しない事を救うための特効薬ですが、必要な場合に「blocker()」を呼ぶという原則的なコードで、問題なく「削除」が実行できる事が判って来ました。 特効薬に頼ってばかりは、良くありません。

 

「blocker()」を呼ぶ局面

「blocker()」はコード中で以下の場合に呼び出されます。

 

◎「ついたペタ」のタブページを開いた時。(「つけたペタ」から戻る場合も)

◎「削除リスト」に不良「ペタ」の登録をした直後。

◎「ファイルパレット」を閉じた直後。(「削除リスト編集」の有無にかかわらず)

 

「MutationObserver」を使わないので、表示された「ペタ」リストをチェックする「forループ」は、呼び出される毎に1周するだけですが、現在のところ問題はなさそうに見えます。 削除対象の「ペタ」が多数重なった時は不明(通信速度がネックになる?)ですが、数個なら無問題の様です。

 

「MutationObserver」を使わない事で、タイミング調整が変化しました。 一番最後の「10msec」は、不要に見えますが無いと動作しません。 

 

先に書きましたが、「50msec」の部分は、「display='none';」のコードごと不要かも知れません。

 

今後の方針

リスト編集を行った後の「読込みプログラムのリセット」が実現しました。 おそらくこの部分の最後の問題と思いますが、「差分追加」の「ON/OFF」でもリセットが欲しい所です。 滅多に使わないし、無くてもなんとかなるのですが。

 

自動削除動作がスムーズになったと思います。 しかし、こちらは実際の運用に即したテストが、更に必要と思います。

 

 

「Bad Peta Delete ver. 0.3」

以下のコードを「Tampermonkey」に登録する事で、不良「ペタ」を自動削除するスクリプト「Bad Peta Delete」を動作させる事が出来ます。 未確認の部分がありますが、Chrome Firefox Edgeでの動作確認を進めているところです。

 

〔追記〕2019.08.09

「@match」パラメーターをより正確な設定に改めました。

 

〔Bad Peta Delete〕ver. 0.3

// ==UserScript==
// @name         Bad Peta Delete
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description   不良「ペタ」を登録して自動削除する
// @author       Ameba Blog User
// @match        https://peta.ameba.jp/p/showPeta*
// @match        https://peta.ameba.jp/p/delete*
// @match        http://peta.ameba.jp/p/showPeta*
// @match        http://peta.ameba.jp/p/delete*
// @grant        none
// ==/UserScript==

window.addEventListener('load', function(){
    'use strict';

    var peta_block_data={}; // 総合ブロックデータ
    var peta_block_id;
    var block_filter_id;
    var block_regex_id;
    var edit_mode=0;
    var list_edit_mode=0;

    var ua=0;
    var agent=window.navigator.userAgent.toLowerCase();
    if(agent.indexOf('firefox') > -1){ ua=1; } // Firefoxの場合のフラグ
    if(agent.indexOf('edge') > -1){ ua=2; } // Edgeの場合のフラグ

    let read_json=localStorage.getItem('peta_id_back'); // ローカルストレージ 保存名
    peta_block_data=JSON.parse(read_json);
    if(peta_block_data==null){
        peta_block_data=[['tmp1', 'img1'], ['tmp2', 'img2']]; }

    reg_set();

    function reg_set(){
        let k;
        peta_block_id=[];
        for(k=0; k<peta_block_data.length; k++){
            peta_block_id[k]=peta_block_data[k][0]; }
        block_filter_id=peta_block_id.join('|');
        block_regex_id=RegExp(block_filter_id); } // フィルター作成


    let target0=document.querySelector('body'); // 監視 target
    let monitor0=new MutationObserver(mode_select);
    monitor0.observe(target0, {childList: true, subtree: true}); // 監視開始

    mode_select();

    function mode_select(){
        let peta_title=document.querySelector('#ucsMainLeft h1');
        peta_title.onclick=function(event){
            if (event.which==17 || event.ctrlKey==true){
                event.preventDefault();
                edit_mode=3;
                mode_backup(); } // ファイル管理パレット
            else{
                if(edit_mode==0){
                    edit_mode=1;
                    peta_title.style.boxShadow='inset 0 0 0 20px red';
                    peta_title.style.color='#fff'; }
                else if(edit_mode==1){
                    edit_mode=0;
                    peta_title.style.boxShadow='none';
                    peta_title.style.color='#666'; }
                event.preventDefault();
                checker(); }}

        if(edit_mode==0 || edit_mode==3){
            peta_title.style.boxShadow='none';
            peta_title.style.color='#666'; }
        else if(edit_mode==1){
            peta_title.style.boxShadow='inset 0 0 0 20px red';
            peta_title.style.color='#fff'; }}


    blocker(); // ページを開いた時に 1度 削除を実行

    function blocker(){
        let k;
        let peta_entry=[];
        let user_href=[];
        let delete_button=[];

        peta_entry=document.querySelectorAll('#mainContentsLi>ul>li>ul>li');
        for(k=0; k<peta_entry.length;k++){
            let n=k;
            if(peta_entry[n].querySelector('h3 > a')){
                user_href[n]=peta_entry[n].querySelector('h3 > a').getAttribute('href');
                delete_button[n]=peta_entry[n].querySelector('.btnSmallDelete'); }

            if(block_regex_id.test(user_href[n])==true){
                setTimeout( function(){
                    delete_button[n].click();
                    setTimeout( function(){
                        let popup=document.querySelectorAll('.minimumPopup');
                        if(popup[1]){
                            popup[1].style.zIndex='-1'; }
                        let select=document.querySelectorAll('.minimumApplyButton a:first-child');
                        if(select[1]){
                            select[1].click();
                        }}, 5); // 2回目クリックのタイミング
                    setTimeout( function(){
                        peta_entry[n].style.display='none';
                    }, 50); // 表示削除のタイミング(2回目クリック後)
                }, 10); // 1回目クリック(ループ全体のタイミング)
            }}}


    function checker(){
        let k;
        let peta_entry=[];
        let user_href=[];
        let user_id=[];
        let user_src=[];

        peta_entry=document.querySelectorAll('#mainContentsLi>ul>li>ul>li');
        if(peta_entry.length !=0 && edit_mode==1){
            for(k=0; k<peta_entry.length; k++){
                let n=k;
                if(peta_entry[n].querySelector('h3 > a')){
                    user_href[n]=peta_entry[n].querySelector('h3 > a').getAttribute('href');
                    user_src[n]=peta_entry[n].querySelector('h3 img').getAttribute('src');
                    peta_entry[n].onclick=function(event){ // リストのクリックで設定
                        event.preventDefault(); // クリックしてもリンク先に飛ばない
                        local_backup(n); }}}

            function local_backup(n){
                if(edit_mode==1){
                    if(block_regex_id.test(user_href[n])!=true){
                        peta_entry[n].style.outline='2px solid red';
                        setTimeout( conf, 100 ); }

                    function conf(){
                        let ok=confirm(" ⛔  自動削除リストに登録をしますか?");
                        if(ok){
                            user_id[n]=user_href[n].replace('https://profile.ameba.jp/ameba/', '');
                            user_id[n]=user_id[n].replace(/\//g, '');
                            user_src[n]=user_src[n].replace('https://stat.profile.ameba.jp/profile_images/', '');
                            peta_block_data.push([user_id[n], user_src[n]]);
                            let write_json=JSON.stringify(peta_block_data);
                            localStorage.setItem('peta_id_back', write_json); // ローカルストレージ名
                            reg_set();
                            blocker(); } // 登録直後に 削除を1度実行
                        peta_entry[n].style.outline='none'; }}}}}


    function mode_backup(){
        list_box();

        if(document.querySelector('#file_palette')==null){
            let sty;
            sty=['position: absolute;top: 0;left: 0;width: 100%;height: 100%;',
                 'color: #333;background: #e2eef0'].join(' ');
            let insert_dialog;
            insert_dialog=document.createElement('div');
            insert_dialog.setAttribute('style', sty);
            insert_dialog.setAttribute('id', 'file_palette');
            let box=document.querySelector('#notifyItemArea.boxTop');
            box.setAttribute('style', 'position: relative;overflow: hidden');
            box.appendChild(insert_dialog);

            let button1=document.createElement('input');
            button1.setAttribute('type', 'submit');
            button1.setAttribute('value', '削除リストを保存する');
            button1.setAttribute('style', 'padding: 2px 8px 0;margin: 6px 15px');
            if(ua==2){
                button1.setAttribute('style', 'padding: 1px 8px 2px;margin: 8px 15px 4px'); }
            insert_dialog.appendChild(button1);

            button1.onclick=function(){
                let write_json=JSON.stringify(peta_block_data);
                let blob=new Blob([write_json], {type: 'application/json'});
                if(ua==2){
                    window.navigator.msSaveBlob(blob, 'peta_mute.json'); } // 保存ファイル名
                else{
                    let a_elem=document.createElement('a');
                    a_elem.href=URL.createObjectURL(blob);
                    a_elem.download='peta_mute.json'; // 保存ファイル名
                    if(ua==1){
                        a_elem.target='_blank';
                        document.body.appendChild(a_elem); }
                    a_elem.click();
                    if(ua==1){
                        document.body.removeChild(a_elem); }
                    URL.revokeObjectURL(a_elem.href); }}

            let button_add=document.createElement('input');
            button_add.setAttribute('type', 'checkbox');
            button_add.setAttribute('style', 'margin: 0 5px 0 15px;vertical-align: -2px');
            if(ua==2){
                button_add.setAttribute('style', 'margin: 0 5px 0 30px;vertical-align: -1px');}
            insert_dialog.appendChild(button_add);

            let button_add_label=document.createElement('span');
            button_add_label.setAttribute('style', 'background: #e2eef0');
            button_add_label.innerText='差分追加';
            insert_dialog.appendChild(button_add_label);

            let button2=document.createElement('input');
            button2.setAttribute('type', 'file');
            button2.setAttribute('style', 'margin: 6px 0 0 10px;vertical-align: 1px;width: 300px');
            if(ua==1){
                button2.setAttribute('style', 'margin: 6px 0 0 10px;vertical-align: 2px;width: 300px');}
            insert_dialog.appendChild(button2);

            button_add.checked=true;
            button2.addEventListener("change", function(){
                if(!(button2.value)) return; // ファイルが選択されない場合
                let file_list=button2.files;
                if(!file_list) return; // ファイルリストが選択されない場合
                let file=file_list[0];
                if(!file) return; // ファイルが無い場合

                let file_reader=new FileReader();
                file_reader.readAsText(file);
                file_reader.onload=function(){
                    if(file_reader.result.slice(0, 15)=='[["tmp1","img1"'){ // peta_mute.jsonの確認
                        let data_in=JSON.parse(file_reader.result);

                        if(button_add.checked==true){ // 差分追加処理
                            for(let k=0; k<data_in.length; k++){
                                if(block_regex_id.test(data_in[k][0])!=true){
                                    peta_block_data.push([data_in[k][0], data_in[k][1]]); }}}
                        else{
                            peta_block_data=data_in; } // 読込み上書き処理
                        let write_json=JSON.stringify(peta_block_data);
                        localStorage.setItem('peta_id_back', write_json); // ローカルストレージ 保存名
                        reg_set();
                        list_box();
                        if(list_edit_mode==1){ list_editor(); }}};});

            let button3=document.createElement('input');
            button3.setAttribute('id', 'list_edit');
            button3.setAttribute('type', 'submit');
            button3.setAttribute('value', '⬜ 削除リスト編集');
            button3.setAttribute('style', 'padding: 2px 6px 0;margin: 4px 15px 0');
            if(ua==1){
                let sty='padding: 0 6px 1px;margin: 4px 15px 0;line-height: 20px';
                button3.setAttribute('style', sty);}
            if(ua==2){
                button3.setAttribute('style', 'padding: 0 6px 1px;margin: 6px 15px 0;');}

            insert_dialog.appendChild(button3);
            button3.onclick=function(){
                if(list_edit_mode==0){
                    list_edit_mode=1;
                    list_editor(); }
                else{ list_edit_mode=0;
                     document.querySelector('#list_edit').value='⬜ 削除リスト編集'; }}

            let button4=document.createElement('input');
            button4.setAttribute('type', 'submit');
            button4.setAttribute('value', '✖ 閉じる');
            button4.setAttribute('style', 'padding: 2px 6px 0;margin: 4px 15px 0;float: right');
            if(ua==1){
                button4.setAttribute('style', 'padding: 0 6px;margin: 4px 15px;float: right');}
            if(ua==2){
                button4.setAttribute('style', 'padding: 0 6px 1px;margin: 6px 15px 0;float: right');}
            insert_dialog.appendChild(button4);
            button4.onclick=function(){
                edit_mode=0; // ファイルパレットを閉じた時に edit_modeを3→0に
                list_edit_mode=0;
                insert_dialog.remove();
                list_box_clear();
                blocker(); }}} // ファイルパレットを閉じた時に 削除リスト設定結果を実行


    function list_box(){
        if(document.querySelector('#mainContentsLi ul.ex')==null){
            ex_li_space();
            ex_li_disp(); }
        else{
            document.querySelector('#mainContentsLi ul.ex').remove();
            ex_li_disp(); }

        function ex_li_space(){
            let content_ul=document.querySelector('#mainContentsLi ul');
            content_ul.setAttribute('style', 'display: none');
            if(document.querySelector('#desc')){
                document.querySelector('#desc').style.display='none'; }}

        function ex_li_disp(){
            let k;
            let content=document.querySelector('#mainContentsLi');
            content.style.minHeight='300px';
            let content_ul=document.querySelector('#mainContentsLi ul');
            let ex_ul=document.createElement('ul');
            ex_ul.setAttribute('class', 'ex');
            content.insertBefore(ex_ul, content_ul);

            for (k=0; k<peta_block_data.length; k++){
                let li=document.createElement('li');
                let sty;
                if(k==0){
                    sty='margin: 0;padding: 0;height: 0;border: none';}
                else if(k==1){
                    sty='margin: 0;padding: 0;height: 0;border-bottom: 1px dotted #bdbdbd';}
                else{
                    sty='margin: 0;padding: 6px;height: 44px;border-bottom: 1px dotted #bdbdbd';}
                li.setAttribute('style', sty);
                li.setAttribute('class', 'ex');
                ex_ul.appendChild(li); }

            let ex_li=document.querySelectorAll('#mainContentsLi li.ex');

            let user_id=[];
            let user_src=[];
            let user_href=[];

            for (k=0; k<peta_block_data.length; k++){
                if(k !=0 && k !=1){
                    user_id[k]=peta_block_data[k][0];
                    user_href[k]='https://profile.ameba.jp/ameba/' + user_id[k] + '/';
                    let ex_li_a=document.createElement('a');
                    ex_li_a.setAttribute('href', user_href[k]);
                    ex_li_a.setAttribute('style', 'margin-left: 30px;font-size: 16px;line-height: 44px');
                    ex_li[k].appendChild(ex_li_a).textContent=user_id[k];

                    user_src[k]=peta_block_data[k][1];
                    user_src[k]='https://stat.profile.ameba.jp/profile_images/' + user_src[k];
                    let ex_li_span=document.createElement('span');
                    let sty=['float: left;width: 40px;height: 40px;background: #fff;',
                             'padding: 1px;margin-left: 120px;border: 1px solid #d4d4d4;'].join(' ');
                    ex_li_span.setAttribute('style', sty);
                    ex_li[k].appendChild(ex_li_span);
                    let ex_li_img=document.createElement('img');
                    ex_li_img.setAttribute('src', user_src[k]);
                    ex_li_img.setAttribute('style', 'max-width: 40px;max-height: 40px');
                    ex_li_span.appendChild(ex_li_img); }}}}


    function list_box_clear(){
        if(document.querySelector('#mainContentsLi ul.ex')){
            document.querySelector('#mainContentsLi ul.ex').remove();
            let content_ul=document.querySelector('#mainContentsLi ul');
            content_ul.setAttribute('style', 'display: block');
            if(document.querySelector('#desc')){
                document.querySelector('#desc').style.display='block'; }}}


    function list_editor(){
        if(list_edit_mode==1){
            let str='⬛ 削除リスト編集 クリックしたIDの削除指定を解除します';
            document.querySelector('#list_edit').value=str;

            let k;
            let user_id=[];
            let ex_li=document.querySelectorAll('#mainContentsLi li.ex');
            for (k=0; k<ex_li.length; k++){
                let n=k;
                ex_li[n].onclick=function(event){ // リストのクリックで設定
                    event.preventDefault(); // クリックしてもリンク先に飛ばない
                    edit_backup(n); }}

            function edit_backup(n){
                if(list_edit_mode==1){
                    ex_li[n].style.outline='2px solid red';
                    setTimeout( conf, 100 ); }

                function conf(){
                    let ok=confirm(" ⛔  自動削除リストの登録を解除しますか?");
                    if(ok){
                        user_id[n]= ex_li[n].querySelector('a').textContent;
                        peta_block_data.splice(peta_block_id.indexOf(user_id[n]), 1);
                        let write_json=JSON.stringify(peta_block_data);
                        localStorage.setItem('peta_id_back', write_json); // ローカルストレージ名
                        reg_set();

                        document.querySelector('#file_palette').remove(); // パレット再描画をさせる
                        mode_backup(); // ファイル読込み機能をリフレッシュ 同名再読込みを可能にする
                        list_editor(); }
                    ex_li[n].style.outline='none'; }}}}

})