CodeIQ向けユーザースクリプト(非公認)

CodeIQのための便利なユーザースクリプト(Greasemonkey)です。(By @Azicore

Windows版Chrome最新版のTampermonkey環境で動作確認しています。必ず自己責任でご利用下さい。

更新情報:

2018/03/19 -
GitHubに過去の全ソースコードを公開しました。
2018/03/08 -
CodeIQの終了が発表されたようです。当ページのユーザースクリプトを今までご利用いただきありがとうございました。
2018/02/26 -
【重要】当ページおよび各スクリプトのURLが変更になりました。大変お手数ですが、ブックマーク等されていた方は変更をお願いいたします。旧URLは、自動的に新URLへリダイレクトします。
2016/09/08 -
CodeIQのサイトリニューアルに伴い、「バッジ問題フィルター」を修正し、v0.2.2を公開。
2016/03/17 -
「バッジ一覧表示」のバグを修正し、v0.3.1を公開。
2016/01/28 -
「バッジ一覧表示」に3D球体表示機能「バッジボール」を追加し、v0.3.0を公開。
2016/01/07 -
「バッジ問題フィルター」を改良し、v0.2.1を公開。
2015/11/08 -
「バッジ一覧表示」のバグを修正し、v0.2.2を公開。「挑戦予定問題の締切日順ソート」を改良し、v0.3.0を公開。「バッジ問題フィルター」を改良し、v0.2.0として再公開。
2015/08/02 -
当ページおよび各スクリプトのURLを変更。
2015/07/23 -
「挑戦予定問題の締切日順ソート」のバグを修正し、v0.2.3を公開。「バッジ一覧表示」のバグを修正し、v0.2.1を公開。
2015/06/04 -
「挑戦予定問題の締切日順ソート」のバグを修正し、v0.2.2を公開。
2015/04/08 -
挑戦済み問題の判別機能復活に伴い、「挑戦済み問題チェッカー」を公開終了。
2015/03/27 -
「挑戦済み問題チェッカー」を公開。「挑戦予定問題の締切日順ソート」を修正し、v0.2.1を公開。
2015/03/23 -
CodeIQのサイトリニューアルに伴い、「バッジ一覧表示」と「挑戦予定問題の締切日順ソート」を修正し、ともにv0.2.0として公開。「バッジ問題フィルター」は公開終了。
2015/02/13 -
「挑戦予定問題の締切日順ソート」のバグを修正し、v0.1.3を公開。
2014/12/09 -
「挑戦予定問題の締切日順ソート」のバグを修正し、v0.1.2を公開。
2014/08/27 -
Chrome拡張のTampermonkeyで正常に動くよう各スクリプトを修正。
2014/08/02 -
「バッジ一覧表示(新バージョン)」を公開。
2014/07/30 -
「バッジ問題フィルター」のバグを修正し、v0.1.1を公開。
2014/07/24 -
「挑戦予定問題の締切日順ソート」と「バッジ問題フィルター」を公開。

GitHubに過去の全ソースコードを公開しています。

バッジ一覧表示(新バージョン)

概要

マイページ上で、あなたの所有しているバッジ全てを見やすく並べて表示します。以前公開したブックマークレットバージョンに比べて、表示サイズの調整、枠線の表示、色彩によるソート、設定の保存、の各機能が追加されています。バッジ数が多すぎて画面に収まりきらない、ごちゃごちゃして美しくない、とお悩みだった方にもご満足いただけます。

さらに、バッジボール機能を使えば、全てのバッジを3次元の球体状に表示させて楽しむことができます。

注意事項

ダウンロード

codeiq_badge_viewer.user.js

ソースコードを隠す «

// ==UserScript==
// @name        CodeIQ badge viewer
// @namespace   jp.ne.sakura.azisava
// @description Display all CodeIQ badges you have
// @include     https://codeiq.jp/my_*
// @run-at      document-end
// @version     0.3.1
// ==/UserScript==

var main = function($) {

'use strict';

var $badges = $('li.views-row > a img');
var n = $badges.length;
var $body = $('body');
var w = $(window).width();
var h = $(window).height();
var PI_2 = 2 * Math.PI; // 2π

// --------------------------------------------------------------------------------
// Badge Viewer
(function() {
    var cls = 'badgeViewer_'; // IDとクラス名のプレフィクス
    var badgeSize = 216; // バッジ画像の本来のサイズ
    var processing = false; // 処理中フラグ
    var pref = {
        frameWidth: { value: 864, step: 18, unit: 'px', min: 180 },   // 全体フレームの幅
        bpl       : { value:  10, step:  1, unit: '個', min:   3 },   // 1段あたりの個数
        frame     : { value:   1, step:  0, text: ['なし', 'あり'] }, // フレームのあり・なし
        colorSort : { value:   0, step:  0, text: ['OFF', 'ON'] }     // 色彩ソートのON・OFF
    };
    var key = 'codeiq_badge_viewer';
    var localStorageAvailable = true;
    
    // 保存された設定があれば取得
    try {
        var prefCache = localStorage.getItem(key);
        if (prefCache) {
            var prefList = prefCache.split(',');
            for (var i = 0; prefList.length > i; i++) {
                var prefItem = prefList[i].split('=');
                pref[prefItem[0]].value = +prefItem[1];
            }
        }
    } catch (e) {
        localStorageAvailable = false;
    }
    
    // ボタン用のCSS
    var btncss = {
        margin: '0px 4px',
        padding: '0.25em 0.75em',
        borderRadius: '1.0em',
        background: '#cccccc',
        fontWeight: 'bold',
        userSelect: 'none',
        '-webkit-user-select': 'none',
        cursor: 'pointer'
    };
    var margincss = { marginLeft: '24px' };
    var boldcss = { fontWeight: 'bold' };
    
    // 各ボタンクリック時の関数
    var buttonClick = function(e) {
        if (processing) return false;
        processing = true;
        e.stopPropagation();
        var $this = $(this);
        var type = $this.attr('data-value-name');
        var p = pref[type];
        // +/-系ボタン
        if (p.step) {
            var sign = $this.html() == '+' ? 1 : -1;
            p.value += sign * p.step;
            if (p.min > p.value) p.value = p.min;
            $('#' + cls + type).html(p.value + p.unit);
        // ON/OFF系ボタン
        } else {
            p.value = +!p.value;
            $('#' + cls + type).html(p.text[p.value]);
        }
        // 設定を保存
        if (localStorageAvailable) {
            var prefList = [];
            for (var i in pref) {
                prefList.push(i + '=' + pref[i].value);
            }
            localStorage.setItem(key, prefList.join(','));
        }
        // バッジを再描画
        displayBadges();
    };
    var $button = function(html, name) {
        return $('<span>').html(html).css(btncss).attr('data-value-name', name).on('click', buttonClick);
    };
    var $span = function(html, css) {
        return $('<span>').html(html).css(css);
    };
    
    var appendBadges = function(badges) {
        var frameWidth   = pref.frameWidth.value;
        var bpl          = pref.bpl.value;
        var paddingRatio = 0.55; // 上下左右のパディング(バッジの幅に対する比)
        var marginRatio  = 0.1;  // バッジとバッジの間のマージン(バッジの幅に対する比)
        var badgeWidth   = frameWidth / (bpl + (bpl - 1) * marginRatio + 2 * paddingRatio);
        var textHeight   = frameWidth / 20;
        var basePosition = 90;  // フレーム最上部の位置
        $('.' + cls + 'badge').remove();
        for (var i = 0; badges.length > i; i++) {
            $body.append(
                badges[i].clone().toggleClass(cls + 'badge').css({
                    position: 'absolute',
                    width   : badgeWidth + 'px', height: badgeWidth + 'px',
                    left    : (w - frameWidth) / 2 + (paddingRatio + i % bpl * (1 + marginRatio)) * badgeWidth,
                    top     : ((i / bpl | 0) * (1 + marginRatio) + 2 * paddingRatio) * badgeWidth + textHeight + basePosition
                })
            );
        }
        // フレームとテキストを表示
        $body.append(
            $('<div>').css({
                position  : 'absolute',
                width     : frameWidth - 2 * pref.frame.value + 'px',
                height    : (Math.ceil(n / bpl) * (1 + marginRatio) - marginRatio + 3 * paddingRatio) * badgeWidth + textHeight - 2 * pref.frame.value + 'px',
                left      : (w - frameWidth) / 2,
                top       : basePosition + 'px',
                border    : pref.frame.value ? '1px solid #999999' : '0px',
                lineHeight: textHeight + 2 * paddingRatio * badgeWidth + 'px',
                fontSize  : textHeight + 'px',
                textAlign : 'center'
            }).addClass(cls + 'badge').html('You have ' + n + ' badges!')
        );
        processing = false;
    };
    
    var displayBadges = function() {
        // 色彩ソートをする場合
        if (pref.colorSort.value) {
            var sortedBadges = [];
            $body.append($('<canvas>').attr({ width: badgeSize, height: badgeSize }).attr('id', cls + 'badgeCanvas').css('display', 'none'));
            var ctx = $('#' + cls + 'badgeCanvas')[0].getContext('2d');
            var loadFinish = function() {
                if ($badges.length > sortedBadges.length) return false;
                $('#' + cls + 'badgeCanvas').remove();
                // 明度でソート
                sortedBadges.sort(function(a, b) {
                    return a.value > b.value ? -1 : 1;
                });
                // 各段ごとに色相でソート
                for (var i = 0; sortedBadges.length > i; i++) {
                    sortedBadges[i].hue += PI_2 * (i / pref.bpl.value | 0);
                }
                sortedBadges.sort(function(a, b) {
                    return a.hue > b.hue ? 1 : -1;
                });
                // バッジを表示
                appendBadges(sortedBadges);
            };
            $badges.each(function(i) {
                var $this = $(this);
                var img = new Image();
                img.onload = function() {
                    ctx.drawImage(img, 0, 0);
                    var data = ctx.getImageData(0, 0, badgeSize, badgeSize).data;
                    var hy = 0;
                    var hx = 0;
                    var v = 0;
                    for (var i = 0; data.length > i; i += 4) {
                        var r = data[i];
                        var g = data[i + 1];
                        var b = data[i + 2];
                        hy += g - b;
                        hx += 2 * r - g - b;
                        v += r > g ? r > b ? r : b : g > b ? g : b;
                    }
                    var h = Math.atan2(Math.sqrt(3) * hy, hx);
                    $this.hue = (h + PI_2) % PI_2; // 色相
                    $this.value = v / data.length; // 明度
                    sortedBadges.push($this);
                    loadFinish();
                };
                img.onerror = function() {
                    $this.hue = PI_2;
                    $this.value = 0;
                    sortedBadges.push($this);
                    loadFinish();
                };
                img.src = $this.attr('src');
            });
        // 色彩ソートをしない場合
        } else {
            var badges = [];
            $badges.each(function() {
                badges.push($(this));
            });
            // バッジを表示
            appendBadges(badges);
        }
    };
    
    var start = function() {
        $body.append(
            // 全画面を覆う<div>を生成
            $('<div>').css({
                width     : w,
                height    : $body.height(),
                position  : 'absolute',
                left      : 0,
                top       : 0,
                background: '#fff'
            }).append(
                // 上部にボタン類を表示
                $('<div>').css({
                    padding: '8px 8px 0px',
                    lineHeight: '1.5'
                }).append(
                    $span('枠線:', {}),
                    $button(pref.frame.text[pref.frame.value], 'frame').attr('id', cls + 'frame'),
                    $span('全体幅:', margincss),
                    $span(pref.frameWidth.value + pref.frameWidth.unit, boldcss).attr('id', cls + 'frameWidth'),
                    $button('+', 'frameWidth'),
                    $button('&minus;', 'frameWidth'),
                    $span('1段の個数:', margincss),
                    $span(pref.bpl.value + pref.bpl.unit, boldcss).attr('id', cls + 'bpl'),
                    $button('+', 'bpl'),
                    $button('&minus;', 'bpl'),
                    $span('色彩ソート:', margincss),
                    $button(pref.colorSort.text[pref.colorSort.value], 'colorSort').attr('id', cls + 'colorSort')
                ).on('click', function(e) {
                    e.stopPropagation();
                })
            // ボタン以外をクリック時は全て消去
            ).on('click', function() {
                $('.' + cls + 'badgeFrame').fadeOut(function() { $(this).remove(); });
                $('.' + cls + 'badge').remove()
                $(window).off('.' + cls);
            }).addClass(cls + 'badgeFrame')
        );
        $(window).on('resize.' + cls, function() {
            w = $(window).width();
            $('.' + cls + 'badgeFrame').css('width', w);
            $('.' + cls + 'badge').remove();
            displayBadges();
        });
        displayBadges();
    };
    
    $('dt', $('.menuBlock').eq(1)).empty().append(
        $('<span>').html('Achievements').attr('title', 'クリックすると、あなたのバッジを見やすく並べて表示します。').css({
            textDecoration: 'underline',
            cursor: 'pointer'
        }).on('click', start)
    );
    
})();

// --------------------------------------------------------------------------------
// Badge Ball
(function() {
    var cls = 'badgeBall_'; // IDとクラス名のプレフィクス
    var enabled = false; // 起動中フラグ
    
    var points = [];                 // 位置ベクトル(Pointオブジェクト)
    var pointsXYZ = [];              // 位置ベクトル(PointXYZオブジェクト)
    var r = 200;                     // 球体の半径
    var s = 4 * Math.PI * r * r / n; // 要素1つあたりの表面積
    var sr = Math.sqrt(s) * 0.45;    // 要素の半径
    var sd = sr * 2;                 // 要素の直径
    
    var k = 0.4 / n;                 // 斥力の定数
    var maxTrial = 400;              // 最大試行回数
    var limit = 0.001;               // 計算停止時の平均移動量
    var dragRatio = 0.003;           // ドラッグ量に対する角度変化の比
    var rotationDecay = 0.9;         // 慣性回転の減衰
    
    // 緯度経度によって球面位置を表すクラス
    var Point = function(lon, lat) {
        this.lon = lon; // 経度(0~2π)
        this.lat = lat; // 緯度(-π/2~π/2)
    };
    Point.prototype = {
        _checkArg: function(arg) {
            return arg > 1 ? 1 : -1 > arg ? -1 : arg;
        },
        // 自身から見た与えられた点の方向と距離を返す
        getRelativePositionOf: function(p) {
            var cos = { t: Math.cos(this.lat), p: Math.cos(p.lat), z: Math.cos(p.lon - this.lon) };
            var sin = { t: Math.sin(this.lat), p: Math.sin(p.lat), z: Math.sin(p.lon - this.lon) };
            cos.x = sin.t * sin.p + cos.t * cos.p * cos.z;
            var x = Math.acos(cos.x);
            sin.x = Math.sin(x);
            var d = Math.acos(this._checkArg((sin.p - sin.t * cos.x) / (cos.t * sin.x)));
            if (0 > sin.z) d = PI_2 - d;
            return { dist: x, dir: d };
        },
        // 与えられた方向と距離だけ自身を移動する
        moveTo: function(v) {
            var x = v.dist, d = v.dir;
            var cos = { t: Math.cos(this.lat), x: Math.cos(x), d: Math.cos(d) };
            var sin = { t: Math.sin(this.lat), x: Math.sin(x), d: Math.sin(d) };
            sin.p = sin.t * cos.x + cos.t * sin.x * cos.d;
            var p = Math.asin(sin.p);
            cos.p = Math.cos(p);
            var z = Math.acos(this._checkArg((cos.x - sin.t * sin.p) / (cos.t * cos.p)));
            this.lon = (0 > sin.d ? this.lon + PI_2 - z : this.lon + z) % PI_2;
            this.lat = p;
        },
        // 与えられた点が同じ位置かどうかを返す
        equals: function(p) {
            return this.lon == p.lon && this.lat == p.lat;
        },
        // xyz座標を返す
        getXYZ: function(r) {
            if (!r) r = 1;
            var t = r * Math.cos(this.lat);
            return new PointXYZ(t * Math.sin(this.lon), -r * Math.sin(this.lat), t * Math.cos(this.lon));
        }
    };
    
    // xyz座標によって球面位置を表すクラス
    var PointXYZ = function(x, y, z) {
        this.x = x;
        this.y = y;
        this.z = z;
    };
    PointXYZ.prototype = {
        // x軸を中心にyz平面で回転する
        rotateX: function(t) {
            var y = this.y;
            var cos = Math.cos(t), sin = Math.sin(t);
            this.y = cos * y - sin * this.z;
            this.z = sin * y + cos * this.z;
        },
        // y軸を中心にxz平面で回転する
        rotateY: function(t) {
            var x = this.x;
            var cos = Math.cos(t), sin = Math.sin(t);
            this.x = cos * x - sin * this.z;
            this.z = sin * x + cos * this.z;
        },
        // 経度と緯度を返す
        getLonLat: function() {
            return new Point(
                (Math.atan2(this.x, this.z) + PI_2) % PI_2,
                -Math.atan2(this.y, Math.sqrt(this.x * this.x + this.z * this.z))
            );
        }
    };
    
    // 全画像を配置
    var renderElements = function(scale) {
        if (!enabled) return;
        if (!scale) scale = 1;
        var $img = $('img.' + cls + 'badge').show();
        // img要素が無い場合は生成
        if ($img.length == 0) {
            $badges.each(function() {
                $body.append($('<img>').addClass(cls + 'badge').attr('src', $(this).attr('src')));
            });
            $img = $('img.' + cls + 'badge');
        }
        var sds = sd * scale;
        var srs = sr * scale;
        for (var i = 0; points.length > i; i++) {
            var p = points[i];
            var c = pointsXYZ[i];
            $img.eq(i).css({
                width     : sds + 'px',
                height    : sds + 'px',
                opacity   : 0.9,
                position  : 'absolute',
                left      : w / 2 - srs + 'px',
                top       : Math.max(250, h / 2) - srs + 'px',
                zIndex    : (c.z + r) * 10 | 0,
                transform : 'perspective(400px)'
                    + ' translate3d(' + c.x * scale + 'px, ' + c.y * scale + 'px, ' + c.z * scale + 'px)'
                    + ' rotate3d(' + Math.cos(p.lon) + ', 0, ' + Math.sin(-p.lon) + ', ' + p.lat + 'rad)'
                    + ' rotateY(' + p.lon + 'rad)'
            });
        }
    };
    
    // 全体を回転
    var rotateElements = function(x, y) {
        for (var i = 0; pointsXYZ.length > i; i++) {
            pointsXYZ[i].rotateY(x * dragRatio);
            pointsXYZ[i].rotateX(y * dragRatio);
            points[i] = pointsXYZ[i].getLonLat();
        }
    };
    
    // イベントを登録
    var prepareEvents = function() {
        var dragging = false;
        var x, y;
        var inertia = null;
        var $html = $('html');
        // マウスドラッグによる回転
        $html.on('mousedown.' + cls, function(e) {
            e.preventDefault();
            dragging = true;
            inertia && clearInterval(inertia);
            x = y = null;
            $html.css('cursor', 'move');
        }).on('mousemove.' + cls, function(e) {
            if (!dragging) return;
            e.preventDefault();
            var xtmp = e.pageX;
            var ytmp = e.pageY;
            if (x != null && y != null) {
                rotateElements(x - xtmp, y - ytmp);
            }
            x = xtmp;
            y = ytmp;
            renderElements();
        }).on('mouseup.' + cls, function(e) {
            dragging = false;
            if (x != null && y != null) {
                var xtmp = x - e.pageX;
                var ytmp = y - e.pageY;
                inertia = setInterval(function() {
                    rotateElements(xtmp, ytmp);
                    renderElements();
                    xtmp *= rotationDecay;
                    ytmp *= rotationDecay;
                    if (!enabled || 0.1 > Math.abs(xtmp) && 0.1 > Math.abs(ytmp)) {
                        clearInterval(inertia);
                        inertia = null;
                    }
                }, 50);
            }
            $html.css('cursor', 'default');
        });
        // ウインドウリサイズ
        $(window).on('resize.' + cls, function() {
            w = $(window).width();
            h = $(window).height();
            $('#' + cls + 'background').width(w);
            renderElements();
        });
        // 閉じるボタン
        $body.append(
            $('<div>').css({
                position: 'absolute',
                right   : '8px',
                top     : '36px',
                fontSize: '48px',
                color   : '#666666',
                cursor  : 'pointer'
            }).html('&times;').attr('title', '閉じる').on('click', function() {
                enabled = false;
                $('#' + cls + 'background').fadeOut(function() { $(this).remove(); });
                $('img.' + cls + 'badge').hide();
                $(this).remove();
                $('html').off('.' + cls);
                $(window).off('.' + cls);
            })
        );
    };
    
    // 出現アニメーション
    var appearBall = function() {
        var totalStep = 20;
        var step = 0;
        var timer = setInterval(function() {
            if (!enabled) {
                clearInterval(timer);
            } else if (++step == totalStep) {
                clearInterval(timer);
                prepareEvents();
                renderElements();
            } else {
                var x = step / totalStep;
                renderElements(x * (1.8 - x) / 0.8); // 二次曲線:f(x)=x(x-2m)/(1-2m) (m=0.9)
            }
        }, 50);
    };
    
    // 要素と座標の準備
    var start = function() {
        enabled = true;
        // 全画面を覆う<div>を生成
        $body.append(
            $('<div>').attr('id', cls + 'background').css({
                width     : w,
                height    : $body.height(),
                position  : 'absolute',
                left      : 0,
                top       : 0,
                background: '#fff',
                opacity   : 0.9
            })
        );
        // 座標が未計算の場合
        if (points.length == 0) {
            // メッセージ
            var $msg = $('<div>').attr('id', cls + 'message').css({
                position : 'absolute',
                left     : 0,
                top      : '48px',
                width    : w,
                textAlign: 'center',
                fontSize : '24px'
            }).html('計算中です。少々お待ち下さい。');
            $body.append($msg);
            // 初期配置(ランダム)
            for (var i = 0; n > i; i++) {
                points.push(new Point(Math.random() * PI_2, (Math.random() - 0.5) * Math.PI));
            }
            // 斥力シミュレーション
            var timer = setInterval(function() {
                var e = 0, etmp;
                for (var i = 0; points.length > i; i++) {
                    var p = points[i];
                    var f = { x: 0, y: 0 };
                    for (var j = 0; points.length > j; j++) {
                        var q = points[j];
                        if (p.equals(q)) {
                            continue;
                        }
                        var v = p.getRelativePositionOf(q);
                        var t = -k / (v.dist * v.dist);
                        f.x += t * Math.cos(v.dir);
                        f.y += t * Math.sin(v.dir);
                    }
                    p.moveTo({
                        dist: etmp = Math.sqrt(f.x * f.x + f.y * f.y),
                        dir : Math.atan2(f.y, f.x)
                    });
                    pointsXYZ[i] = p.getXYZ(r);
                    e += etmp;
                }
                if (!enabled) {
                    clearInterval(timer);
                } else if (limit * n > e || --maxTrial == 0) {
                    clearInterval(timer);
                    $msg.remove();
                    appearBall();
                }
            }, 0);
        // 座標が計算済みの場合
        } else {
            appearBall();
        }
    };
    
    // 起動ボタン
    $('dt', $('.menuBlock').eq(1)).append(
        $('<img>').attr('src',
            'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAIAAADZrBkAAAAAAXNSR0IArs' +
            '4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACZSURBVDhPlZBRDoAgDEO5OY' +
            'fjYFrtfEwyNb4P7NYWEttC732MsV1IaxNeiQvO6aRz7h7KtnUiVEMjIm3yFqEa1pIJmAkJ10Tp3gYSgl' +
            'odKKOi1POKz6gotD48+B71U7PGHKuTRQvfHnueErZLHPADd/XrT+YhJ6iV7oFnewjXiObMhC1CNTQi0h' +
            'l7tnWqhraIXAkd8134QWs7uFtlwYH/w6UAAAAASUVORK5CYII='
        ).attr('title', 'バッジボール').css({
            cursor: 'pointer',
            marginLeft: '8px'
        }).on('click', start)
    );
})();

};

var el = document.createElement('script');
el.textContent = 'jQuery(' + main + ');';
document.body.appendChild(el);

挑戦予定問題の締切日順ソート

概要

マイページの「あとで挑戦予定」の問題を、締切日の早い順に自動的にソートします。

注意事項

ダウンロード

codeiq_bookmark_sorter.user.js

ソースコードを隠す «

// ==UserScript==
// @name        CodeIQ bookmark sorting script
// @namespace   jp.ne.sakura.azisava
// @description Sort bookmarked questions in CodeIQ my page
// @include     https://codeiq.jp/my_challenge_before
// @include     https://codeiq.jp/my_challenge_before*
// @run-at      document-end
// @version     0.3.0
// ==/UserScript==

var main = function($) {
    var msg = '[CodeIQ bookmark sorting script] ';
    var e = [];
    var mon = new Date().getMonth() + 1;
    var page = 1;
    var gather = function($body, callback) {
        console.log(msg + 'Gathering page ' + page++ + '...');
        $('.view-display-id-codeiq_mypage_challenge_before > .view-content > div', $body).each(function() {
            var $t = $(this);
            e.push([
                $t,
                $('.cdata_Limit > strong', $t).text().replace(/([AP]M)(\d+)/, function(str, $1, $2) {
                    return $1 == 'AM' ? $2 % 12 : 12 > +$2 ? +$2 + 12 : $2;
                }).replace(/^\D+(\d+)\D(\d+)\D+(\d+)\D(\d+)$/, function(str, $1, $2, $3, $4) {
                    return (mon > +$1 ? 2 : 1) * 1e8 + $1 * 1e6 + $2 * 1e4 + $3 * 100 + $4 * 1;
                }),
                $('h2', $t).text()
            ]);
        });
        var $next = $('.pager__item--next > a', $body);
        if ($next.length) {
            $.ajax({
                url: $next.attr('href'),
                dataType: 'html',
                success: function(html) {
                    html = html
                        .replace(/<(img|link) ([^>]+)>/g, '')
                        .replace(/<(script|iframe)([ >])/g, '<!-- $1$2')
                        .replace(/<\/(script|iframe)>/g, '</$1 -->');
                    gather($(html), callback);
                },
                error: function() {
                    console.log(msg + 'Error occurred.');
                }
            });
        } else {
            callback();
        }
    };
    gather($('body'), function() {
        e.sort(function(a, b) {
            return a[1] > b[1] ? 1 : -1;
        });
        var $d = $('.view-display-id-codeiq_mypage_challenge_before > .view-content').empty();
        for (var i = 0; e.length > i; i++) {
            var $g = e[i][0];
            $g.attr('class', '').addClass('views-row ' + (i % 2 ? 'views-row-odd' : 'views-row-even'));
            (i == 0 || i == e.length - 1) && $g.addClass(i ? 'views-row-last' : 'views-row-first');
            var $m = $('.titDate > a', $g);
            $('img', $m).length || $m.prepend('<img src="/sites/all/themes/codeiq/images/btn_delete.gif" width="19" height="19" alt="削除">');
            $d.append($g);
            console.log(msg + 'Key=' + e[i][1] + '; Name="' + e[i][2] + '";');
        }
        $('ul.pager').empty();
        console.log(msg + 'Sort completed.');
    });
};

var el = document.createElement('script');
el.textContent = 'jQuery(' + main + ');';
document.body.appendChild(el);

バッジ問題フィルター

概要

ログイン済みのトップページで、バッジ付与の可能性があると思われる問題だけ自動的に強調表示(背景色を変更)します。

注意事項

ダウンロード

codeiq_badge_question_filter.user.js

ソースコードを隠す «

// ==UserScript==
// @name        CodeIQ badge question filter
// @namespace   jp.ne.sakura.azisava
// @description Change background color of badge questions in CodeIQ top page
// @include     https://codeiq.jp/
// @include     https://codeiq.jp/#*
// @include     https://codeiq.jp/q/search*
// @run-at      document-end
// @version     0.2.2
// ==/UserScript==

var main = function($) {
    var msg = '[CodeIQ badge question filter] ';
    var css = { background: '#fc6' }; // バッジ問題に設定する背景色
    var key = 'codeiq_badge_questions'; // localStorageのキー
    var maxlog = 200; // キャッシュする問題数の上限
    var cache;
    // localStorageが使えない環境では実行しない
    try {
        var lsval = localStorage.getItem(key);
        cache = lsval ? lsval.split(',') : [];
    } catch (e) {
        console.log(msg + 'Error: localStorage is not available.');
        return false;
    }
    // 現在のキャッシュデータをハッシュに入れる
    var hash = {};
    for (var i = 0; cache.length > i; i++) {
        var c = cache[i].split('=');
        hash[c[0]] = c[1];
    }
    cache = [];
    var $q = $('a.panel-codeiq, a.pickupBox[href*="site_type=0"], a.panel-item[href*="site_type=0"]');
    var qnum = $q.length;
    var finish = function() {
        // 全問題を確認し終わったときの処理
        if (!--qnum) {
            for (var i in hash) if (i !== '') cache.push(i + '=' + hash[i]);
            // 古いキャッシュを消去する
            cache.length = Math.min(cache.length, maxlog);
            // 新たなキャッシュデータをlocalStorageに保存する
            localStorage.setItem(key, cache.join(','));
            console.log(msg + 'Check completed.');
        }
    };
    // 掲載中の全ての問題についてくり返す
    var qlist = {};
    $q.each(function() {
        var $t = $(this);
        var qid = $t.attr('href').match(/\d+$/)[0];
        var $e = qlist[qid];
        qlist[qid] = $e ? $e.add($t) : $t;
    });
    $.each(qlist, function(qid, $t) {
        var $h = $t.find('.panel-title, .pickupBox-title, .item-title').eq(0);
        var title = $h.text().replace(/^[ \n]+|[ \n]+$/g, '');
        var url = '/q/' + qid;
        // 各問題に対する処理
        var check = function(isBadge) {
            if (isBadge) $t.css(css);
            cache.push(qid + '=' + (isBadge ? 'y' : 'n'));
            delete hash[qid];
        };
        // キャッシュに存在する問題の場合、キャッシュのデータに従う
        if (hash[qid]) {
            var isBadge = hash[qid] == 'y'; // バッジ問題かどうか
            check(isBadge);
            console.log(msg + 'Cache hit: "' + title + '"');
            finish();
            return true;
        }
        // キャッシュに存在しない問題の場合、問題ページを確認しに行く
        $.ajax({
            url: url,
            dataType: 'html',
            success: function(html) {
                html = html
                    .replace(/<img src="[^"]*tit_sub09\.gif"[^>]+>/, '<span class="badgecheck"></span>')
                    .replace(/<(img|link) ([^>]+)>/g, '')
                    .replace(/<(script|iframe)([ >])/g, '<!-- $1$2')
                    .replace(/<\/(script|iframe)>/g, '</$1 -->');
                var $c = $('span.badgecheck', html);
                var isBadge = $c.length && $c.parent().next().text().indexOf('バッジ') >= 0; // バッジ問題かどうか
                check(isBadge);
                console.log(msg + 'Check success: "' + title + '"');
            },
            error: function() {
                console.log(msg + 'Check failed: "' + title + '"');
            },
            complete: finish
        });
    });
};

var el = document.createElement('script');
el.textContent = 'jQuery(' + main + ');';
document.body.appendChild(el);

挑戦済み問題チェッカー

概要

2015年3月23日のサイトリニューアルによって区別ができなくなってしまった挑戦済みの問題の背景色を変更して、一目で分かるようにします。

注意事項

ダウンロード

codeiq_history_checker.user.js

ソースコードを表示 »