Colorize replaced/ing substrings in Markov Algorithm Online's history.
// ==UserScript==
// @name MAO_Colorizer
// @namespace https://satanic0258.github.io/
// @version 1.0.2
// @description Colorize replaced/ing substrings in Markov Algorithm Online's history.
// @author satanic0258
// @include https://mao.snuke.org/tasks/*
// @grant none
// @copyright 2021, satanic0258 (https://satanic0258.github.io/)
// @license MIT License; https://opensource.org/licenses/MIT
// ==/UserScript==
/*jshint esversion: 6 */
$(function() {
'use strict';
// 履歴テキストをパース
function parseHistoryText(line) {
const l = line.indexOf(':');
return [line.substring(0, l), line.substring(l + 2)];
}
// 履歴オブジェクトをパース
function parseHistoryObj(history) {
let ary = Array(history.length);
for(let i = 0; i < history.length; ++i) {
ary[i] = parseHistoryText(history[i].innerText);
}
return ary;
}
// lineに適用される規則の[ruleIdx, pos]を返す (適用できる規則が無いとき [])
function applyRulePos(line) {
for(let ri = 0; ri < rules.length; ++ri) {
const pos = line.indexOf(rules[ri].pat.replace(/ /g, '␣'));
if(pos == -1) continue;
return [ri, pos];
}
return [];
}
// textarea上の欄を更新
function update_player_s(line) {
let s = document.getElementById('player_s');
s.value = parseHistoryText(line)[1];
}
// 履歴のli要素を作る
function createLi(text) {
let newLi = document.createElement('li');
newLi.innerHTML = text;
newLi.onclick = function(){update_player_s(newLi.innerText);return false;};
newLi.style = 'white-space:nowrap;';
return newLi;
}
// 行の色付け情報 (0番目bit:置換元, 1番目bit:置換先)
let colorAry = [];
// 色付けの初期化
function colorize_reset() {
let select = document.getElementById('player_history');
let selectAry = select.querySelectorAll('option');
colorAry = [Array(parseHistoryText(selectAry[0].innerHTML)[1].length)];
colorAry[0].fill(0);
colorAry[0].push(-1);
let history = document.getElementById('player_history_list');
while (history.firstChild) history.removeChild(history.firstChild);
history.appendChild(createLi(select.innerText));
}
// 最後2行の色付け
function colorize_last2() {
let optionAry = document.getElementById('player_history').querySelectorAll('option');
let optionLast2 = Array.prototype.slice.call(optionAry).slice(-2);
let sepHistory = parseHistoryObj(optionLast2);
let oi = colorAry.length - 1;
let v = Array(sepHistory[1][1].length);
v.fill(0);
v.push(-1);
colorAry.push(v);
{ // 色付け位置の確定
let pa = applyRulePos(sepHistory[0][1]);
if(pa.length != 2) return;
const ri = pa[0];
const l = pa[1];
// 置換元
for(let i = l; i < l + rules[ri].pat.length; ++i) colorAry[oi + 0][i] |= 1;
// 置換先
let repSz = rules[ri].rep.length;
if(repSz > 0 && rules[ri].rep[0] == ':') --repSz;
for(let i = l; i < l + repSz; ++i) colorAry[oi + 1][i] |= 2;
}
let history = document.getElementById('player_history_list');
let historyAry = history.querySelectorAll('li');
// 色付けの実行
for(let _ = 0; _ < 2; ++_, ++oi) {
let line = sepHistory[_][0] + ': ';
const rhs = sepHistory[_][1];
let sub = '';
for(let i = 0; i < rhs.length; ++i) {
sub += E(rhs[i]);
if(colorAry[oi][i] != colorAry[oi][i + 1]) {
if(colorAry[oi][i] == 1) sub = '<span class="MAO_Colorizer_from">' + sub + '</span>'
else if(colorAry[oi][i] == 2) sub = '<span class="MAO_Colorizer_to">' + sub + '</span>'
else if(colorAry[oi][i] == 3) sub = '<span class="MAO_Colorizer_from MAO_Colorizer_to">' + sub + '</span>'
line += sub;
sub = '';
}
}
if(_ == 0) historyAry[oi].innerHTML = line;
else history.appendChild(createLi(line));
}
}
{ // historyの置き換え
let select = document.getElementById('player_history');
select.style = "display: none;"; // 元のhistoryを非表示
let history = document.createElement('ul');
history.id = "player_history_list";
history.className = "form-control profont";
history.style = "height: 320px; margin-bottom: 5px; overflow: scroll; list-style: none;";
select.parentNode.insertBefore(history, select); // 代わりにリストを置く
colorize_reset();
{ // historyの変更検知
const observer = new MutationObserver( function(mutations) {
let options = mutations[0].target.querySelectorAll('option');
if(colorAry.length < options.length) colorize_last2();
else colorize_reset();
history.scrollTop = history.scrollHeight;
});
const config = {childList: true};
observer.observe(select, config);
}
}
{ // スタイル設定
let css = document.createElement('style');
let ruleText = '.MAO_Colorizer_from{background-color:#c6e3ff;color:black;}';
ruleText += '.MAO_Colorizer_to{font-weight:bold;color:red;}';
ruleText += 'ul#player_history_list>li:active{background-color:#c6e99d;}';
let rule = document.createTextNode(ruleText);
css.media = 'screen';
css.type = 'text/css';
if (css.styleSheet) {
css.styleSheet.cssText = rule.nodeValue;
} else {
css.appendChild(rule);
};
document.getElementsByTagName('head')[0].appendChild(css);
}
});