引用符/コメント
引用符/コメント
「引用の追加」マクロのコードを改変しました。
- ポップアップメニューから任意の 引用マーク や 箇条書きの行頭記号、行コメントの記号 などの種類を選択して、選択範囲をふくむの各行の先頭に追加します。
選択範囲がないばあいはカーソル行の先頭に挿入します。
- 行番号のドラッグでの複数行選択やトリプルクリックでの行選択などで末尾改行が含まれているばあい、さいごの改行を無視します。
- 行頭に挿入する文字列として クリップボード の文字列データや 入力ダイアログ で指定した文字列を使用することもできます。
・「クリップボード」の機能は「行の先頭に貼り付け」マクロとおなじものです。 ・「任意の文字列」は改行コードやタブ文字も記述できますが、それぞれ入力ダイアログで「\\\n」と「\\\t」で入力されたもの (注:¥記号3つ) を改行コードとタブ文字に置換するようにしてあります。 ※「\n」や「\t」と記述したばあい、そのままの文字列として「\n」や「\t」が返されます。
挿入/削除できる引用マークやコメントマーク
- 各行の先頭にメタ記号を追加
- ␣ がついたものは、半角スペースつきで記号を挿入します。
- 「1つ削除」/「すべて削除」では、記号のうしろの半角スペースの有無を区別しません。
- 「・」中黒 の半角差分の 「・」半角カナの中黒(U+FF65)と、「·」欧文用ユニコード文字のビュレット(U+00B7) を削除対象として追加してあります。
> ␣ (メール 引用マーク) >> (BBS アンカー記号) ・ (箇条書き 全角中黒) ␣ * ␣ (箇条書き アスタリスク) - ␣ (箇条書き 半角ハイフンマイナス) ※ (注意書き ※印) ␣ (半角スペース) ☐ (全角スペース) › (TABコード) ⏎ (空行: 改行コード) ※削除コマンドからの削除不可 // ␣ (C系, JavaScript コメント) # ␣ (Perl, Ruby, Python コメント) ; ␣ (INI コメント) ' ␣ (VB コメント) -- ␣ (SQL コメント) : ␣ (MS-DOS ラベル) :: ␣ (BAT コメント) REM ␣ (BAT コメント)
- ※以下の3種は、「1つ削除」/「すべて削除」ではアンコメントされません。
ポップアップメニュー内にそれぞれ専用の "アンコメント" コマンドがあります)
- 選択範囲の行全体をひとまとめでコメントアウト
<!-- (XML コメント) --> /* (C系, JavaScript コメント) */
- 選択範囲の行全体を複数行形式でコメントアウト(※字下げ位置でコメントアウトします)
/* * (C系, JavaScript ブロックコメント) */
- ポップアップメニューの既存の項目を減らしたいばあいは、ソースコード内の "m.Add( … );" の行を // でコメントアウトするとその項目をメニューから隠せます。
ただし、メニューから隠れるだけで、「1つ削除」/「すべて削除」の対象から除外されるわけではありません。
ダウンロード
ダウンロード: 「メディア:引用符/コメント.zip」(アイコン入り)
- 第三版: 2019/04/07 (コマンドを追加: 空行の追加、任意の文字列の削除)
- 第二版: 2018/10/30
- 第一版: 2018/10/28
ソースコード
#title = "引用符を追加..."
#tooltip = "引用符/コメントマークを追加・削除"
// #icon = "Mery用 マテリアルデザインっぽいアイコン.icl",96
// 選択範囲の行頭に引用符/コメントマークを追加・削除する
// 公式wikiのマクロライブラリの「引用の追加」を改造した
// (Last modified: 2019/04/07)
/*
* --------------------------------------------------
* 引用の追加 ( => 2018/10/14 公開停止)
* Orginal Copyright (c) Kuro. All Rights Reserved.
* www: http://www.haijin-boys.com/
* --------------------------------------------------
* Modified by sukemaru
* 「引用符を追加」または「引用符/コメント」
* --------------------------------------------------
*/
// 引用符の種類
var q = new Array( "" , // 以下 r = 1~10、11~20、21~30、31~ の ID 順
"> " , "・" , " * " , "- " , " " , " " , "\t" , "// " , "# " , "; " ,
"' " , "-- " , ": " , ":: " , "REM " , "※" , "" , "" , ">> " , ">>" ,
">" , "・ " , "・" , "· " , "·" , "* " , "*" , "--" , "-" , "//" ,
"#" , ";" , "'" , "::" , ":" );
/*
* ※半角スペースの有無で「1つ削除/すべて削除」がマッチしなくなるので、
* ">> " 以降に半角スペース あり/なし の差分要素を適当に追加してあります。
* ※半角スペース差分(中黒=ビュレットは全角/半角差分も)の変更は、配列内の編集ではなく
* ポップアップメニュー項目のID(番号)変更で対応しないと、「1つ削除/すべて削除」が効かなくなります
* (同一文字をふくむ要素は文字列の長いものが先に置かれていないとダメ)。
* 「・」は半角カナの中黒(U+FF65)、「·」は欧文用ユニコード文字のビュレット(U+00B7)
*/
// ポップアップメニューの項目とID
var m = CreatePopupMenu();
// m.Add( "ラベル", r ); の各行は、任意に上下移動(並べ替え)してよいが、
// r の数値は上の配列 q の並び順やテキスト変換処理の case r: に対応しているので変更しないこと!
m.Add( "? 任意の文字列 (&E)...", 50 );
m.Add( " クリップボード (&C)", 40 );
m.Add( "", 0, meMenuSeparator );
m.Add( "> > メール引用符 (&>)", 1 );
m.Add( ">> >> BBS アンカー (&>)", 20 );
m.Add( "・ ・ 箇条書き (&/)", 2 );
m.Add( " * * 箇条書き (&*)", 3 );
m.Add( "- - 箇条書き (&-)", 4 );
m.Add( "※ ※注意書き (&K)", 16 );
m.Add( "", 0, meMenuSeparator );
m.Add( "␣ 半角スペース (&1)", 5 );
m.Add( "⃞ 全角スペース (&2)", 6 );
m.Add( "› タブコード (&T)", 7 );
m.Add( "⏎ 空行 (&N)", 52 );
m.Add( "", 0, meMenuSeparator );
m.Add( "\/\/ JS・C コメント (&J)", 8 );
m.Add( "# Perl コメント (&P)", 9 );
m.Add( "; INI コメント (&I)", 10 );
m.Add( "’ VB コメント (&V)", 11 );
m.Add( "-- SQL コメント (&S)", 12 );
m.Add( ": MS-DOS ラベル (&M)", 13 );
m.Add( ":: BAT コメント (&B)", 14 );
m.Add( "REM BAT コメント (&R)", 15 );
m.Add( "", 0, meMenuSeparator );
m.Add( "● 1つ削除 (&D)", 17 );
m.Add( "● すべて削除 (&A)", 18 );
m.Add( "", 0, meMenuSeparator );
m.Add( "? 行頭から任意の文字列を削除 (&Q)...", 51 );
m.Add( "", 0, meMenuSeparator );
m.Add( "/* * */ JS コメントアウト 2 (&J)", 42 );
m.Add( "/* */ JS コメントアウト 1 (&J)", 41 );
m.Add( " JS アンコメント (&U)", 43 );
m.Add( "", 0, meMenuSeparator );
m.Add( "<!-- --> XML コメントアウト (&X)", 44 );
m.Add( " XML アンコメント (&L)", 45 );
// m.Add( "", 0, meMenuSeparator );
// m.Add( "キャンセル", 0 ); // Escキーでキャンセルできるのでアクセラレータなし
// ポップアップメニューの表示
// m.Track(0); ならキャレット位置、m.Track(1); ならカーソル位置にサブメニューがポップアップ
var r = m.Track( mePosMouse = 1 );
if ( r > 0 ) {
Redraw = false;
var sx = ScrollX, sy = ScrollY; // スクロール位置を保存
var s = document.selection;
// 選択範囲がないときのカーソル位置を取得
// pos は「マクロ実行前に選択範囲なし」フラグとしても使う
var pos = s.IsEmpty ? s.GetActivePos() : "";
// 選択範囲を取得する
var ax = s.GetTopPointX( mePosLogical );
var ay = s.GetTopPointY( mePosLogical );
var bx = s.GetBottomPointX( mePosLogical );
var by = s.GetBottomPointY( mePosLogical );
// 選択範囲の末尾が行頭にあるときの調整
if ( ay != by && bx == 1 && document.Text.charAt( s.GetActivePos() ) )
by --;
// 選択範囲を拡張して確定する
s.SetActivePoint( mePosLogical, 1, by );
s.EndOfLine( false, mePosLogical );
s.SetAnchorPoint( mePosLogical, 1, ay );
// 選択範囲の文字列を取得
var st = s.Text; // 拡張された行全体のテキスト
var exit = false; // Quit の代用フラグ
// IDごとのテキスト変換処理
switch ( r ) {
case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10:
case 11: case 12: case 13: case 14: case 15: case 16: case 20:
// 引用符を追加
s.Text = insertQuote( st, q[r] );
/* とりあえずコメントアウトしておく */
// // マクロ実行前に選択範囲がなかった場合は、カーソルを元の位置に戻す
// if ( pos ) {
// s.SetActivePos( pos + q[r].length );
// exit = true;
// }
break;
case 52:
// 空行を挿入 ※空行は削除コマンドの削除対象に含まれない
var nl = "\n"; // 空改行
s.Text = InsertQuote( st, nl ); // 各行の先頭に追加
if ( pos ) {
s.SetActivePos( pos + nl.length );
exit = true;
}
break;
case 17:
// 各行の先頭の引用符/コメントマークを1つ削除
var dt = deleteQuote( st );
if ( dt == st ) {
if ( pos ) {
s.SetActivePos( pos );
exit = true;
}
break;
}
else {
s.Text = dt; // 削除完了
if ( pos && dt.length ) {
s.SetActivePos( pos - ( st.length - dt.length ) );
exit = true;
}
break;
}
case 18:
var org = st;
// 各行の先頭の引用符/コメントマークをすべて削除
for ( var i = 0; i < 40; i ++ ) { // st を最大40回 deleteQuote() でループ処理
var dt = deleteQuote( st );
if ( dt == st ) {
break;
}
else {
st = dt; // deleteQuote() 処理したテキスト dt を st に再代入
}
}
if ( dt == org ) {
if ( pos ) {
s.SetActivePos( pos );
exit = true;
}
break;
}
else {
s.Text = dt; // 削除完了
if ( pos && dt.length) {
s.SetActivePos( pos - ( org.length - dt.length ) );
exit = true;
}
break;
}
case 40:
// クリップボードのテキストデータを取得して、各行の先頭に追加
var cb = ClipboardData.GetData();
if ( cb ) {
s.Text = InsertQuote( st, cb );
}
if ( pos ) {
s.SetActivePos( pos + cb.length );
exit = true;
}
break;
/* 任意の文字列 */
case 50:
// ダイアログのテキスト入力フィールドから文字列を取得
// 文字コードには非対応(入力されたままの文字列を返す)
var p = Prompt(
"前につける文字列:\t改行=\\\\\\n ; タブ=\\\\\\t (注:¥記号3つ)", ""
).replace( /\\\\\\n/g , "\n" ).replace( /\\\\\\t/g , "\t" );
if ( p ) {
s.Text = InsertQuote( st, p ); // 各行の先頭に追加
}
if ( pos ) {
s.SetActivePos( pos + p.length );
exit = true;
}
break;
/* 任意の文字列を削除 */
case 51:
// ダイアログのテキスト入力フィールドから文字列を取得
var p = Prompt(
"行頭から削除する文字列:\tタブ=\\\\\\t (注:¥記号3つ)", ""
).replace( /\\\\\\t/g , "\t" );
var reg = new RegExp( "^" + Quote( p ) , "gm" );
// var reg = new RegExp( "^" + p.replace( /[$()*+.?[\\\]^{|}]/g, "\\$&" ) , "gm" );
dt = st.replace( reg , "" ); // 各行の先頭の指定文字列を削除
if ( dt == st ) {
if ( pos ) {
s.SetActivePos( pos );
exit = true;
}
break;
}
else {
s.Text = dt; // 削除完了
if ( pos && dt.length ) {
s.SetActivePos( pos - ( st.length - dt.length ) );
exit = true;
}
}
break;
case 41: case 44:
// 「引用の追加」では行単位に拡張した選択範囲をまとめてコメントアウトするので、
// 行の中間部分だけをコメントアウトするなら「カッコで囲う」マクロを使うこと。
var p1, p2;
if ( r == 41 ) { // /* JavaScript コメントアウト1 */
p1 = "/* ";
p2 = " */";
}
else { // ( r == 44 ) // <!-- XML コメントアウト -->
p1 = "<!-- ";
p2 = " -->";
}
s.Text = p1 + st + p2; // 選択範囲 st をコメントアウト
if ( ! st ) { // 空行のばあい
s.SetActivePos( pos + p1.length ); // カーソルをコメント枠のなかに移動
exit = true;
}
break;
case 42:
/**
* ※ 行頭の字下げされた位置でコメントアウトする(コメントドキュメント向け)
* ・選択範囲内の最小の字下げ位置にあわせる。
* ・基本的に「JS アンコメント」してもレイアウトを保持できるが、
* 各行の字下げルールが同じである前提なので、半角スペースもタブコードも1文字として数える。
* ・空白文字だけの行は空行と見做し、行頭記号 " * " を付けた後ろに空白文字を残さない。
* ・「引用符/コメント」では行単位でコメントアウトするので、
* 行の中間部分だけをコメントアウトするなら「カッコで囲う」マクロを使うこと。
*/
var p1 = "/**", p2 = " */", ast = " * ", nl = "\n";
s.Text = CommentOutJS( st, p1, p2, ast ); // 選択範囲全体を /* *コメントアウト */
if ( ! st ) { // マクロ実行前に空行だった場合はカーソルをコメント枠のなかに移動
s.SetActivePos( pos + ( p1 + nl + ast ).length );
exit = true;
}
// カーソルを接頭辞のうしろに移動
else {
s.SetActivePoint( mePosLogical, 1, ay );
s.EndOfLine();
exit = true;
}
break;
case 43: // case 41 & 42 の JavaScript コメント をアンコメントする
/*
* ・この文章のようなインデントされたコメントブロックの字下げ位置を考慮するが、
* 先頭のコメントマーク "/*" のスラッシュの前の字下げ(空白部分)が基準となる。
* ・複数のコメントブロックが選択範囲内にある場合、最初のコメントブロックしかアンコメントしない。
* ・中間行の行頭記号はアスタリスク「*」と全角/半角の中黒「・」「・」を削除対象とする。
* (行頭記号と前後の半角スペース各1を削除する)
* ・JSコメントでない箇条書き文で実行した場合も行頭記号を削除する。
*
* ▼ 注意事項 ▼
* ・選択範囲がJSコメントか箇条書き文であれば、選択範囲の先頭/末尾の余計な空白行も削除する。
* ・中間行の末尾空白文字も削除し、空白行ならインデント部分も削除する。
*
* コメントマークは 配列 [ "接頭辞" , "接尾辞" , "中間行の行頭記号" ] で用意する。
* 各要素はあらかじめJSの正規表現で記述しておくこと。
*/
// コメントの "接頭辞" と "接尾辞" と "行頭記号" を正規表現で配列に格納
var p = new Array( "\\/\\*+ ?" , " ?\\*\\/" , " ?(\\*|[・・]) ?" );
// コメントアウトを解除
var dc = DeleteComment( st, p )
// 選択範囲に /* JSコメント */ がなかった場合 undo 履歴を残さない
if ( dc == st ) {
if ( pos ) {
s.SetActivePos( pos );
exit = true;
}
}
else {
// 先頭と末尾の空行を削除してアンコメント完了
// ※[\s ]* だけだと消したくない部分まで消されるので \n をつける
s.Text = dc.replace( /[\s ]*$/gm , "" )
.replace( /^[\s ]*\n|\n[\s ]*$|/g , "" );
}
break;
case 45: // case 44 の <!--␣ XMLコメント ␣--> をアンコメントする
// 選択範囲内のコメントマークをすべて削除
var dc = st.replace( / ?<!-+ ?| ?-+ *> ?/g , "" );
// 選択範囲に <!-- XMLコメント --> がなかった場合 undo 履歴を残さない
if ( dc == st ) {
if ( pos ) {
s.SetActivePos( pos );
exit = true;
}
}
else {
s.Text = dc // アンコメント完了
}
break;
default:
break;
}
// 選択範囲を復元(選択範囲を 移動/コピー/切り取り しやすいように末尾改行まで含める)
if ( ! exit ) {
s.SetActivePos( s.GetActivePos() + 1 );
s.SetAnchorPoint( mePosLogical, 1, ay );
// 選択範囲の中身が ^\n のみなら選択解除
if ( s.Text.match( /^\n$/g ) )
s.SetActivePos( s.GetAnchorPos() );
}
ScrollX = sx; ScrollY = sy; // スクロール位置を復元
Redraw = true;
}
/* 関数 insertQuote( arg1, arg2 ) */
// Kuro版まま
function insertQuote( arg1, arg2 ) {
var a = arg1.split( "\n" );
for ( var i = 0; i < a.length; i ++ ) {
a[i] = arg2 + a[i];
}
return a.join( "\n" );
}
/* 関数 deleteQuote( arg1 ) */
// 各行ごとに、配列 q の並び順で最初にマッチした引用符を削除
// Kuro版から
// ①変数名を変更
// ②最後の if 文の「break;」を補遺し、「1つ削除」の不具合を修正
function deleteQuote( arg1, arg2 ) {
var a = arg1.split( "\n" );
for ( var i = 0; i < a.length; i ++ ) {
for ( var j = 0; j < q.length; j ++ ) {
if ( q[j].length == 0 ) {
continue;
}
var qt = q[j];
if ( a[i].substr( 0, qt.length ) == qt ) {
a[i] = a[i].substr( qt.length );
break;
}
}
}
return a.join( "\n" );
}
/**
* 関数 CommentOutJS( arg0, arg1, arg2, arg3 ) // CommentOutJS( st, p1, p2, ast )
*
* ※ 行頭のインデントを維持して字下げされた位置でコメントアウトするパターン(コメントドキュメント向け)
* ・選択範囲内の最小の字下げ位置にあわせる。
* ・基本的に「JS アンコメント」してもレイアウトを保持できるが、
* 各行の字下げルールが同じである前提なので、半角スペースもタブコードも1文字として数える。
* ・空白文字だけの行は空行と見做し、行頭記号 " * " を付けた後ろに空白文字を残さない。
*/
function CommentOutJS( arg0, arg1, arg2, arg3 ) {
var a = arg0.split( "\n" ); // 選択範囲 st を "\n" で区切って配列 a に
var b = []; // 各行の字下げ数 id を取得する配列
// 各行の字下げ数からの最小値を取得
for ( var i = 0; i < a.length; i ++ ) { // 「1行め」から繰りかえし処理
var id = a[i].search( /[^ \t]/ ); // 字下げの空白文字数を取得(空白行では -1)
b.push( ( id < 0 ) ? 100000 : id ); // ※ -1 は sort のジャマなのでデタラメな数値に置きかえる
}
b.sort( CompareForSort ); // 配列 b を昇順で並びかえ( => 最小値 b[0] だけ使う)
var blanc = a[0].slice( 0, b[0] ); // 先頭行から最小の字下げ部分の「空白文字 ␣␣ 」を取得
// 字下げ位置に中間行の行頭記号 " * " を追加
for ( var i = 0; i < a.length; i ++ ) { // 「1行め」から繰りかえし処理
if ( a[i].match( /^[ \t]*$/ ) ) { // 空白行
a[i] = blanc + arg3;
}
else {
a[i] = blanc + arg3 + a[i].slice( b[0] );
}
}
// 各行を "\n" で区切って連結し、接頭辞と接尾辞を付け足す
var prefix = blanc + arg1 + "\n"; // 接頭辞 "/*" の前に「字下げ ␣␣ 」、後ろに改行
var suffix = "\n" + blanc + arg2; // 接尾辞 " */" の前に改行と「字下げ ␣␣ 」
return prefix + a.join( "\n" ) + suffix;
}
/**
* 関数 DeleteComment( arg1, arg2 )
*
* 複数のコメントブロックが選択範囲内にある場合、最初のコメントブロックしかアンコメントしない
* 引数 arg2 は 配列[ "接頭辞" , "接尾辞" , "中間行の接頭辞" ] で、
* 各要素はあらかじめJSの正規表現で記述されていないとダメ
*/
function DeleteComment( arg1, arg2 ) {
var a = arg1.split( "\n" ); // 選択範囲 st を "\n" で区切って配列 a に格納する
var _reg, _re, _id, _a1, _a2;
var _hit = false, _line0 = 0, _line1 = a.length; // 初期化の必要そうな変数
// コメントの接尾辞と接尾辞を検索・置換する
for ( var j = 0; j < 2; j ++ ){
if ( arg2[j] == "" ) { // 接頭辞 p[0], 接尾辞 p[1] が定義されていないならスルー
continue;
}
for ( var i = _line0; i < a.length; i ++ ) { // 先頭行からループ処理
_reg = new RegExp( arg2[j] , "" ); // 接頭辞 p[0], 接頭辞 p[1] の検索用正規表現変数
if ( a[i].match( _reg ) ) { // ヒットしたら
a[i] = a[i].replace( _reg , "" ); // 置換処理(削除)
if ( j == 0 ) {
_hit = true; // ヒットフラグ = true
_line0 = i; // 接頭辞がヒットした行
break; // 接頭辞を処理したら接尾辞の処理へ
}
else { // ( j == 1 )
_line1 = i; // 接尾辞がヒットした行
break; // 接尾辞を処理したらループを抜ける
}
}
}
}
// 中間行の行頭記号 p[2] (アスタリスク、中黒)を検索・置換する
// 行頭空白文字の後ろの2文字までを置換対象にする |^ a*bcdefg には誤爆しないはず
if ( arg2[2].length ) {
// 接頭辞(よりも前)の行と接尾辞(よりも後ろ)の行は、検索・置換の対象外にする
for ( var i = ( _hit ) ? _line0 + 1 : 0; i < _line1; i ++ ) {
_re = new RegExp( "^[ \\t]*" + arg2[2] , "" ); // 行頭空白+行頭記号 検索用の正規表現変数
if ( a[i].match( _re ) ) { // 行頭記号がヒットしたら
_id = a[i].indexOf( a[i].match( /[^ \t]/ ) ) + 2; // 置換範囲(空白文字数+2文字)
_reg = new RegExp( arg2[2] , "" ); // 行頭記号 置換用の正規表現変数
_a1 = a[i].slice( 0, _id ).replace( _reg , "" ); // 空白文字の後ろの2文字までを置換
_a2 = a[i].slice( _id ); // 行頭記号よりも後ろの文字列は置換対象にしない
a[i] = _a1 + _a2;
}
}
}
return a.join( "\n" ); // 各行を "\n" で区切って連結しなおす
}
/* 以下の関数は『sort メソッド (Array) (JavaScript) | MSDN』より
https://msdn.microsoft.com/ja-jp/library/4b4fbfhk%28v=vs.94%29.aspx */
// Sorts array elements in ascending order numerically.
// sort メソッドデフォルトの ascii 昇順 (1, 10, 2, 20) ではなく、数値の大きさ (1, 2, 10, 20) でソート
function CompareForSort( first, second ) {
if ( first == second )
return 0;
if ( first < second )
return -1;
else // ( first > second )
return 1;
}
/* 以下の関数は『JavaScript/正規表現 - Wikibooks』より
https://ja.wikibooks.org/wiki/JavaScript/正規表現
https://ja.wikibooks.org/wiki/JavaScript/%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE */
// PerlのquotemetaやRubyのRegExp.quoteのように、()や[]など正規表現のメタ文字と解釈される可能性のある文字をエスケープして返す関数は、Stringオブジェクトのreplaceメソッドを使用して簡単に作成することができます。
function Quote( str ) {
return str.replace( /\W/g, function( $0 ) {
return '\\' + $0;
} );
};
謝辞
Kuro 氏の「引用の追加」は Mery のマクロの勉強をはじめたきっかけになったマクロです。
文字列操作、ポップアップメニューの使い方、JavaScript の配列や関数の使い方、if 文、switch 文、for 文、etc... 入門者の "とっかかり" として最適なマクロだったとおもいます(※もとの「引用の追加」は、このページのものよりもシンプル&コンパクトなマクロでした)。
たいへん有用なマクロを作ってくださった Kuro 氏に感謝申し上げます。
残念ながら「引用の追加」マクロは公開停止になってしまったので、この度わたしの手元でカスタマイズしたものを公開させていただきました。
改訂版の公開にご承諾いただいた Kuro 氏に重ねて御礼申し上げます。 (sukemaru)
スポンサーリンク