利用者:Mr.sirokuma/common.js

出典: 謎の百科事典もどき『エンペディア(Enpedia)』
ナビゲーションに移動 検索に移動

注意: 保存後、変更を確認するにはブラウザーのキャッシュを消去する必要がある場合があります。

  • Firefox / Safari: Shift を押しながら 再読み込み をクリックするか、Ctrl-F5 または Ctrl-R を押してください (Mac では ⌘-R)
  • Google Chrome: Ctrl-Shift-R を押してください (Mac では ⌘-Shift-R)
  • Microsoft Edge: Ctrl を押しながら 最新の情報に更新 をクリックするか、Ctrl-F5 を押してください。
/**
 * 個人メニューバーにカスタムリンクを追加
 */
$(function() {
    // 1. サブサブページへのリンク
    mw.util.addPortletLink(
        'p-personal',
        mw.util.getUrl('利用者:Mr.sirokuma/サブページ'),
        'サブサブページ',
        'pt-subsubpage',
        'サブページへ移動します',
        null,
        '#pt-preferences' // 個人設定の前に挿入
    );

    // 2. 作成したページへのリンク
    mw.util.addPortletLink(
        'p-personal',
        mw.util.getUrl('利用者:Mr.sirokuma/作成したページ'),
        '作成したページ',
        'pt-myworks',
        '作成したページ一覧へ移動します',
        null,
        '#pt-preferences' // 同じく個人設定の前に挿入
    );
});
/**
 * サイドバー最下部に編集回数ランクバーを設置(完全日本語版)
 */
(function() {
    const userName = mw.config.get('wgUserName');
    if (!userName) return;

    $.getJSON(mw.util.wikiScript('api'), {
        action: 'query',
        list: 'users',
        ususers: userName,
        usprop: 'editcount',
        format: 'json'
    }).done(function(data) {
        const editCount = data.query.users[0].editcount;
        
        // 100刻みの計算
        const nextMilestone = (Math.floor(editCount / 100) + 1) * 100;
        const prevMilestone = Math.floor(editCount / 100) * 100;
        const remaining = nextMilestone - editCount;
        const progress = ((editCount - prevMilestone) / (nextMilestone - prevMilestone)) * 100;

        // 称号の定義(日本語)
        let rankName = "";
        const ranks = [
            { min: 0, name: "🥚 編集者のたまご" },
            { min: 50, name: "🐣 ひよっこ編集者" },
            { min: 100, name: "🌱 草取り初段" },
            { min: 200, name: "🖋️ 執筆の修行身" },
            { min: 300, name: "☘️ 一人前の編集者" },
            { min: 400, name: "🍃 風の編集者" },
            { min: 500, name: "🔥 炎の編集者" },
            { min: 750, name: "🌊 知の探究者" },
            { min: 1000, name: "📜 歴史の編纂者" },
            { min: 1500, name: "🛡️ ウィキの守護神" },
            { min: 2000, name: "🛠️ 驚異の職人技" },
            { min: 3000, name: "⚖️ 公正なる裁定者" },
            { min: 5000, name: "🏛️ 栄誉ある執筆家" },
            { min: 7500, name: "🔮 叡智の賢者" },
            { min: 10000, name: "🌌 百科事典の覇者" },
            { min: 30000, name: "👑 生ける伝説" }
        ];

        for (let i = ranks.length - 1; i >= 0; i--) {
            if (editCount >= ranks[i].min) {
                rankName = ranks[i].name;
                break;
            }
        }

        const contribUrl = mw.util.getUrl('Special:Contributions/' + userName);

        const html = `
            <nav id="p-edit-rank-jp" class="vector-menu vector-menu-portal portal" style="margin: 15px 8px; padding: 12px; background: #ffffff; border-radius: 10px; border: 1px solid #c8ccd1; box-shadow: 0 2px 5px rgba(0,0,0,0.05);">
                <div style="font-size: 0.85em; font-weight: bold; color: #202122; margin-bottom: 3px;">${rankName}</div>
                <div style="font-size: 0.65em; color: #72777d; margin-bottom: 10px;">現在のランク</div>
                
                <a href="${contribUrl}" style="text-decoration: none; color: inherit;" title="あなたの投稿記録を表示します">
                    <div style="background: #eaecf0; border-radius: 10px; height: 10px; width: 100%; overflow: hidden; border: 1px solid #c8ccd1; position: relative;">
                        <div style="background: linear-gradient(90deg, #36c 0%, #447ff5 100%); height: 100%; width: ${progress}%; transition: width 1.5s ease-in-out;"></div>
                    </div>
                    
                    <div style="margin-top: 10px; display: flex; flex-direction: column; gap: 5px;">
                        <div style="display: flex; justify-content: space-between; align-items: baseline;">
                            <span style="font-size: 0.7em; color: #54595d;">次の目標まで あと <b>${remaining}</b></span>
                            <span style="font-size: 0.8em; font-weight: bold; color: #36c;">${progress.toFixed(0)}%</span>
                        </div>
                        <div style="font-size: 0.7em; color: #72777d; border-top: 1px solid #eeeeee; padding-top: 5px; display: flex; justify-content: space-between;">
                            <span>通算編集回数</span>
                            <span><b>${editCount.toLocaleString()}</b> 回</span>
                        </div>
                    </div>
                </a>
            </nav>
        `;

        // サイドバーの「一番最後」に配置
        const $sidebar = $('#mw-panel, #sidebar, .mw-sidebar');
        if ($sidebar.length) {
            $sidebar.append(html);
        } else {
            // 万が一メインのパネルが見つからない場合は既存のメニューの後ろへ
            $('.vector-menu-portal').last().after(html);
        }
    });
})();
/**
 * 鉄道・道路・会社 テンプレート挿入ボタン(5種セット)
 */
(function(mw, $) {
    const action = mw.config.get('wgAction');
    if (!['edit', 'submit'].includes(action)) return;

    // --- テンプレート定義 ---
    const templates = {
        station: `{{駅情報
|社色=
|文字色=
|駅名=
|画像=
|px1=
|画像説明=
|よみがな=
|副駅名=
|前の駅1=
|駅間A1=
|駅間B1=
|次の駅1=
|電報略号=
|駅番号1=
|所属事業者=
|所属路線=
|キロ程1=
|起点駅=
|所在地=
|座標=
|地図国コード=
|座標右上表示=
|駅構造=
|ホーム=
|開業年月日=
|廃止年月日=
|乗車人員=
|乗車人員n=
|乗降人員=
|乗降人員n=
|統計年度=
|乗換=
|備考=
}}`,
        route: `{{Infobox 鉄道路線
| 路線名 = 
| 路線色 = #000000
| 路線色2 = #000000
| ロゴ = 
| ロゴサイズ = 
| 画像 = 
| 画像サイズ = 
| 画像説明 = 
| 通称 = 
| 現況 = 
| 国 = 
| 所在地 = 
| 種類 = 
| 路線網 = 
| 区間 = 
| 起点 = 駅
| 終点 = 駅
| 駅数 = 駅
| 停留場数 = 停留場
| 停留所数 = 停留所
| 経由路線 = 
| 輸送実績 = 
| 1日利用者数 = 
| 電報略号 = 
| 路線記号 = 
| 路線番号 = 
| 路線色3 = 
| 開業 = 
| 項目1 = 
| 日付1 = 
| 項目2 = 
| 日付2 = 
| 項目3 = 
| 日付3 = 
| 最終延伸 = 
| 全通 = 
| 休止 = 
| 廃止 = 
| 再開 = 
| 所有者 = 
| 運営者 = 
| 運転指令所 = 
| 車両基地 = 
| 使用車両 = 
| 路線構造 = 
| 路線距離 = km
| 営業キロ = km
| 軌間 = mm
| 線路数 = 
| 複線区間 = 
| 電化区間 = 
| 電化方式 = 
| 車両限界 = 
| 最大勾配 = ‰
| 最小曲線半径 = m
| 高低差 = m
| 閉塞方式 = 
| 保安装置 = 
| ラック方式 = 
| 最高速度 = km/h
| 駅間平均長 = 
| 諸元備考 = 
| 諸元備考内容 = 
| 路線図 = 
| 路線図名 = 
| 路線図表示 = 
}}`,
        company: `{{基礎情報 会社
|社名 = 
|英文社名 = 
|ロゴ = 
|画像 = 
|画像説明 = 
|種類 = 
|機関設計 = 
|市場情報 = 
|略称 = 
|国籍 = 
|本社郵便番号 = 
|本社所在地 = 
|本店郵便番号 = 
|本店所在地 = 
|設立 = 
|業種 = 
|法人番号 = 
|統一金融機関コード = 
|SWIFTコード = 
|事業内容 = 
|代表者 = 
|資本金 = 
|発行済株式総数 = 
|売上高 = 
|営業利益 = 
|経常利益 = 
|純利益 = 
|純資産 = 
|総資産 = 
|従業員数 = 
|支店舗数 = 
|決算期 = 
|会計監査人 = 
|所有者 = 
|主要株主 = 
|主要部門 = 
|主要子会社 = 
|関係する人物 = 
|外部リンク = 
|特記事項 = 
}}`,
        train: `{{基礎情報 列車
|色=
|文字色=
|列車名=
|ローマ字=
|画像=
|画像説明=
|国=
|種別=
|列車番号=
|現況=
|前身=
|運行開始=
|種別変更=
|運行終了=
|後継=
|運行事業者=
|走行距離=
|所要時間=
|走行路線=
|運行区間=
|起点=
|起終点=
|終点=
|停車駅数=
|使用車両=
|両数=
|最高速度=
|平均速度=
|表定速度=
|外部リンク=
|備考=
}}`,
        road: `{{Infobox road
| 背景色 = 
| 文字色 = 
| アイコン = 
| 名称 = 
| 画像 = 
| 画像サイズ = 
| 画像説明 = 
| 別称 = 
| 現況 = 
| 国 = 
| 種類 = 
| 路線延長 = 
| 総延長 = 
| 実延長 = 
| 陸上区間 = 
| 海上区間 = 
| 制定年 = 
| 開通年 = 
| 最終延伸 = 
| 廃止年 = 
| 起点 = 
| 終点 = 
| 経由地 = 
| 接続道路 = 
| 備考 = 
}}`
    };

    // --- 挿入関数 ---
    const insertTpl = (tpl) => {
        const $txtArea = $('#wpTextbox1');
        if (!$txtArea.length) return;
        const el = $txtArea[0];
        const start = el.selectionStart;
        const end = el.selectionEnd;
        const fullText = $txtArea.val();
        
        // 前後に改行を入れて、確実にブロックとして挿入
        const textToInsert = "\n" + tpl + "\n";
        
        $txtArea.val(fullText.substring(0, start) + textToInsert + fullText.substring(end));
        el.focus();
        el.setSelectionRange(start + 1, start + textToInsert.length - 1);
        $txtArea.trigger('change');
    };

    // --- ボタン設置 ---
    const setup = () => {
        const $target = $('.section-main .group-insert, #toolbar');
        if (!$target.length) {
            setTimeout(setup, 1000);
            return;
        }

        const btnStyle = {
            'margin': '2px 3px',
            'padding': '3px 6px',
            'cursor': 'pointer',
            'background': '#f8f9fa',
            'border': '1px solid #c8ccd1',
            'border-radius': '2px',
            'font-size': '11.5px',
            'font-weight': 'bold',
            'display': 'inline-block'
        };

        const createBtn = (label, tplKey) => {
            return $('<span>').text(label).css(btnStyle).on('click', (e) => {
                e.preventDefault();
                insertTpl(templates[tplKey]);
            });
        };

        // ボタンを生成してツールバーへ追加
        $target.first().append(
            createBtn('🚉駅', 'station'),
            createBtn('🛤️路線', 'route'),
            createBtn('🚄列車', 'train'),
            createBtn('🏢会社', 'company'),
            createBtn('🛣️道路', 'road')
        );
    };

    $(setup);

})(mediaWiki, jQuery);
/**
 * Enpedia 執筆支援ツール【超豪華・永続版】
 */
(function() {
    'use strict';

    const OPTION_NAME = 'enpedia-omikuji-save';
    const omikujiData = [
        { rank: "超大吉", msg: "伝説の編集者。あなたの書いた一文が歴史に残るでしょう。" },
        { rank: "大吉", msg: "誤字脱字がゼロ。読みやすすぎて涙が出るレベルの記事が書けます。" },
        { rank: "大吉", msg: "出典(ソース)が向こうから歩いてくるレベルで見つかります。" },
        { rank: "中吉", msg: "適当に書いた導入文が、驚くほどきれいにまとまります。" },
        { rank: "中吉", msg: "プレビュー1回で完璧。サーバーとの相性も抜群な一日。" },
        { rank: "中吉", msg: "赤リンクが青く染まっていく。あなたの執筆がWikiを救う。" },
        { rank: "小吉", msg: "他人の修正が、実はあなたのミスではなく「改善」だったと判明。" },
        { rank: "小吉", msg: "以前から気になっていた細かいバグや表記揺れがスッキリ直る。" },
        { rank: "小吉", msg: "今日は地味なメンテナンス作業が捗ります。カテゴリ整理が吉。" },
        { rank: "末吉", msg: "新しいテンプレートの使い道が見つかるかも。試行錯誤が大事。" },
        { rank: "末吉", msg: "執筆に行き詰まったら、一度別の記事を読んでみると閃きが。" },
        { rank: "末吉", msg: "大きな進展はないけれど、着実な一歩。保存はこまめに。" },
        { rank: "吉", msg: "いつか書きたいと思っていたネタの資料が偶然手に入ります。" },
        { rank: "吉", msg: "議論が平和に解決。相互理解こそWikiの醍醐味です。" },
        { rank: "吉", msg: "お茶を飲みながら編集すると吉。リラックスして向き合おう。" },
        { rank: "凶", msg: "プレビューしたつもりで、していなかった。ケアレスミスに注意。" },
        { rank: "凶", msg: "「保存」ボタンを押した瞬間に誤字を見つける。あるあるですね。" },
        { rank: "凶", msg: "編集合戦の気配。今日は深追いせず、一旦寝て落ち着こう。" },
        { rank: "大凶", msg: "ブラウザがクラッシュする予感。下書きは必ず外部メモ帳に!" },
        { rank: "執筆神", msg: "【激レア】もはや運勢を超越。あなたが書くことすべてが真実になる。" }
    ];

    const getTodayStr = () => new Date().toISOString().split('T')[0];

    // --- おみくじ永続化の強化 ---
    const loadResult = (callback) => {
        const today = getTodayStr();
        const local = localStorage.getItem(OPTION_NAME);
        if (local && local.startsWith(today)) {
            return callback(parseInt(local.split('|')[1]));
        }

        if (!mw.config.get('wgUserName')) return callback(null);
        new mw.Api().get({ action: 'query', meta: 'userinfo', uiprop: 'options' }).then(data => {
            const saved = data.query.userinfo.options[OPTION_NAME];
            if (saved && saved.startsWith(today)) {
                localStorage.setItem(OPTION_NAME, saved);
                return callback(parseInt(saved.split('|')[1]));
            }
            callback(null);
        });
    };

    const saveResult = (index) => {
        const val = getTodayStr() + '|' + index;
        localStorage.setItem(OPTION_NAME, val); // 即時反映用
        if (mw.config.get('wgUserName')) {
            new mw.Api().saveOption(OPTION_NAME, val);
        }
    };

    const setupOmikuji = () => {
        loadResult((savedIndex) => {
            const isDrawn = savedIndex !== null;
            const res = isDrawn ? omikujiData[savedIndex] : { rank: "?", msg: "運勢未確定" };

            const html = `
                <div id="p-omikuji" class="portal" role="navigation">
                    <h3 style="display:block; font-size:12px; color:#555; font-weight:bold; padding:4px 0;">🔮 今日の運勢: <span id="omi-status-top" style="color:#d00;">${res.rank}</span></h3>
                    <div class="body" style="background:#f9f9f9; border:1px solid #ddd; border-radius:4px; padding:8px; text-align:center;">
                        <div id="omi-result" style="font-weight:bold; font-size:16px; margin-bottom:5px; color:#d00;">${res.rank}</div>
                        <div id="omi-msg" style="font-size:11px; color:#444; line-height:1.4; min-height:3em;">${res.msg}</div>
                        ${!isDrawn ? '<button id="omi-btn" style="margin-top:10px; cursor:pointer; padding:4px 12px; font-size:11px; border-radius:3px; border:1px solid #ccc; background:#fff;">おみくじを引く</button>' : ''}
                    </div>
                </div>`;
            
            const $sidebar = $('#mw-panel, .mw-sidebar, #sidebar, #p-navigation');
            if ($sidebar.length) $sidebar.last().after(html);

            $('#omi-btn').on('click', function() {
                const index = Math.floor(Math.random() * omikujiData.length);
                const finalRes = omikujiData[index];
                $('#omi-result').text(finalRes.rank).hide().fadeIn();
                $('#omi-msg').text(finalRes.msg).hide().fadeIn();
                $('#omi-status-top').text(finalRes.rank);
                $(this).remove();
                saveResult(index);
            });
        });
    };

    // --- 文字数カウンター(豪華演出版) ---
    const setupCounter = () => {
        if (!['edit', 'submit'].includes(mw.config.get('wgAction'))) return;
        const $textbox = $('#wpTextbox1');
        if (!$textbox.length) return;

        const $counter = $('<div id="enp-word-count"></div>').css({
            position: 'fixed', bottom: '20px', right: '20px',
            backgroundColor: 'rgba(0, 0, 0, 0.75)', color: '#fff',
            padding: '8px 18px', borderRadius: '30px', fontSize: '14px',
            zIndex: '9999', pointerEvents: 'none', fontWeight: 'bold',
            boxShadow: '0 4px 10px rgba(0,0,0,0.3)', transition: 'all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)'
        }).text('文字数: 0');

        $('body').append($counter);

        let lastVal = $textbox.val().length;
        const milestones = [500, 1000, 2000, 3000, 5000, 6000, 7000, 8000, 9000, 10000];

        $textbox.on('input keyup change', () => {
            const current = $textbox.val().length;
            if (current === lastVal) return; // 変化なしなら終了

            $counter.text('文字数: ' + current.toLocaleString());

            // 1. 超派手なキリ番演出 (500, 1000...)
            const hitMilestone = milestones.find(m => lastVal < m && current >= m);
            if (hitMilestone) {
                // 背景を虹色アニメーションに
                $counter.css({
                    transform: 'scale(2.5) rotate(720deg) translateY(-50px)',
                    background: 'linear-gradient(45deg, #ff0000, #ff7f00, #ffff00, #00ff00, #0000ff, #4b0082, #8b00ff)',
                    boxShadow: '0 0 40px #fff, 0 0 70px #ff00ff'
                });
                
                // 画面全体に一瞬フラッシュ
                const $flash = $('<div style="position:fixed;top:0;left:0;width:100%;height:100%;background:#fff;z-index:9998;opacity:0.5;"></div>').appendTo('body');
                $flash.fadeOut(1000, function(){ $(this).remove(); });

                setTimeout(() => {
                    $counter.css({ transform: 'scale(1) rotate(0deg)', background: 'rgba(0, 0, 0, 0.75)', boxShadow: '0 4px 10px rgba(0,0,0,0.3)' });
                }, 1500);

            } else if (Math.floor(current / 100) > Math.floor(lastVal / 100)) {
                // 2. 通常の100文字演出
                $counter.css({ transform: 'scale(1.5) translateY(-15px)', backgroundColor: '#d4af37' });
                setTimeout(() => {
                    $counter.css({ transform: 'scale(1)', backgroundColor: 'rgba(0, 0, 0, 0.75)' });
                }, 400);
            }

            lastVal = current;
        });
    };

    $(function() {
        setupOmikuji();
        setupCounter();
    });
})();