「div要素」の中に書き込みを可能にする
「Ctrl+F11+Bank」でカーソル位置に登録した範囲のHTMLデータをペーストしますが、この処理には案外と難しい問題があります。 JavaScriptのメソッドを調べると、「ペースト」の機能を簡単に実現するメソッドがありません。
HTMLには「p」「span」「div」等の要素の「入れ子」の規則が決まっていて、例えば「p」「span」の中に「div」を入れられません。 適当なメソッドがないのは、この問題に関係するのかも知れません。 詳しくは以下を参照ください。
編集中のコピー&ペーストは簡単に見えても、実際は「OS」「ブラウザ」「編集画面」が相互に絡んだ複雑な処理がされているのだと思います。
ツールとしては万能のメソッドが無いので、目的に合うコードを作る必要がありますが、このツールは「一定の面積を持つ範囲」のぺーストを前提としたコードにしています。 短い定型の文字列を扱うなら、IMEの文字登録を使えば済みますから。
これまでの仕様
旧いバージョンのペースト部のコードの仕様を説明します。「登録範囲の内容」は様々ですから、入れ子に出来る要素が最も多様な「div」に入れて、その「div」をノード挿入する事で、ペーストを実現しています。
◎ 普通の「pタグ」段落内にカーソルがあり、そこでペーストを実行した場合。
「pタグ」の中には「div」を入れられない規則なので、「pタグ」から出た場所に「div」を挿入します。
この際、周囲と「1行の間隔を空ける」ことで、ペースト後の扱いをし易くしています。 これは、不要な1行の削除は、1行の追加より簡単と判断したからです。
◎ カーソルが空白行の位置にある場合は、このツールにとってペースト操作の望ましいカーソル位置です。 これは、一番多く考えられる場合です。
下の様に、基本的にその場所にペーストされますが、先の場合と同様に、周囲との間隔を自動的に1行空ける様にしています。
以上の例は、「JavaScript」という文字列のペーストに過ぎませんが、IMEのペーストと違って「<div>JavaScript</div>」という「div要素」が書き込まれます。
従来の欠点を改善
これまでの方式で、ペーストが働かない事が時々ありました。 この原因の殆どが「自動補完編集」による離れた「開始タグ」「終結タグ」の存在です。 編集中の削除操作などで、「div」要素の「終結タグ」を含む部分が削除されると、自動の補完機能が働いて、文書末尾に</div>が追加されます。「終結タグ」が無いまま記事が公開されるとブログページ全体が崩れるので、この補完機能は必要なものです。
しかしこの補完により、ペーストを実行しようとした場所が「補完したdiv要素」の中に入ってしまう事が生じます。
これまでのコードは、これを「ペースト出来ない」位置と判断して、ペーストを中止します。 この問題を改善するために、「div要素」内にペースト可能とする条件を追加し、条件分岐を作り変えました。
最初に書いた様に、ペーストという操作は色々な場合があり、このコードで不具合が生じる事があるかも知れません。 テストでは、「ペースト出来ない」という場合が減ったのは事実ですが、今後に改善するかも知れません。
なお、複雑に入れ子になった場所でペーストが動作しないのは、従来の通りです。
「登録」データのバックアップ
「登録」したデータは、ブラウザがPCディスク内に専有するローカルストレージに保存しています。 ところろが、全く他の問題でブラウザの「Cookie削除」をした際に、ストレージの内容が消えてしまいました。「登録」が増えて来ると、その「登録元」の場所が見つかり難くなり、そんなに簡単に「登録」が失われては困ります。
簡単に削除されるローカルストレージ
以前は、ローカルストレージのデータは簡単に消せなかったはずですが、昨今のブラウザセキュリティ強化の影響なのか、「Cookie削除」と一緒に「ローカルストレージ」のデータも簡単に削除できる様になっています。
以下は Chromeの場合ですが、編集画面のURL窓の「 」マークをクリックして「サイトの設定」をクリックします。
Chrome管理画面が開き、最初に目に入る「データの削除」を押すと、パスワード等は大丈夫ですが、「Cookie」「ローカルストレージ」の内容が簡単に削除されます。
「Cookie」だけに見える表示なのに、「https://blog.ameba.jp」で登録していたローカルストレージがみんな消えてしまいました。
〔注意〕ローカルストレージの内容は、セキュリティ上では「Cookie」と同レベルで、Web上に表示されても問題のない範囲という認識が大事です。
「登録」 のデータバックアップは必須
という事で、「Fixed Format Palette」に「登録」したデータのバックアップは、絶対に必要と思い知りました。 コードは、これまでのツールのものを流用出来ました。
ショートカットは、Bank番号の「0」を使用していなかったので「Ctrl + F11 + 0」「Alt + F11 + 0」の両方で「バックアップメニュー」が起動する様にしました。
●「定型登録データを保存する」で「ダウンロード」フォルダーに「FFP(n).json」のファイル名のバックアップファイルが保存されます。
●「ファイルを選択」を押して「ダウンロード」フォルダーを探し、上記の名前のファイルを読込みます。 これで、バックアップしたデータを再読込みができます。
読込み操作は、現在の「ローカルストレージ」の「登録」データを上書きします。「登録」データの再編成や修正は、仮に編集画面にペーストして、再度「登録」すれば良いでしょう。
●「×閉じる」を押すと、バックアップメニューが終了します。
編集する際に、登録Bankから必要な定型表示を選んでペーストできます
▶ 登録操作・ペースト操作は「通常表示」の編集画面で操作
▶ 行数・サイズ・表示内容等に制限なく 9個の登録が可能です
ショートカット ➔ Bankへ登録: Alt + F11 + Bank
ショートカット ➔ ペースト : Ctrl + F11 + Bank
登録Bankは 1 ~ 9 の数字キー・テンキーで指定します
▶ ダウンロードにバックアップ保存 : Ctrl + F11 + 0
「Fixed Format Palette」ver. 2.1
このツールは、文書中のブロック範囲を「Bank1~9」に「登録」し、任意の編集画面で「Bank」から選んで定型入力を行うツールです。 従来の扱い難さを改善し、キー操作の失敗をカバーする工夫をしています。
「Fixed Format Palette」を利用するには、下のコードを「Tampermonkey」の新規作成画面にペーストして、保存をしてください。
〔コピー方法〕 軽量シンプルなツール「PreBox Button 」を使うと
コード枠内を「Ctrl+左Click」➔「Copy code 」を「左Click」
の操作で、掲載コードのコピーが可能になります。
〔 Fixed Format Palette 〕 ver. 2.1
// ==UserScript== // @name Fixed Format Palette // @namespace http://tampermonkey.net/ // @version 2.1 // @description 編集枠に定型表示を自動記入するツール // @author Ameba Blog User // @match https://blog.ameba.jp/ucs/entry/srventry* // @grant none // ==/UserScript== window.addEventListener('load', function(){ let editor_iframe=document.querySelector('.cke_wysiwyg_frame'); let iframe_doc=editor_iframe.contentWindow.document; let iframe_html=iframe_doc.querySelector('html'); let format_data; let bank; let ua=0; // Chromeの場合のフラグ let agent=window.navigator.userAgent.toLowerCase(); if(agent.indexOf('firefox') > -1){ ua=1; } // Firefoxの場合のフラグ let read_json=localStorage.getItem('Format_Data'); // ローカルストレージ 保存名 format_data=JSON.parse(read_json); if(format_data==null){ format_data=[['0','FixedFormatPalette'],['1',''],['2',''],['3',''],['4',''],['5',''],['6',''],['7',''],['8',''],['9','']]; } format_data[0][1]='FixedFormatPalette'; let write_json=JSON.stringify(format_data); localStorage.setItem('Format_Data', write_json); // ローカルストレージ 保存 let target=document.getElementById('cke_1_contents'); // 監視 target let monitor=new MutationObserver(catch_key); monitor.observe(target, {childList: true}); // ショートカット待受け開始 catch_key(); function catch_key(){ editor_iframe=document.querySelector('.cke_wysiwyg_frame'); if(editor_iframe){ // iframe がある「通常表示」の場合 iframe_doc=editor_iframe.contentWindow.document; iframe_doc.addEventListener("keydown", check_key1); let gate; let send; function check_key1(event){ send=-1; if(event.keyCode==27){ //「Esc」登録処理の終了 event.preventDefault(); sign_clear(); } if(event.ctrlKey && event.keyCode==122){ // 「Ctrl + F11」Bankから書出し event.preventDefault(); gate=1; } if(gate==1){ get_bank(event); } if(event.altKey && event.keyCode==122){ // 「Alt + F11」Bank登録 event.preventDefault(); gate=2; } if(gate==2){ get_bank(event); } if(send>0){ event.stopImmediatePropagation(); // 二重ペーストを防ぐのに必要 if(gate==1){ gate=-1; bank=send; write_format(); } if(gate==2){ gate=-1; bank=send; sign(0); setTimeout(()=>{ memo_format(); }, 20); }} if(send==0){ gate=-1; sign(1); backup(); }} function get_bank(event){ if(event.keyCode==48 || event.keyCode==96){ event.preventDefault(); send=0; } // ファイルの読込・書出し if(event.keyCode==49 || event.keyCode==97){ event.preventDefault(); send=1; } // Bank1 if(event.keyCode==50 || event.keyCode==98){ event.preventDefault(); send=2; } // Bank2 if(event.keyCode==51 || event.keyCode==99){ event.preventDefault(); send=3; } // Bank3 if(event.keyCode==52 || event.keyCode==100){ event.preventDefault(); send=4; } // Bank4 if(event.keyCode==53 || event.keyCode==101){ event.preventDefault(); send=5; } // Bank5 if(event.keyCode==54 || event.keyCode==102){ event.preventDefault(); send=6; } // Bank6 if(event.keyCode==55 || event.keyCode==103){ event.preventDefault(); send=7; } // Bank7 if(event.keyCode==56 || event.keyCode==104){ event.preventDefault(); send=8; } // Bank8 if(event.keyCode==57 || event.keyCode==105){ event.preventDefault(); send=9; } // Bank9 if(event.keyCode<48 || (event.keyCode>57 && event.keyCode<96) || event.keyCode>105){ // 3個目が数字外の場合(削除防止) if(event.keyCode==122){ ; } else if(event.keyCode==229){ // 漢字変換がONで書き込み操作をした場合(削除防止) event.stopImmediatePropagation(); alert( " ⛔ 編集枠内にカーソルを入れて 漢字変換をOFFにしてください\n"+ " == Fixed Format Palette =="); editor_iframe.blur(); gate=-1; } else{ event.preventDefault(); iframe_doc.getSelection().removeAllRanges(); // 選択範囲を解除(削除防止) gate=-1; }} setTimeout(()=>{ iframe_doc.getSelection().removeAllRanges(); // 選択範囲を解除(削除防止) gate=-1; }, 2000); // 2sec以内にBankを押さないと gateをリセット }} // check_key1 「通常表示」の場合 else{ //「HTML表示」の場合 target.addEventListener("keydown", check_key2); let gate; function check_key2(event){ if(event.ctrlKey && event.keyCode==122){ // HTML表示で書込み操作をした場合 event.stopImmediatePropagation(); alert( " ⛔ 定型ブロックの記入は通常編集枠で行ってください\n"+ " == Fixed Format Palette =="); document.querySelector('.CodeMirror textarea').blur(); } if(event.altKey && event.keyCode==122){ // HTML表示で読込み操作をした場合 event.stopImmediatePropagation(); alert( " ⛔ 定型ブロックの登録は通常編集枠で行ってください\n"+ " == Fixed Format Palette =="); document.querySelector('.CodeMirror textarea').blur(); }}} // check_key2 } // catch_key function write_format(){ let selection=iframe_doc.getSelection(); let range; if(selection && selection.rangeCount>0){ range=selection.getRangeAt(0); } let ac_node=selection.anchorNode; let insert_node_d; read_json=localStorage.getItem('Format_Data'); // ローカルストレージ 保存名 format_data=JSON.parse(read_json); // ストレージの読出し insert_node_d=iframe_doc.createElement('div'); insert_node_d.innerHTML=format_data[bank][1]; if(ac_node.parentNode.tagName=='DIV'){ ac_node.parentNode.insertBefore(insert_node_d, ac_node.nextSibling); d_before_after(); } // d要素生成の条件 親が div要素の中から作成 else if(ac_node.parentNode.tagName=='P'){ if(ac_node.nodeType==3 && ac_node.parentNode.parentNode.tagName=='BODY'){ ac_node.parentNode.parentNode.insertBefore(insert_node_d, ac_node.parentNode.nextSibling); d_before_after(); when_end(); }} // d要素生成の条件 通常のP要素のテキストノードから作成 else if(ac_node.parentNode.tagName=='BODY'){ if((ac_node.tagName=='P' || ac_node.tagName=='DIV') && ac_node.firstChild.tagName=='BR'){ ac_node.parentNode.insertBefore(insert_node_d, ac_node); d_before_after(); when_end(); }} // div要素生成の条件 空白行から作成 function d_before_after(){ if(insert_node_d==insert_node_d.parentNode.firstChild || insert_node_d.previousElementSibling.firstChild.tagName !='BR'){ insert_node_d.insertAdjacentHTML('beforebegin', '<p>\u00A0</p>');} if(insert_node_d.nextElementSibling==null || insert_node_d.nextElementSibling.firstChild.tagName !='BR'){ insert_node_d.insertAdjacentHTML('afterend', '<p>\u00A0</p>');} range.setEnd(insert_node_d, 0); range.collapse();} // 生成したdiv要素の前後の空白行処理 function when_end(){ if(insert_node_d.nextElementSibling==insert_node_d.parentNode.lastChild){ insert_node_d.insertAdjacentHTML('afterend', '<p>\u00A0</p>'); iframe_html.scrollTop=iframe_html.scrollHeight;}} // 文末処理 } // write_format() function memo_format(){ let selection=iframe_doc.getSelection(); let range; if(selection && selection.rangeCount>0){ range=selection.getRangeAt(0); } let select_fomat=document.createElement('div'); select_fomat.appendChild(range.cloneContents()); select_fomat=select_fomat.innerHTML; let conf_str= '🔴 選択した範囲を Bank 「 No '+bank+' 」 に上書き登録します\n'+ ' 登録したデータは 「Ctrl+F11+'+bank+'」 で文書に書込めます\n\n'+ '🔴 これまでの以下の Bank登録内容は削除されます\n'+ format_data[bank][1]; let ok=confirm(conf_str); if(ok){ format_data[bank][1]=select_fomat; let write_json=JSON.stringify(format_data); localStorage.setItem('Format_Data', write_json); } // ローカルストレージ 保存 selection.removeAllRanges(); // 反転を解除 editor_iframe.blur(); // 編集枠からカーソルを外し誤記入を防ぐ sign_clear(); } // memo_format() function sign(type){ let css= '.disp_line_ffp { display: inline-block; margin: 0 0 4px; padding: 4px 15px 1px;'+ 'font-size: 16px; color: #fff; background: #2196f3; }'+ '.disp_line_ffpb { font-weight: bold; padding: 0.2em; }'+ '.disp_line_ffp input{ font-size: 14px; }'; let disp_line=document.createElement('span'); disp_line.setAttribute('class', 'disp_line_ffp'); if(type==0){ // 定型の登録メニュー表示 disp_line.appendChild(document.createTextNode(' Bank ◁')); let bank_span=document.createElement("span"); bank_span.setAttribute('class', 'disp_line_ffpb'); bank_span.appendChild(document.createTextNode(bank)); disp_line.appendChild(bank_span); disp_line.appendChild(document.createTextNode('▷ Esc:end ')); } if(type==1){ // ファイル保存・読込みメニュー表示 let button1=document.createElement('input'); button1.setAttribute('type', 'submit'); button1.setAttribute('class', 'ffp_button1'); button1.setAttribute('value', '定型登録データを保存する'); button1.setAttribute('style', 'padding: 1px 8px 0; margin: 0 15px; color: #000'); if(ua==1){ button1.setAttribute('style', 'padding: 0 8px; margin: 0 15px; color: #000'); } disp_line.appendChild(button1); let button2=document.createElement('input'); button2.setAttribute('class', 'ffp_button2'); button2.setAttribute('type', 'file'); button2.setAttribute('style', 'margin: 0 0 5px 10px; height: 23px; width: 300px'); if(ua==1){ button2.setAttribute('style', 'margin: 0 0 4px 10px; height: 25px; width: 300px'); } disp_line.appendChild(button2); let button3=document.createElement('input'); button3.setAttribute('class', 'ffp_button3'); button3.setAttribute('type', 'submit'); button3.setAttribute('value', '✖ 閉じる'); button3.setAttribute('style', 'padding: 1px 6px 0; margin: 0 15px; color: #000'); if(ua==1){ button3.setAttribute('style', 'padding: 0 6px; margin: 0 15px; color: #000'); } disp_line.appendChild(button3); } monitor.disconnect(); // MutationObserverを 起動表示に反応させない let style_ext=document.createElement('style'); // disp_line のデザインを指定 style_ext.setAttribute('id', 'style_extffp'); style_ext.insertAdjacentHTML('afterbegin', css); if(!target.querySelector('#style_extffp')){ target.appendChild(style_ext); } if(!target.querySelector('.disp_line_ffp')){ target.insertBefore(disp_line, editor_iframe); } monitor.observe(target, {childList: true}); } // Fixed Format Palette 登録部の起動表示 function sign_clear(){ if(target.querySelector('.disp_line_ffp')){ target.querySelector('.disp_line_ffp').remove(); }} // Fixed Format Palette 登録部の起動表示を削除 function backup(){ let button1=document.querySelector('.ffp_button1'); let button2=document.querySelector('.ffp_button2'); let button3=document.querySelector('.ffp_button3'); button1.onclick=function(){ let write_json=JSON.stringify(format_data); // Memo用配列 format_data を書出す let blob=new Blob([write_json], {type: 'application/json'}); let a_elem=document.createElement('a'); a_elem.href=URL.createObjectURL(blob); a_elem.download='FFP.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); } 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)=='[["0","FixedFor'){ // FFP.jsonの確認 let data_in=JSON.parse(file_reader.result); format_data=data_in; // Memo用配列 format_data を上書き let write_json=JSON.stringify(format_data); localStorage.setItem('Format_Data', write_json); // ローカルストレージに保存 }};}); button3.onclick=function(){ sign_clear(); }} // backup() })
「Fixed Format Palette」最新版について
旧いバージョンの JavaScriptツールは、アメーバのページ構成の変更で動作しない場合があり、導入する場合は最新バージョンをお勧めします。
●「Fixed Format Palette」の最新バージョンへのリンクは、以下のページのリンクリストから探せます。