HTML編集箇所へ案内するツール

「通常表示」で編集をしていてHTML編集が必要になった場合、先ず「HTML表示」のボタンをクリックし、次に「HTML表示」の編集枠の中で編集する箇所を探す必要があります。

 

記事の書き始めなら、「HTML表示」を開くとHTMLの先頭部が表示され、すぐに目的の場所がみつかります。 しかし、長い記事の途中の修正などでは、HTML上の問題の場所を探すのが、とても大変な事があります。 間違って、別の場所を修正してしまったという経験があるのは、きっと私だけではないでしょう。 この扱い難さを解決するツールが「Both-WH」です。 

 

操作は単純です。「通常表示」編集枠内にカーソルがある状態で「Ctrl + F8」を押すと、「HTML表示」編集枠に切り替わり、元のカーソル位置のHTML部分にカーソルが表示されます。 記事の途中なら、その場所へスクロールしてHTMLが表示されます。

また、「HTML表示」で「Ctrl + F8」を押すと「通常表示」に戻り、最初にジャンプをした場所に戻ります。

 

 

改善した部分

これまで、両方の編集枠のカーソル位置を関連付けるため、マークとなる図形文字を仮記入していました。 記事中に同じ文字があると検索違いで誤動作するので、普通は使われない文字を利用していました。 この方式は検索コードが簡単ですが、誤動作でマーク文字が残った時は困ります。

 

そこで、今回からマーク文字を以下の「マークタグ」に改めました。

 

<i id="i">•</i>

 

「•」は、Unicodeで「\u200B」「U+200B」「ゼロ幅スペース」などと呼ばれる文字で、表示されない機能文字です。 このマークタグは万が一削除に失敗しても、記事の表示には影響しません。 更に、タグ削除を考えて「i」というIDを付けています。

 

下は「HTML表示」で実際に表示させたマークタグです。 但し、通常はこのタグが目に触れる事はありません。

 

 

 

特殊な一部のWin10絵文字が及ぼす問題

Win10絵文字の中で「異体字」や「特殊文字」を含む行(段落)からジャンプを行うと、文字位置のカウントがズレて、マークタグの一部が残り、後方の記事の1~2文字が削除されたりします。 これに関しては「Both-WH」の開発時にとても苦労し、問題を生じるWin10絵文字を調べて感知フィルターを作りました。

 

今回もこのフィルターを引き継ぎ、問題が生じ得る場合は、以下のダイアログを表示し、「HTML表示」に移動した直後にタグ削除をしない仕様にしています。

 

 

下は「HTML表示」にジャンプした状態です。 普通は、ジャンプ直後にマークタグを削除しますが、特殊な絵文字が含まれた行なので残しています。

 

 

この場合に、従来は手作業でマーク文字の削除が必要でした。 今回のバージョンは、「通常表示」に戻った時点でタグを自動削除します。 マークタグをHTML編集で壊さない限り、「Ctrl + F8」で戻ればタグが残る事はありません。

 

私は特殊な絵文字を使う事がなく、これまでのバージョンでもマークが残る事は無かったのですが、Win10絵文字は少しずつ拡張されている様で、フィルターは不完全かも知れません。 特殊な絵文字がある行でジャンプした場合は、カーソル後方の1~2文字が消される可能性がある事は、理解してください

 

問題を生じる絵文字はWin10絵文字の1/6程度です。 絵文字がある行(段落)であっても、絵文字より手前でジャンプすれば、問題は生じません。

 

特殊なWin10絵文字のフィルターに関しては以下を参照ください。

  「Both-WH」絵文字対策の修正 5 

  「Both-WH」ver. 0.2 絵文字対策の修正版 

 

 

ジャンプ元のカーソル位置を正確に探せる

記事中に配置される各種の要素で、このツールの動作をテストしました。「リブログカード」の様なiframe構造は、その内部のHTMLにアクセスできませんが、カード型の「アフィリエイト」リンクは、カード内の文字の位置へもジャンプできます。 下は「通常表示」でカード内の「円」を反転させ、このHTMLを調べる例です。

 

 

 

「Ctrl + F8」で「HTML表示」が開いた状態です。

 

 

この様な「カード」、コード表記の「pre枠」、各種の「囲み枠」、などの内部のHTMLへも問題なくジャンプできます。

 

 

「Both-WH」の仕様

● アメブロの「最新版エディタ」で動作します。 デフォルトデザインの編集画面でも、Stylus等でアレンジした編集画面でも、正常に動作します。

 

●「通常表示」のカーソル位置で「Ctrl + F8」を押すと、「HTML表示」を開いて、その箇所にカーソルを自動的に表示します。

 

●「HTML表示」で「Ctrl + F8」を押すと、ショートカットで最後に離れた「通常表示」の位置を表示します。 但し、戻った通常表示枠にはカーソルを入れません。 これは、ショートカット連打によるミスを防ぐためです。

 

● 通常の記事では、HTMLへ1sec程度で移動出来ます。 ただし、実際のHTML文字数が見た目より大変に多い場合や、記事が長くて移動場所が文書の最後に近い場合などでは、「HTML表示」を開くまでに数秒かかる場合があります。

 

● 移動元の行中(正確には段落)に一部のWin10絵文字が使われている場合は、「HTML」表示のカーソル位置にマークタグが露出します。 再び「Ctrl +F8」で戻った時に、このマークタグは削除されます。

 

● 想定外の理由により、ジャンプ後のマークタグ削除処理によって、周辺の記事の1~2文字を不本意に削除する事が有り得ます。 重要な文書でのトラブルは、自己責任とお考えください。

 

● 旧バージョンがある場合は、「Tampermonkey」の管理画面で旧バージョンのスクリプトを削除するか無効にしてください。

 

 

「Both-WH」ver. 1.2

以下のコードを「Tampermonkey」にコピー&ペーストして登録する事で、このツールが使用出来ます。 このツールはChrome版 / Firefox版 の「Tampermonkey」で動作を確認しています。

 

〔コピー方法〕 軽量シンプルなツール「PreBox Button   」を使うと

  コード枠内を「Ctrl+左Click」➔「Copy code 」を「左Click」

  の操作で、掲載コードのコピーが可能になります。

 

 

〔 Both-WH 〕ver. 1.2

// ==UserScript==
// @name         Both-WH
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  「通常表示」「HTML表示」のカーソル位置を「Ctrl+F8」で往復する
// @author       Ameba Blog User
// @match        https://blog.ameba.jp/ucs/entry/srventry*
// @grant        none
// ==/UserScript==


window.addEventListener('load', function(){

    let editor_iframe;
    let iframe_doc;
    let wysiwyg; // 通常表示の iframe内 html
    let native_line; // 通常表示のスクロール位置
    let selection;
    let range;
    let insert_node;
    let mark_regex;
    let activeline;
    let codemirror_scroll;


    let target=document.getElementById('cke_1_contents'); // 監視 target は3箇所で共用
    let monitor1=new MutationObserver( catch_key );
    monitor1.observe(target, {childList: true}); // ショートカット待受け開始

    catch_key();

    function catch_key(){
        if(document.querySelector('.cke_wysiwyg_frame') !=null){ //「通常表示」から実行開始
            editor_iframe=document.querySelector('.cke_wysiwyg_frame');
            iframe_doc=editor_iframe.contentWindow.document;

            iframe_doc.addEventListener('keydown', check_key);
            function check_key(event){
                if(event.which==17 || event.ctrlKey==true){
                    if(event.which==119 || event.keyCode==119){ set_mark(); }}}


            function set_mark(){
                selection=iframe_doc.getSelection();
                range=selection.getRangeAt(0);
                insert_node=iframe_doc.createElement("i"); // iタグ 空タグ
                insert_node.appendChild(iframe_doc.createTextNode("\u200B"));
                insert_node.setAttribute("id", "i");
                range.insertNode(insert_node); // カーソル位置にマークタグを書き込む

                wysiwyg=iframe_doc.querySelector('html');
                native_line=wysiwyg.scrollTop; // 通常表示のスクロール位置を記録

                document.querySelector('button[data-mode="source"]').click( in_CodeMirror() ); // HTML表示に移動

                function in_CodeMirror(){
                    let monitor2=new MutationObserver( task_CodeMirror );
                    monitor2.observe(target, {childList: true});

                    function task_CodeMirror(){
                        if(document.querySelector('.CodeMirror-activeline pre')){ // アクティブ行が条件
                            function key_in(key_Code){
                                let keyEvent=new KeyboardEvent('keydown', {keyCode: key_Code});
                                document.querySelector('.CodeMirror textarea').dispatchEvent(keyEvent); }
                            mark_regex=RegExp('<i id="i">•</i>');

                            let line_count=0;
                            for(let j=0; j<3000; j++){
                                activeline=document.querySelector('.CodeMirror-activeline pre');
                                if(mark_regex.test(activeline.textContent)==true ){ break; }
                                else{ line_count +=1; key_in(40); }} // アクティブ行を下方へ移動

                            let zero_pattern=[
                                '\uD83C\uDFC3','\uD83C\uDFC4','\uD83C\uDFCA','\uD83C\uDFCB',
                                '\uD83C\uDFCC','\uD83C\uDFF3','\uD83D\uDC31','\uD83D\uDC41',
                                '\uD83D\uDC68','\uD83D\uDC69','\uD83D\uDC6E','\uD83D\uDC6F',
                                '\uD83D\uDC71','\uD83D\uDC73','\uD83D\uDC81','\uD83D\uDC86',
                                '\uD83D\uDC87','\uD83D\uDE45','\uD83D\uDE46','\uD83D\uDE47',
                                '\uD83D\uDE4B','\uD83D\uDE4D','\uD83D\uDE4E','\uD83D\uDEA3',
                                '\uD83D\uDEB4','\uD83D\uDEB5','\uD83D\uDEB6','\uD83E\uDD26',
                                '\uD83E\uDD37','\uD83E\uDD38','\uD83E\uDD39','\uD83E\uDD3C',
                                '\uD83E\uDD3D','\uD83E\uDD3E','\uD83E[\uDDD6-\uDDDF]',
                                '\u0023\uFE0F\u20E3',
                                '[\u002A]\uFE0F\u20E3',
                                '[\u0030-\u0039]\uFE0F\u20E3',
                                '\u26F9\uFE0F','\u26A0\uFE0F'
                            ].join('|');
                            let zero_regex=RegExp(zero_pattern);
                            let zero_stop=0;
                            if(zero_regex.test(activeline.textContent)==true){
                                alert("位置マークの <i>タグ は 「通常表示」 に戻った時に削除されます");
                                zero_stop=1; }

                            let index_uni=activeline.textContent.indexOf('<i id="i">•</i>'); // unicode16の文字数
                            let real=activeline.textContent.match(/./ug); // サロゲートペアも1文字に文字列を配列化
                            let dim=0; // サロゲートペア文字補正値
                            for(dim=0; dim<index_uni; dim++){
                                let str=real.slice(index_uni - dim, index_uni - dim +15).join('');
                                if(str=='<i id="i">•</i>'){ break; }}
                            index_uni -=dim; // サロゲートペア文字のズレ補正
                            if(activeline.getElementsByClassName('cm-tab')){
                                index_uni -=4*(activeline.getElementsByClassName('cm-tab').length); } // タブ文字補正

                            key_in(36);
                            for(let i=0; i<index_uni; i++){ key_in(39); } // アクティブ行内で右方へindex値だけ移動
                            if(zero_stop==1){ key_in(37); } // 特殊文字がある場合は左方へ1文字移動 削除なし
                            else{
                                for(let i=0; i<15; i++){ key_in(46); }} // タグ文字列15文字<i id="i">•</i>を削除

                            codemirror_scroll=document.querySelector('.CodeMirror-scroll');
                            let win_height=codemirror_scroll.clientHeight;
                            let styles=getComputedStyle(document.querySelector('.cm-bracket'));
                            let line_height=parseFloat(styles.lineHeight);
                            let scroll=0;
                            if(line_count*line_height>=0.4*win_height){
                                if(line_count*line_height>=win_height){ scroll=0.6*win_height; }
                                else{ scroll=line_count*line_height - 0.4*win_height; }}
                            codemirror_scroll.scrollTop +=scroll;

                            document.querySelector('.CodeMirror textarea').focus(); // 入力窓にカーソルを入れる
                            monitor2.disconnect(); } // task_CodeMirrorの終了で監視ループを抜ける
                    }} // in_CodeMirror
            } // set_mark
        } // WYSIWYG表示での場合


        else if(document.querySelector('.CodeMirror') !=null){ //「HTML表示」から実行開始
            document.querySelector('.CodeMirror').addEventListener('keydown', function(event){
                if(event.which==17 || event.ctrlKey==true){
                    if(event.which==119 || event.keyCode==119){ to_native_line(); }}});


            function to_native_line(){
                document.querySelector('button[data-mode="wysiwyg"]').click( in_wysiwyg() ); // 通常表示に移動

                function in_wysiwyg(){
                    let monitor3=new MutationObserver( task_wysiwyg );
                    monitor3.observe(target, {childList: true});

                    task_wysiwyg();

                    function task_wysiwyg(){
                        if(document.querySelector('.cke_wysiwyg_frame') !=null){ //「通常表示」が条件
                            editor_iframe=document.querySelector('.cke_wysiwyg_frame');
                            iframe_doc=editor_iframe.contentWindow.document;
                            wysiwyg=iframe_doc.querySelector('html');

                            if(iframe_doc.querySelector('#i')){
                                iframe_doc.querySelector('#i').remove(); }

                            wysiwyg.scrollTop=native_line; // 記録された通常表示のスクロール位置に移動
                            if(wysiwyg.scrollTop==native_line){
                                monitor3.disconnect(); }}} // task_wysiwyg の終了で監視ループを抜ける
                } // in_wysiwyg
            } // to_native_line
        } // HTML表示での場合
    } // catch_key

})();

 

 

 

「Both-WH ⭐」最新版について 

旧いバージョンの JavaScriptツールは、アメーバのページ構成の変更で動作しない場合があり、導入する場合は最新バージョンをお勧めします。

 

●「Both-WH ⭐」の最新バージョンへのリンクは、以下のページのリンクリストから探せます。