サブパネルの WYSIWYG
このツールは、ページにペーストする表示パーツ(大きさがページサイズでも可能です)を実際のデザインを「サブパネル」上で選択・確認した上でペーストできます。
また、表示パーツを登録バンクに登録する際も、そのコピーの仕方が適切かどうかを「サブパネル」で確認が出来ます。
この様に「サブパネル」は重要でこのツールの唯一のインターフェイスですが、使い込むにつれて、コピーしたパーツの表示が、実際の編集画面上のものと少し違う場合に気付きました。 ペーストすれば正常なデザインとなり問題はないですが、パネル上のデザイン違いはやはり問題です。
「width」「font-family」「font-size」
編集画面(通常表示)は、実際のブログページの状態を再現する様に「WYSIWYG」のエディター環境を備えています。 ブログページの「本文幅」「基本フォント種」「フォントサイズ」を編集画面の「編集枠」にも適用して、ブログ誌面の状態を再現するテクニックが「WYSIWYG」です。(アメブロの場合は行間隔を省略)
ところが、「サブパネル」は編集画面のHTML基底部に生成しているので、「編集枠」のスタイルが適用されません。 これが、「サブパネル」と「編集枠」のパーツ表示の違いになります。 実は、私のブログ環境では、フォントを「メイリオ・16px」に統一しているので、ツール制作時は相違が現れず、最近になって気付いたのです。 アメブロのスキンは「MS Pゴシック」が多用されていて、それを使用しているユーザーは「サブパネル」のフォントの違いに違和感を感じていたかも知れません。
h要素のスタイル 設定
また、私が一番最初に気付いた違いは、「h要素」のスタイル設定です。 これは、編集アイコンの「段落」ボタンにより設定できる「見出し」の設定で、アメブロ独自の文字修飾が同時に適用されます。「h要素」の文字修飾は、ブラウザのデフォルトCSSが先ず適用されますが、「編集枠」内ではアメブロ独自の設定に書換えられます。 そのため「サブパネル」と「編集枠」上で「h要素」のデザインが異なっていました。
サブパネルに追加した補正CSS
「編集枠」の「本文幅」「デザイン幅で表示」「基本フォント種」「フォントサイズ」は、編集画面の「iframe」内の「body.cke_editable」に設定される値です。 これをスクリプトで取得し、CSSコードに書き換えるのが以下のコードです。
ver. 5.3 324行
「h要素」の指定は全てのスキンで共通なので、「サブパネル」の生成時のCSSに追加しています。
サブパネルの表示の相違
下は ver. 5.2 までの表示例で、「h4」「h3」「h2」を指定したテキストを、このツールに登録した状態です。
この編集時のブログスキンは本文幅が「480px」、本文のフォント種は「MS Pゴシック・14px」ですが、サブパネルは「幅620px」、フォント種は「メイリオ・16px」です。 上図のフォント種は判り難いですが、「h要素」の表示は甚だしく異なっている事が判ります。
下は ver. 5.3 の補正コード導入後です。
完全に「編集枠」と「サブパネル」の表示が同じになりました。 サブパネルの幅自体も、記事の実際の本文幅になっています。
「Fixed Format Palette ⭐」ver. 5.3 を使用するには
このツールは Chrome・Edge / Firefox の各ブラウザ版「Tampermonkey」で動作します。「Tampermonkey」にページ末尾の掲載コードを登録する事で、このツールを利用することが出来ます。
なお、「Tampermonkey」に登録した旧バージョンがある場合はそれを削除して、このバージョンを新たに登録してください。
❶「Tampermonkey」を導入します
使用しているブラウザに拡張機能「Tampermonkey」を導入する事が必要です。 以下のページに簡単な導入の説明があるので参照ください。
❷「Tampermonkey」にスクリプトを登録します
●「Tampermonkey」の「+」マークの「新規スクリプト」タブを開きます。
●「新規スクリプト」には、最初からテンプレートが記入されています。 これは全て削除して、完全に空白の編集枠に 下のコードをコピー&ペーストします。
〔コピー方法〕 軽量シンプルなツール「PreBox Button 」を使うと
コード枠内を「Ctrl+左Click」➔「Copy code 」を「左Click」
の操作で、掲載コードのコピーが可能になります。
● 最後に「ファイル」メニューの「保存」を押すと、ツールが使用可能になります。
〔 Fixed Format Palette ⭐ 〕 ver. 5.3
// ==UserScript== // @name Fixed Format Palette ⭐ // @namespace http://tampermonkey.net/ // @version 5.3 // @description 編集枠に定型表示を自動記入するツール // @author Ameba Blog User // @match https://blog.ameba.jp/ucs/entry/srventry* // @exclude https://blog.ameba.jp/ucs/entry/srventrylist.do* // @grant none // ==/UserScript== let retry=0; let interval=setInterval(wait_target, 100); function wait_target(){ retry++; if(retry>10){ // リトライ制限 10回 1sec clearInterval(interval); } let target=document.getElementById('cke_1_contents'); // 監視 target if(target){ clearInterval(interval); main(); }} function main(){ let editor_iframe=document.querySelector('.cke_wysiwyg_frame'); let iframe_doc; let iframe_html; if(editor_iframe){ iframe_doc=editor_iframe.contentWindow.document; iframe_html=iframe_doc.querySelector('html'); } let format_data; let bank; let mode=-1; // 通常表示Write=0・Record=1 Html表示Record=2 Backup=3 let select_format; 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(){ if(document.querySelector('.l-gHeaderLeft__link a')){ // 起動を表示 📛 document.querySelector('.l-gHeaderLeft__link a') .style.boxShadow='inset -14px 0 0 0 #79fbf6'; } editor_iframe=document.querySelector('.cke_wysiwyg_frame'); if(editor_iframe){ // iframe がある「通常表示」の場合 mode=-1; if(document.querySelector('.ffp_menu')){ document.querySelector('.ffp_menu').remove(); } iframe_doc=editor_iframe.contentWindow.document; iframe_doc.addEventListener("keydown", check_key); function check_key(event){ if(event.ctrlKey && event.keyCode==122){ // 「Ctrl + F11」Bankから書出し event.preventDefault(); mode=0; setTimeout(()=>{ write_format(); }, 20); } if(event.altKey && event.keyCode==122){ // 「Alt + F11」Bank登録 event.preventDefault(); mode=1; setTimeout(()=>{ record_format(); }, 20); } if(event.keyCode==122){ // ページ拡大の誤入力を抑止 event.preventDefault(); } } // check_key() } //「通常表示」の場合 else{ //「HTML表示」の場合 mode=-1; if(document.querySelector('.ffp_menu')){ document.querySelector('.ffp_menu').remove(); } document.addEventListener("keydown", check_key_h); function check_key_h(event){ if(event.ctrlKey && event.keyCode==122){ // HTML表示で書込み操作をした場合 event.preventDefault(); event.stopImmediatePropagation(); unselect_h(); setTimeout(()=>{ alert( " ⛔ 定型ブロックの記入は通常編集枠で行ってください\n"+ " == Fixed Format Palette =="); }, 20); } if(event.altKey && event.keyCode==122){ //「Alt+F11」Bank登録(HTMLで登録) if(ua==0){ event.preventDefault(); mode=2; // HTMLでの登録処理 setTimeout(()=>{ record_format_h(); }, 20); } else if(ua==1){ // FirefoxはHTML編集でブロック登録が出来ない event.preventDefault(); event.stopImmediatePropagation(); unselect_h(); setTimeout(()=>{ alert( " ⛔ 定型ブロックの登録は通常編集枠で行ってください\n"+ " == Fixed Format Palette =="); }, 20); }} if(event.keyCode==122){ // ページ拡大の誤入力を抑止 event.preventDefault(); } } // check_key_h } //「HTML表示」の場合 } // catch_key function write_format(){ disp_menu(); iframe_doc.addEventListener("keydown", menu_key_0); function menu_key_0(event){ if(event.keyCode==27 && mode==0){ //「Esc」 event.preventDefault(); mode=-1; menu_end(); } if(event.keyCode==13 && mode==0){ //「Enter」 event.preventDefault(); if(format_data[bank][1]!=''){ write_in(); } // ペースト処理 else{ mode=-1; } setTimeout(()=>{ menu_end(); }, 20); }} let ffp_menu=document.querySelector('.ffp_menu'); ffp_menu.addEventListener("click", menu_mouse); function menu_mouse(event){ event.preventDefault(); event.stopImmediatePropagation(); if(format_data[bank][1]!=''){ write_in(); } // ペースト処理 else{ mode=-1; } setTimeout(()=>{ menu_end(); }, 20); } function write_in(){ 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=iframe_doc.createElement('div'); insert_node_d.innerHTML=format_data[bank][1]; if(ac_node && ac_node.parentNode){ // insert_node_d 生成の条件 親が div要素か BODYの場合 if(ac_node.parentNode.tagName=='DIV' || ac_node.parentNode.tagName=='BODY'){ ac_node.parentNode.insertBefore(insert_node_d, ac_node.nextSibling); d_before_after(); } // insert_node_d 生成の条件 ひとつ外の親が div要素か BODYの場合 else if(ac_node.parentNode.parentNode.tagName=='DIV' || ac_node.parentNode.parentNode.tagName=='BODY'){ ac_node.parentNode.parentNode.insertBefore( insert_node_d, ac_node.parentNode.nextSibling); d_before_after(); } mode=4; } function d_before_after(){ range.setEnd(insert_node_d, 0); range.collapse(); // レンジを閉じる caret_back(); }} } // write_format() function record_format(){ let selection=iframe_doc.getSelection(); let range; if(selection && selection.rangeCount>0){ range=selection.getRangeAt(0); } select_format=document.createElement('div'); select_format.appendChild(range.cloneContents()); select_format=select_format.innerHTML; disp_menu(); iframe_doc.addEventListener("keydown", menu_key_1); function menu_key_1(event){ if(event.keyCode==27 && mode==1){ //「Esc」 event.preventDefault(); mode=-1; menu_end(); } else if(event.keyCode==13 && mode==1){ //「Enter」 event.preventDefault(); format_data[bank][1]=select_format; let write_json=JSON.stringify(format_data); localStorage.setItem('Format_Data', write_json); mode=4; menu_end(); }} // ローカルストレージ 保存 let ffp_menu=document.querySelector('.ffp_menu'); ffp_menu.addEventListener("click", menu_mouse_1); function menu_mouse_1(event){ if(mode==1){ event.preventDefault(); event.stopImmediatePropagation(); format_data[bank][1]=select_format; let write_json=JSON.stringify(format_data); localStorage.setItem('Format_Data', write_json); mode=4; caret_back(); menu_end(); }} // ローカルストレージ 保存 } // record_format() function record_format_h(){ let result=new Promise( function(){ document.execCommand("copy"); }) navigator.clipboard.readText().then( function(clipText){ select_format=clipText; }) .then( function(){ disp_menu(); }) .then( function(){ document.querySelector('.CodeMirror textarea').blur(); }) document.addEventListener("keydown", menu_key_2); function menu_key_2(event){ if(event.keyCode==27 && mode==2){ //「Esc」 event.preventDefault(); mode=-1; unselect_h(); menu_end(); } if(event.keyCode==13 && mode==2){ //「Enter」 event.preventDefault(); mode=4; format_data[bank][1]=select_format; let write_json=JSON.stringify(format_data); localStorage.setItem('Format_Data', write_json); // ローカルストレージ 保存 unselect_h(); menu_end(); }} setTimeout(()=>{ let ffp_menu=document.querySelector('.ffp_menu'); ffp_menu.addEventListener("click", menu_mouse_2); function menu_mouse_2(event){ if(mode==2){ event.preventDefault(); event.stopImmediatePropagation(); mode=4; format_data[bank][1]=select_format; let write_json=JSON.stringify(format_data); localStorage.setItem('Format_Data', write_json); // ローカルストレージ 保存 unselect_h(); menu_end(); }} }, 40); } // record_format_h(} function disp_subwin(){ let css= '.ffp_menu { position: absolute; top: 15px; right: 10px; z-index: 15; '+ 'font-family: Meiryo; font-size: 16px; line-height: 1.6; width: 620px; '+ 'padding: 8px 8px 18px; border: 1px solid #009688; border-radius: 4px; '+ 'background: #fff; box-shadow: 20px 20px 60px 0 rgba(0, 0, 0, .2), '+ 'inset 0 0 0 8px #eee; transform-origin: top; } '+ '.ffp_menu a { pointer-events: none; } '+ '.ffp_menu img { max-width: 99%; height: auto; } '+ '.ffp_menu h2, .ffp_menu h3, .ffp_menu h4 { '+ 'font-weight: normal; line-height: 1.2; } '+ '.ffp_menu h2 { font-size: 1.96em; margin: 0.83em 0; } '+ '.ffp_menu h3 { font-size: 1.4em; margin: 1em 0; } '+ '.ffp_menu h4 { font-size: 1em; margin: 1.33em 0; } '+ '.ffp_menu .empty { '+ 'font-size: 32px; color: #ccc; padding: 15px 0 5px; text-align: center; } '+ '.ffp_title { padding: 4px 0 2px 15px; margin: 10px 0; background: #2196f3; '+ 'color: #fff; font: 16px Meiryo; } '+ '.ffp_title .wrap { display: inline-block; } '+ '.ffp_sw { display: inline-block; padding: 0 6px; line-height: 24px; '+ 'height: 22px; border: 1px solid #fff; border-radius: 3px; cursor: pointer; } '+ '.ffp_sw:hover { color: #2196f3; background: #fff; } '+ '.ffp_input { display: none; }'; css+=cke_style(); let style_menu=document.createElement('style'); style_menu.setAttribute('id', "style_ffp_menu"); style_menu.textContent=css; if(!document.querySelector('#style_ffp_menu')){ document.querySelector('.l-body').appendChild(style_menu); } let menu=document.createElement('div'); menu.setAttribute('class', "ffp_menu"); if(!document.querySelector('.ffp_menu')){ document.querySelector('.l-body').appendChild(menu); }} function cke_style(){ editor_iframe=document.querySelector('.cke_wysiwyg_frame'); if(editor_iframe){ iframe_doc=editor_iframe.contentWindow.document; if(iframe_doc){ let cke=iframe_doc.querySelector('.cke_editable'); if(cke){ let cke_style=getComputedStyle(cke, null); let width=cke_style.getPropertyValue("width").replace(/[^0-9]/g, ''); let font_family=cke_style.getPropertyValue("font-family"); let font_size=cke_style.getPropertyValue("font-size"); return '.ffp_menu { width: '+ (width-16) + 'px; font-family: '+ font_family +'; font-size: '+ font_size +'; }' }}}} function disp_menu(){ disp_subwin(); let menu=document.querySelector('.ffp_menu'); setTimeout(()=>{ let selection=iframe_doc.getSelection(); if(selection){ let range=selection.getRangeAt(0); range.collapse(); // 選択範囲がある時は反転を解除 }}, 40 ); // 登録の読取りの時間余裕 let out=0; bank=1; menu_change(1); // 初期表示Bank if(ua==0){ menu.onmousewheel=function(event){ let wd=event.wheelDelta/120; out-=wd; if(out>=0){ bank=out%9+1; } else{ bank=(out+1)%9+9; } menu_change(bank); }} if(ua==1){ menu.addEventListener('DOMMouseScroll', function(event){ let wd=- event.detail/3; out-=wd; if(out>=0){ bank=out%9+1; } else{ bank=(out+1)%9+9; } menu_change(bank); }); } iframe_doc.addEventListener("keydown", menu_roll); document.addEventListener("keydown", menu_roll); function menu_roll(event){ if(event.keyCode==40 && mode>-1 && mode<3){ //「⇩」 event.preventDefault(); event.stopImmediatePropagation(); if(bank<9){ bank+=1; } else{ bank=1; } menu_change(bank); } if(event.keyCode==38 && mode>-1 && mode<3){ //「⇧」 event.preventDefault(); event.stopImmediatePropagation(); if(bank>1){ bank-=1; } else{ bank=9; } menu_change(bank); }} function menu_change(bank){ let menu=document.querySelector('.ffp_menu'); let menu_str; if(mode==0){ menu_str= '<div class="ffp_title">'+ 'Bank◂'+ bank +'▸ の内容を貼り付けます '+ '<span class="wrap">'+ '実行:<span class="ffp_sw en">Enter</span> '+ '中止:<span class="ffp_sw es">Esc</span></span>'+ '<span class="wrap">'+ ' <span class="ffp_sw bk">File</span></span></div>'; if(format_data[bank][1]==''){ menu_str+='<div class="empty">未登録</div>'; } else{ menu_str+=format_data[bank][1] ; }} if(mode==1 || mode==2){ menu_str='<div id="check">'+ select_format + '</div>'; // テスト menu.innerHTML=menu_str; let check=document.querySelector('#check'); if(check){ if(getComputedStyle(check, null).getPropertyValue("height")=='0px'){ select_format=''; // 非表示要素は無入力に変換する:登録の削除 menu_str= '<div class="ffp_title" style="background: #000;">'+ 'Bank◂'+ bank +'▸ の以下の内容を削除します '+ '<span class="wrap">'+ '削除:<span class="ffp_sw en">Enter</span> '+ '中止:<span class="ffp_sw es">Esc</span></span></div>'; if(format_data[bank][1]==''){ menu_str+='<div class="empty">未登録</div>'; } else{ menu_str+=format_data[bank][1]; }} else { // 登録データを更新する場合 menu_str= '<div class="ffp_title">'+ 'Bank◂'+ bank +'▸ に以下の内容を登録します '+ '<span class="wrap">'+ '登録:<span class="ffp_sw en">Enter</span> '+ '中止:<span class="ffp_sw es">Esc</span></span>'+ '<span class="wrap">'+ ' <span class="ffp_sw bk">File</span></span></div>'+ select_format + '<div class="ffp_title" style="background: #000;">'+ 'Bank◂'+ bank +'▸ の以下の登録内容は削除されます</div>'; if(format_data[bank][1]==''){ menu_str+='<div class="empty">未登録</div>'; } else{ menu_str+=format_data[bank][1] ; }}}} menu.innerHTML=menu_str; let ffp_swbk=document.querySelector('.ffp_sw.bk'); if(ffp_swbk){ ffp_swbk.onclick=function(event){ event.stopImmediatePropagation(); caret_back(); unselect_h(); ffp_backup(); }} let ffp_title=document.querySelector('.ffp_title'); if(ffp_title){ ffp_title.onclick=function(event){ event.stopImmediatePropagation(); caret_back(); }} let ffp_swen=document.querySelector('.ffp_sw.en'); if(ffp_swen){ ffp_swen.onclick=function(event){ unselect_h(); menu.click(); }} let ffp_swes=document.querySelector('.ffp_sw.es'); if(ffp_swes){ ffp_swes.onclick=function(event){ event.stopImmediatePropagation(); caret_back(); mode=-1; unselect_h(); menu.remove(); }} } // menu_change() } // disp_menu(); function menu_end(){ if(document.querySelector('.ffp_menu')){ document.querySelector('.ffp_menu').remove(); }} function caret_back(){ let iframe_body=iframe_doc.querySelector('.cke_editable'); if(iframe_body){ iframe_body.focus(); }} function unselect_h(){ let CodeMirror=document.querySelector('.CodeMirror'); if(CodeMirror){ key_in(36); //「Home」の入力で選択解除しコピー元の削除を防ぐ function key_in(key_Code){ let keyEvent=new KeyboardEvent('keydown', {keyCode: key_Code}); document.querySelector('.CodeMirror textarea').dispatchEvent(keyEvent); }}} function ffp_backup(){ if(document.querySelector('.ffp_menu')){ document.querySelector('.ffp_menu').remove(); } disp_subwin(); let menu=document.querySelector('.ffp_menu'); menu.style.background='#2196f3'; menu.style.padding='20px 8px 22px'; menu.style.width='620px'; let menu_str; menu_str= '<div class="ffp_title"> '+ '<span class="ffp_button1 ffp_sw">登録データをファイルに保存</span> '+ '<span class="ffp_button2 ffp_sw">登録データファイルを読込む</span> '+ '<span class="ffp_button3 ffp_sw">✖</span>'+ '<input class="ffp_input" type="file"></div>'; menu.innerHTML=menu_str; let button1=document.querySelector('.ffp_button1'); let button2=document.querySelector('.ffp_button2'); let ffp_input=document.querySelector('.ffp_input'); let button3=document.querySelector('.ffp_button3'); button1.onclick=function(){ let write_json=JSON.stringify(format_data); // 記録用配列 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(event); event.preventDefault(); if(ua==1){ document.body.removeChild(a_elem); } URL.revokeObjectURL(a_elem.href); } button2.onclick=function(){ ffp_input.click(); } ffp_input.addEventListener("change", function(){ if(!(ffp_input.value)) return; // ファイルが選択されない場合 let file_list=ffp_input.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; // 記録用配列 format_data を上書き let write_json=JSON.stringify(format_data); localStorage.setItem('Format_Data', write_json); // ローカルストレージに保存 }};}); button3.onclick=function(){ if(document.querySelector('.ffp_menu')){ document.querySelector('.ffp_menu').remove(); } caret_back(); unselect_h(); } } // ffp_backup() } // main()
「Fixed Format Palette」はブログ編集画面の表示パーツをコピー登録して、必要な時にペーストできるツールです。 9個のBankを選んで登録でき、ブログに表示できるものはほぼ全て登録が可能です。
▶ 登録・ペースト操作は「通常表示」の編集画面で
操作します( Chromeは「HTML登録」が可能 )
▶ 行数・サイズ・表示内容等に制限はありません
▶ 複雑なデザインを施したパーツの再利用に最適
ショートカット ➔ Bankへ登録: Alt + F11
ショートカット ➔ ペースト : Ctrl + F11
登録Bank・ペーストBank の選択はサブウインドウで指定します。
登録Bankの内容をファイルにバックアップ保存ができます。
「Fixed Format Palette」最新版について
旧いバージョンの JavaScriptツールは、アメーバのページ構成の変更で動作しない場合があり、導入する場合は最新バージョンをお勧めします。
●「Fixed Format Palette」の最新バージョンへのリンクは、以下のページのリンクリストから探せます。