スマホ用のJavascript/CSSコンソールを作ったお話

スマホ版ブラウザでPC版Chromeコンソールのようなものが（即座に！）使えればいいのになと思ったので、非常に簡易的なものではありますが、作りました。

#ブックマークレットのコード
```ruby:bookmarklet
javascript:window.outerJsConsole%3D%7Be%3A function(v%2Cf)%7Bvar a%3Ddocument.createElement(v)%3Bif(v%3D%3D"script")%7Ba.setAttribute("type"%2C"text%2Fjavascript")%3Bif(!f)%7Ba.setAttribute("id"%2C"newScriptTag")%3B%7Da.classList.add("OJCscript")%3B%7Delse if(v%3D%3D"style")%7Ba.setAttribute("type"%2C"text%2Fcss")%3Bf %3F a.setAttribute("id"%2C"baseStyleTag")%3A a.classList.add("OJCstyle")%3B%7Delse%7Ba.classList.add("OJCelm")%3B%7Dreturn a%3B%7D%2Calert%3A function(v%2Ccaution)%7Bvar a%3Dnew Date()%2Cah%3Da.getHours()< 10 %3F "0" %2B a.getHours()%3A a.getHours()%2Cam%3Da.getMinutes()< 10 %3F "0" %2B a.getMinutes()%3A a.getMinutes()%2Cas%3Da.getSeconds()< 10 %3F "0" %2B a.getSeconds()%3A a.getSeconds()%2Cb%3Dthis.now %3F "(JS)" %3A "(CSS)"%2Cc%3Dcaution %3F %60 class%3D"OJCcaution"%60 %3A ""%2Cd%3D%60<table%60 %2B c %2B %60><tbody><tr><td style%3D"font-style%3A italic%3Bwidth%3A 6em">%60 %2B ah %2B "%3A" %2B am %2B "%3A" %2B as %2B %60<br><b style%3D"color%3A %23aaa">%60 %2B b %2B %60<%2Fb><%2Ftd><td><pre style%3D"background%3A transparent%3Bborder%3A none%3Bborder-radius%3A 0%3Bfont-family%3A sans-serif">%60 %2B v %2B %60<%2Fpre><%2Ftd><%2Ftr><%2Ftbody><%2Ftable>%60%2Ce%3Ddocument.getElementById("OJCalert")%3Be.innerHTML%3Dd %2B e.innerHTML%3B%7D%2Carea%3A function()%7Bvar a%3Dthis.e("style"%2Ctrue)%2Cb%3Dthis.e("div")%3Ba.innerHTML%3D%60%23OJCconsole %23OJCalert%7Bfont-family%3A sans-serif%3B%7D%23OJCconsole %23OJCalert%7Bposition%3A fixed%3Bbackground%3A %23f0f0f0%3Bborder%3A 1px solid %23aaa%3Bbottom%3A 30vh%3Bfont-size%3A 0.9em%3Bleft%3A 0%3Bpadding%3A 0.75em%3Bheight%3A 5rem%3Bwidth%3A 100%25%3Boverflow-y%3A scroll%3B-webkit-overflow-scrolling%3A touch%3B%7D%23OJCconsole %23OJCalert table%7Bborder-bottom%3A 1px solid %23aaa%3Bdisplay%3A block%3Bpadding%3A 0.5em 0%3B%7D%23OJCconsole %23OJCalert table.OJCcaution%7Bbackground%3A %23fff0f0%3B%7D%23OJCconsole .OJCwrap%7Bposition%3A fixed%3Bbottom%3A 0%3Bleft%3A 0%3Bheight%3A 30vh%3Bwidth%3A 100%25%3B%7D%23OJCconsole .OJCwrap *%7Bposition%3A relative%3Bappearance%3A none%3B-webkit-appearance%3A none%3B-moz-appearance%3A none%3B%7D%23OJCconsole .OJCwrap textarea%7Bborder%3A 1px solid %23aaa%3Bborder-radius%3A 0%3Bfont-size%3A 16px%3Btransform%3A scale(0.8)%3Btransform-origin%3A top left%3Bheight%3A calc((100%25 - 4em)%2F 0.8)%3Bpadding%3A 0.75em%3Bwidth%3A calc(100%25 %2F 0.8)%3B-webkit-overflow-scrolling%3A touch%3B%7D%23OJCconsole .OJCwrap div%7Bdisplay%3A flex%3Bheight%3A 3em%3Bjustify-content%3A center%3B%7D%23OJCconsole .OJCwrap div button%7Bbackground%3A %23b01%3Bborder%3A 1px solid %23fff%3Bcolor%3A %23fff%3Bdisplay%3A block%3Bflex-grow%3A 1%3Bfont-family%3A sans-serif%3Bfont-size%3A 15px%3Bfont-weight%3A bold%3Bpadding%3A 0.5em%3Bwidth%3A 15%25%3B%7D%60%3Bb.setAttribute("id"%2C"OJCconsole")%3Bb.innerHTML%3D%60<div id%3D"OJCalert"><%2Fdiv><div class%3D"OJCwrap"><div><button onclick%3D"outerJsConsole.change(this)%3B" style%3D"background%3A %23065fd4%3B">JS<%2Fbutton><button onclick%3D"outerJsConsole.apply()%3B">apply<%2Fbutton><button onclick%3D"outerJsConsole.code()%3B">code<%2Fbutton><button onclick%3D"outerJsConsole.close()%3B">close<%2Fbutton><%2Fdiv><textarea id%3D"outerJsTgt"><%2Ftextarea><%2Fdiv>%60%3Bdocument.head.appendChild(a)%3Bdocument.body.appendChild(b)%3B%7D%2Cnow%3A true%2Cchange%3A function(v)%7Bvar a%3D(b%2Cc)%3D>%7Bthis.now%3Db%3Bv.innerHTML%3Dc%3B%7D%3Bthis.now %3F a(false%2C"CSS")%3A a(true%2C"JS")%3B%7D%2Capply%3A function()%7Bvar x%3Ddocument.querySelectorAll(".OJCscript%2C.OJCstyle").length%2Ca%3Ddocument.getElementById("outerJsTgt")%3Btry%7Bvar b%3Ddocument.getElementById("newScriptTag")%3Bb.removeAttribute("id")%3B%7Dcatch(e)%7B%7Dvar c%3Dthis.now %3F this.e("script")%3A this.e("style")%2Cd%3Da.value.replace(%2F%5E%5Cn%2B%7C%5Cn%2B%24%2Fg%2C"")%3Bc.innerHTML%3Dd%3Bdocument.head.appendChild(c)%3Bvar y%3Ddocument.querySelectorAll(".OJCscript%2C.OJCstyle").length%3Bif(x !%3D%3Dy)%7Bthis.alert(d)%3B%7D%7D%2Ccode%3A function()%7Bvar a%3Dthis.now %3F document.querySelectorAll(".OJCscript")%3A document.querySelectorAll(".OJCstyle")%3Bif(a.length%3D%3D0)%7Breturn this.alert("No code.")%3B%7Dvar b%3D""%3Bfor(var i%3D0%3Bi < a.length%3Bi%2B%2B)%7Bb %2B%3Da%5Bi%5D.innerHTML %2B "%5Cn"%3B%7Dreturn this.alert(b)%3B%7D%2Cclose%3A function()%7Bwindow.onerror%3D""%3Bvar a%3Ddocument.querySelectorAll(".OJCelm%2C%23baseStyleTag")%3Bfor(var i%3D0%3Bi < a.length%3Bi%2B%2B)%7Ba%5Bi%5D.parentNode.removeChild(a%5Bi%5D)%3B%7Dwindow.outerJsConsole%3D%7B%7D%3B%7D%2Cinit%3A function()%7Bwindow.onerror%3Dfunction(msg%2Curl%2Clinenumber)%7BouterJsConsole.alert(msg %2B "(at%3A " %2B linenumber %2B ")."%2Ctrue)%3Bvar a%3Ddocument.getElementById("newScriptTag")%3Ba.parentNode.removeChild(a)%3Breturn true%3B%7D%3Bthis.area()%3Bthis.now%3Dtrue%3B%7D%7D%3BouterJsConsole.init()%3Bvoid(0);
```
以上のコードをコピペして登録してください。~~コピペの際には周りの文章ごと選択してメモ帳などで余分な部分を消すと楽です。~~iPhoneから見るとコードが全部表示されてしまうようですね…。なんてこった…。


> ![q-1-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532858/2e1103ba-73ff-abab-f141-42dbf1ef136f.png)


ブックマークレットの登録方法は以下のページが詳しいですので、参考までに。
> ブックマークレットの登録方法:
> https://qiita.com/aqril_1132/items/b5f9040ccb8cbc705d04

#見た目/できること
> <img width="375" alt="q-1-2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532858/8afb71de-43c5-cbdd-11f7-7ba5d5e9d340.png"><img width="375" alt="q-1-3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532858/8cf4cd84-5d17-e194-105a-3326a7ee5ee5.png">
> 寿司

ブックマークレットを起動すると、上記のようなコンソールが現れます。

各ボタンの概要:

- 青いボタン
    - 「JS（JavaScript）」モードと「CSS」モードを切り替えます。
- apply
    - テキストエリア内のコードを適用します。
- code
    - これまでに適用したコードを全てリザルトに表示します。
- close
    - コンソールを閉じます。

## JS（JavaScript）モード
特に説明することは無いです。PC版Chromeコンソールのようにオブジェクトの中身を覗いたりといったことは出来ず、ただスクリプトをそのページに追加するだけです。

ただしそのスクリプトに問題があったときは、以下のような警告をリザルトに表示します。
> <img width="375" alt="q-1-3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532858/af8769de-d2c6-6b93-6942-2b85ccd1fa90.png">

この時、そのスクリプトの適用はされません。

> **Tips:** オブジェクトの中身を見たい場合は、alertやJSON.stringify()などを駆使すると良いです。
> 
```ruby:JavaScript
alert(JSON.stringify(<Object>));
```

## CSSモード
CSSを変更できます。シンプルですね。例えばqiita（このサイト）で使用する場合、

```Ruby:CSS
.it-MdContent {
    color: red;
}
```

このCSSを適用することで、記事の本文を真っ赤に染め上げることができます。

# 最後に
ブックマークレットに加工する前のコードを以下に載せます。

```Ruby:JavaScript
window.outerJsConsole = {
    e: function(v, f) {
        var a = document.createElement(v);
        if(v == "script") {
            a.setAttribute("type", "text/javascript");
            if(!f) {
                a.setAttribute("id", "newScriptTag");
            }
            a.classList.add("OJCscript");
        }else if(v == "style") {
            a.setAttribute("type", "text/css");
            f ? a.setAttribute("id", "baseStyleTag") : a.classList.add("OJCstyle");
        }else {
            a.classList.add("OJCelm");
        }
        return a;
    },
    alert: function(v, caution) {
        var a = new Date(),
            ah = a.getHours() < 10 ? "0" + a.getHours() : a.getHours(),
            am = a.getMinutes() < 10 ? "0" + a.getMinutes() : a.getMinutes(),
            as = a.getSeconds() < 10 ? "0" + a.getSeconds() : a.getSeconds(),
        b = this.now ? "(JS)" : "(CSS)",
        c = caution ? ` class="OJCcaution"` : "",
        d = `<table` + c + `><tbody><tr><td style="font-style: italic; width: 6em">` + ah + ":" + am + ":" + as + `<br><b style="color: #aaa">` + b + `</b></td><td><pre style="background: transparent;border: none;border-radius: 0;font-family: sans-serif">` + v + `</pre></td></tr></tbody></table>`,
        e = document.getElementById("OJCalert");
        e.innerHTML = d + e.innerHTML;
    },
    area: function() {
        var a = this.e("style", true),
        b = this.e("div");
        a.innerHTML = `
        #OJCconsole #OJCalert {
            font-family: sans-serif;
        }
        #OJCconsole #OJCalert {
            position: fixed;
            background: #f0f0f0;
            border: 1px solid #aaa;
            bottom: 30vh;
            font-size: 0.9em;
            left: 0;
            padding: 0.75em;
            height: 5rem;
            width: 100%;
            overflow-y: scroll;
                -webkit-overflow-scrolling: touch;
        }
        #OJCconsole #OJCalert table {
            border-bottom: 1px solid #aaa;
            display: block;
            padding: 0.5em 0;
        }
        #OJCconsole #OJCalert table.OJCcaution {
            background: #fff0f0;
        }
        #OJCconsole .OJCwrap {
            position: fixed;
            bottom: 0;
            left: 0;
            height: 30vh;
            width: 100%;
        }
        #OJCconsole .OJCwrap * {
            position: relative;
            appearance: none;
                -webkit-appearance: none;
                -moz-appearance: none;
        }
        #OJCconsole .OJCwrap textarea {
            border: 1px solid #aaa;
            border-radius: 0;
            font-size: 16px;
            transform: scale(0.8);
            transform-origin: top left;
            height: calc((100% - 4em) / 0.8);
            padding: 0.75em;
            width: calc(100% / 0.8);
                -webkit-overflow-scrolling: touch;
        }
        #OJCconsole .OJCwrap div {
            display: flex;
            height: 3em;
            justify-content: center;
        }
        #OJCconsole .OJCwrap div button {
            background: #b01;
            border: 1px solid #fff;
            color: #fff;
            display: block;
            flex-grow: 1;
            font-family: sans-serif;
            font-size: 15px;
            font-weight: bold;
            padding: 0.5em;
            width: 15%;
        }`;
        b.setAttribute("id", "OJCconsole");
        b.innerHTML = `<div id="OJCalert"></div><div class="OJCwrap"><div><button onclick="outerJsConsole.change(this);" style="background: #065fd4;">JS</button><button onclick="outerJsConsole.apply();">apply</button><button onclick="outerJsConsole.code();">code</button><button onclick="outerJsConsole.close();">close</button></div><textarea id="outerJsTgt"></textarea></div>`;
        document.head.appendChild(a);
        document.body.appendChild(b);
    },
    now: true,
    change: function(v) {
        var a = (b,c) => {this.now = b;v.innerHTML = c;};
        this.now ? a(false,"CSS") : a(true,"JS");
    },
    apply: function() {
        var x = document.querySelectorAll(".OJCscript, .OJCstyle").length,
        a = document.getElementById("outerJsTgt");
        try {
            var b = document.getElementById("newScriptTag");
            b.removeAttribute("id");
        }catch(e) {}
        var c = this.now ? this.e("script") : this.e("style"),
        d = a.value.replace(/^\n+|\n+$/g, "");
        c.innerHTML = d;
        document.head.appendChild(c);
        var y = document.querySelectorAll(".OJCscript, .OJCstyle").length;
        if(x !== y) {
            this.alert(d);
        }
    },
    code: function() {
        var a = this.now ? document.querySelectorAll(".OJCscript") : document.querySelectorAll(".OJCstyle");
        if(a.length == 0) {
            return this.alert("No code.");
        }
        var b = "";
        for(var i = 0; i < a.length; i++) {
            b += a[i].innerHTML + "\n";
        }
        return this.alert(b);
    },
    close: function() {
        window.onerror = "";
        var a = document.querySelectorAll(".OJCelm,#baseStyleTag");
        for(var i = 0; i < a.length; i++) {
            a[i].parentNode.removeChild(a[i]);
        }
        window.outerJsConsole = {};
    },
    init: function() {
        window.onerror = function(msg, url, linenumber) {
            outerJsConsole.alert(msg + " (at: " + linenumber + ").", true);
            var a = document.getElementById("newScriptTag");
            a.parentNode.removeChild(a);
            return true;
        };
        this.area();
        this.now = true;
    }
};
outerJsConsole.init();
```
リザルト用のtableに直接styleを付けたりしていて汚いですが、各サイト毎のCSSに対応する＆文字数削減のためのものですので、ご了承ください。