Chat Control Panel

Adds a small panel to the top of the chat window with a list of macros, allowing you to fire chat macros with mouse actions.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name           Chat Control Panel
// @namespace      kol.interface.unfinished
// @description    Adds a small panel to the top of the chat window with a list of macros, allowing you to fire chat macros with mouse actions.
// @include        https://*kingdomofloathing.com/lchat.php*
// @include        https://*kingdomofloathing.com/mchat.php*
// @grant          GM_getValue
// @grant          GM_setValue
// @version        2.021
// ==/UserScript==

//Version 2.021
// - trying to correct some spacing issues
//Version 2.02
// - change to https
//Version 2.01
// - add @grant, convert GM_log to console.log
//Version 2.0
// - modified heavily to work with the new tabbed chat.  This 
//   changes the way the docking works, and there is now an
//   icon on the top right corner that toggles the appearance
//   of the panel instead of the bar on the right.
//Version 1.0.3
// - more minor interface changes
//   - resizing is now granular, always shrinking or growing by 
//     a full entry.
//   - the toggle bar now stays on the right of the chat control
//     panel so you can quickly toggle on/off without moving
//     the mouse.
//   - the control panel sticks to the top when moved very close
//     to the top of the chat pane.
//Version 1.0.2
// - a minor aesthetic change, using partial transparency to 
//   hide slightly less of the underlying chap panel text.
//Version 1.0.1
// - fix mouse-over message for resize dragging
//Version 1.0
// - changed the docking method, so you now click on a bar 
//   on the right to dock it on the right
// - added a pull bar next to the docking bar so you can 
//   slide it anywhere in the chat pane
// - the former docking tab (line) at the bottom is now a 
//   pull tab so you can resize it to as many entries as 
//   you want (or can fit).
//Version 0.9

var height=unitHeight(5);
var ccmds;
var playername;

var dims;

function rollHandler() {
    var cp = document.getElementById('chatcp');
    var cph = document.getElementById('chatcp_handle');
    if (cp && cph) {
        var v = cp.getAttribute('style');
        var w = cph.getAttribute('style');
        if (v.match(/display\s*:\s*none/)) {
            cp.setAttribute('style',removeStyle(v,'display'));
            cph.setAttribute('style',removeStyle(w,'display'));
            saveDock(false);
        } else {
            cp.setAttribute('style','display:none;'+v);
            cph.setAttribute('style','display:none;'+w);
            saveDock(true);
        }

    }
}


function replaceOrAddStyle(s,a,v) {
    var r = new RegExp(a+'\\s*:','i');
    var sa = s.split(';');
    for (var i=0;i<sa.length;i++) {
        var x = sa[i].match(r);
        if (x) {
            sa[i] = a+':'+v;
            return sa.join(';');
        }
    }
    return a+':'+v+';'+s;
}

function removeStyle(s,a) {
    var r = new RegExp(a+'\\s*:','i');
    var sa = s.split(';');
    for (var i=0;i<sa.length;i++) {
        var x = sa[i].match(r);
        if (x) {
            sa[i] = '';
            return sa.join(';').replace(';;',';');
        }
    }
    return s;
}

function getStyleAttr(s,a) {
    var r = new RegExp('(^|;\\s*)'+a+'\\s*:\\s*([0-9]+)','i');
    var x = s.match(r);
    if (x)
        return Number(x[2]);
    //console.log('could not find style: "' +a+'" in "'+s+'"');
    return -1;
}

function setTopBorder(s) {
    return replaceOrAddStyle(s,'border-top','1px solid black');
}

function removeTopBorder(s) {
    return removeStyle(s,'border-top');
}

function setWidths(e) {
    getMaxDimensions();
    var cp = document.getElementById('chatcp');
    var cph = document.getElementById('chatcp_handle');
    var cpd = document.getElementById('chatcp_dock');
    if (cp && cph && cpd) {
        var w = (dims.headerw) ? dims.headerw : dims.width; //document.body.clientWidth-19;
        //console.log("Setting width to "+w);
        cph.setAttribute('style',cph.getAttribute('style').replace(/width\s*:\s*[0-9;px]*/,'width:'+(w+1)+'px;'));
        cp.setAttribute('style',cp.getAttribute('style').replace(/width\s*:\s*[0-9;px]*/,'width:'+(w+1)+'px;'));
        if (dims.headerw) 
            cpd.setAttribute('style',cpd.getAttribute('style').replace(/left\s*:\s*[0-9;px]*/,'left:'+(dims.headerw-15)+'px;'));
        else
            cpd.setAttribute('style',cpd.getAttribute('style').replace(/left\s*:\s*[0-9;px]*/,'left:'+(w-15)+'px;'));
        var texts = document.getElementsByClassName('chatcp_text');
        for (var i=0;i<texts.length;i++) {
            texts[i].setAttribute('style',texts[i].getAttribute('style').replace(/width\s*:\s*[0-9;px]*/,'width:'+(w-56)+'px;'));
        }
    }
}

function tHandler(e) {
    var ci = this.getAttribute('cmdidx');
    ccmds[ci] = this.value;
    saveTable(ccmds);
    if (e.keyCode==13)
        insertCmd(ccmds[ci]);
}


function insertCmd(c) {
    if (c)
        unsafeWindow.submitchat(c);
}

function bHandler() {
    insertCmd(ccmds[this.getAttribute('cmdidx')]);
}


function addPanel() {
    height = restoreHeight();
    ccmds = restoreTable();
    var f = document.getElementById('ChatWindow');
    var cp = document.getElementById('chatcp');
    if (f && !cp) {
        placeDockIcon(f);
        cp = document.createElement('div');
        var w = (dims.headerw) ? dims.headerw : dims.width;//document.body.clientWidth-19;
        var p = restorePos();
        cp.setAttribute('style','height:'+height+'px;width:'+w+'px;'+((p>0)?'border-top:1px solid black;':'')+'background-image:-moz-linear-gradient(center top , white 50%, transparent 100%);z-index:7;font-size:12px;position:absolute;font-family:arial;left:2px;top:'+p+'px;');
        cp.setAttribute('id','chatcp');

        var t = document.createElement('table');
        t.setAttribute('cellspacing','0');
        for(var i=0;unitHeight(i)<height;i++) {
            newEntry(i,w-40,t);
        }

        var tt = document.createElement('table');
        tt.setAttribute('style','vertical-align:top;');
        var tr = document.createElement('tr');
        var td = document.createElement('td');
        tt.setAttribute('cellspacing','0');
        tt.setAttribute('cellpadding','0');
        td.setAttribute('style','vertical-align:top;');
        td.appendChild(t);
        tr.appendChild(td);
        td = document.createElement('td');
        var span = document.createElement('div');
        span.setAttribute('style','background:lightgray;height:'+(height-4)+'px;width:15px;position:relative;top:2px;');
        span.setAttribute('title','Click and drag to position the chat control panel.');
        span.setAttribute('class','chatcpbar');
        td.appendChild(span);
        tr.appendChild(td);
        tt.appendChild(tr);

        cp.appendChild(tt);

        var handle = document.createElement('div');
        handle.setAttribute('style','height:7px;width:'+(w+1)+'px;text-align:center;vertical-align:middle;border-bottom:1px solid black;background-color:transparent;z-index:8;font-size:12px;position:absolute;font-family:arial;left:2px;top:'+(p+height-3)+'px;');
        handle.setAttribute('id','chatcp_handle');
        handle.setAttribute('title','Click and drag to resize the chat control panel.');
        var a = document.createElement('img');
        a.setAttribute('src',"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%1E%00%00%00%05%08%02%00%00%00%DDC%CB%AD%00%00%00%01sRGB%00%AE%CE%1C%E9%00%00%00%09pHYs%00%00%0B%13%00%00%0B%13%01%00%9A%9C%18%00%00%00%07tIME%07%DA%0C%17%002%18%89S%DB%E9%00%00%00%19tEXtComment%00Created%20with%20GIMPW%81%0E%17%00%00%00%16IDAT%18%D3c%60%A0%19%60%FC%FF%FF%3F%C3%90%03C%D3%D5%00W%E9%05%FD%13%F9%A0%92%00%00%00%00IEND%AEB%60%82");
        handle.appendChild(a);

        //handle.addEventListener('click',rollHandler,false);
        handle.addEventListener('mousedown',dragStartSize,false);
        cp.addEventListener('mousedown',dragStart,true);
        
        f.parentNode.insertBefore(cp,f);
        f.parentNode.insertBefore(handle,f);

        //cp.appendChild(handle);
        window.addEventListener('resize',setWidths,false);

        if (restoreDock())
            rollHandler();
        setWidths();
    }
}

function unitHeight(u) {
    return u*27+5;
}

function heightUnit(h) {
    return Math.floor((h-5)/27);
}

function saveTable(t) {
    var pn = getPlayerNameFromCharpane();
    if (pn) {
        GM_setValue(pn+'_cmds',t.join('#####'));
    }
}

function saveHeight(h) {
    height = h;
    var pn = getPlayerNameFromCharpane();
    if (pn) {
        GM_setValue(pn+'_cpheight',h);
    }
}

function restoreHeight() {
    var pn = getPlayerNameFromCharpane();
    if (pn) {
        return Math.ceil(Number(GM_getValue(pn+'_cpheight',unitHeight(5))));
    }
    return Math.ceil(unitHeight(5));
}

function saveDock(b) {
    var pn = getPlayerNameFromCharpane();
    if (pn) {
        GM_setValue(pn+'_docked',b);
    }
}

function restoreDock() {
    var pn = getPlayerNameFromCharpane();
    if (pn) {
        return Boolean(GM_getValue(pn+'_docked',false));
    }
}

function restoreTable() {
    var pn = getPlayerNameFromCharpane();
    if (pn) {
        var t = GM_getValue(pn+'_cmds','');
        if (t) {
            t = t.split('#####');
            return t;
        }
    }
    return ['Good morning clan-mates!', '/unequip all', '', '', ''];
}


function savePos(y) {
    var pn = getPlayerNameFromCharpane();
    if (pn) {
        GM_setValue(pn+'_pos',y);
    }
}

function restorePos() {
    var pn = getPlayerNameFromCharpane();
    if (pn) {
        var t = Number(GM_getValue(pn+'_pos',2));
        return t;
    }
    return 2;
}


// utility to get player name; hacked/stolen from Antimarty's fortune cookie script
function getPlayerNameFromCharpane() {
    var somef=window.parent.frames;
    var goo;
    for(var j=0;j<somef.length;j++) {
        if (somef[j].name=="charpane") {
            goo=somef[j];
            var username = goo.document.getElementsByTagName("b");
            if (!username || username.length < 1) return playername;
            username = username[0];
            if (!username) return playername;
            username = username.firstChild;
            if (!username) return playername;
            // in full mode the link is <a><b>Name</b></a>
            // in compact mode it's <b><a>Name</a></b>
            // so have to handle this, and also can use it to tell
            // whether it's in compact mode or not.
            var fullmode = true;
            while (username && username.nodeType == 1)
                {
                    username = username.firstChild;
                    fullmode = false;
                }
            if (!username) return playername;
            username = username.nodeValue;
            if (!username) return playername;
            username = username.toLowerCase();
            playername = username;
            return username;
        }
    }
}


// code to drag the panel around
var d;
function dragStart(event, id) {
    var cp = document.getElementById('chatcp');
    var cph = document.getElementById('chatcp_handle');
    if (cp && cph) {
        var v = getStyleAttr(cp.getAttribute('style'),'width');
        if (v>=0) {
            if (event.clientX<v-50)
                return;
        }

        d = new Object();
        d.cp = cp;
        d.cph = cph;
        var x = event.clientX + window.scrollX;
        var y = event.clientY + window.scrollY;

        // Save starting positions of cursor and element.
        d.cursorStartX = x;
        d.cursorStartY = y;
        d.elStartLeft = parseInt(cp.style.left, 10);
        d.elStartTop = parseInt(cp.style.top, 10);

        d.baseH = getStyleAttr(cp.getAttribute('style'),'height');

        if (isNaN(d.elStartLeft)) d.elStartLeft = 2;
        if (isNaN(d.elStartTop)) d.elStartTop = 2;

        document.addEventListener("mousemove", dragGo, true);
        document.addEventListener("mouseup", dragStop, true);
        event.preventDefault();
    }
}

// drag handler for positioning the panel
function dragGo(event)  {
    var x, y;
    // Get cursor position with respect to the page.
    y = event.clientY + window.scrollY;

    //d.elNode.style.left = (d.elStartLeft + x - d.cursorStartX) + "px";
    if ((d.elStartTop + y - d.cursorStartY)>=(dims.headerw? (dims.headert+16) : (dims.top+16)) && (d.elStartTop + y - d.cursorStartY)<(dims.height+(dims.headerw? (dims.headert+16) : (dims.top+16)))-height) {
        if ((d.elStartTop + y - d.cursorStartY)<=3)
            y = d.cursorStartY - d.elStartTop;
        d.cp.style.top = (d.elStartTop + y - d.cursorStartY) + "px";
        if ((d.elStartTop + y - d.cursorStartY)>0)
            d.cp.setAttribute('style',setTopBorder(d.cp.getAttribute('style')));
        else
            d.cp.setAttribute('style',removeTopBorder(d.cp.getAttribute('style')));
        d.cph.style.top = (d.elStartTop + y - d.cursorStartY + d.baseH - 3) + "px";
        savePos(d.elStartTop + y - d.cursorStartY);
    }
    event.preventDefault();
}

// terminate dragging to position the panel
function dragStop(event)  {
    // Stop capturing mousemove and mouseup events.
    document.removeEventListener("mousemove", dragGo, true);
    document.removeEventListener("mouseup", dragStop, true);
    d = null;
}


// code to resize the panel
function dragStartSize(event, id) {
    var cp = document.getElementById('chatcp');
    var cph = document.getElementById('chatcp_handle');
    if (cp && cph) {
        d = new Object();
        d.cp = cp;
        d.cph = cph;
        var y = event.clientY + window.scrollY;
        d.baseH = getStyleAttr(cp.getAttribute('style'),'height');
        d.baseO = getStyleAttr(cph.getAttribute('style'),'top');

        // Save starting positions of cursor and element.
        d.cursorStartY = y;
        d.elStartLeft = parseInt(cp.style.left, 10);
        d.elStartTop = parseInt(cp.style.top, 10);

        if (isNaN(d.elStartLeft)) d.elStartLeft = 2;
        if (isNaN(d.elStartTop)) d.elStartTop = 2;

        document.addEventListener("mousemove", dragGoSize, true);
        document.addEventListener("mouseup", dragStopSize, true);
        event.preventDefault();
    }
}

// create cp index i in table t with width w
function newEntry(i,w,t) {
    var tr = document.createElement('tr');
    tr.setAttribute('id','chatcp_index'+i);
    var td = document.createElement('td');
    var i1 = document.createElement('input');
    i1.setAttribute('type','button');
    //i1.setAttribute('value','>');
    //var arrow = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAMCAYAAACEJVa/AAAAmklEQVQokaXSPQ5BURCG4YcNiGhUGpah0CpFQyOswUpElCLR24VFiJ8FqCQ6Ea7CkdwILseXvN3Mm5nJkJ0mFih8UftRcsIO9XdFuQyghBUSDJB/lky+YIwZrkE0RTEt2fzAOqyVYItyjCTNMcga/0guWKIm2GKYpW/SyaCLNkbhsAf0RaSFM/aoxAiG7uPPY5ofqaLnxYM9cgOSr1GycHbzaQAAAABJRU5ErkJggg==';
    //i1.setAttribute('style','font-size:9px;width:22px;');
    i1.setAttribute('style','background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAMCAYAAACEJVa/AAAAmklEQVQokaXSPQ5BURCG4YcNiGhUGpah0CpFQyOswUpElCLR24VFiJ8FqCQ6Ea7CkdwILseXvN3Mm5nJkJ0mFih8UftRcsIO9XdFuQyghBUSDJB/lky+YIwZrkE0RTEt2fzAOqyVYItyjCTNMcga/0guWKIm2GKYpW/SyaCLNkbhsAf0RaSFM/aoxAiG7uPPY5ofqaLnxYM9cgOSr1GycHbzaQAAAABJRU5ErkJggg==");background-repeat: no-repeat;background-position: center;');
    i1.setAttribute('cmdidx',i);
    i1.addEventListener('mouseup',bHandler,false);
    var i2 = document.createElement('input');
    i2.setAttribute('type','text');
    i2.setAttribute('width','128');
    i2.setAttribute('value',((i>=ccmds.length)?'':ccmds[i]));
    i2.setAttribute('class','chatcp_text');
    i2.setAttribute('style','font-size:9px;width:'+(w-22)+'px;height:13px;');
    i2.setAttribute('cmdidx',i);
    i2.addEventListener('keyup',tHandler,true);
    i2.addEventListener('change',tHandler,true);
    td.appendChild(i1);
    td.appendChild(i2);
    tr.appendChild(td);
    t.appendChild(tr);
}

// update the interior of the panel during resizing
function updateCPSize(h) {
    var bars = document.getElementsByClassName('chatcpbar');
    for (var i=0;i<bars.length;i++) {
        var s = bars[i].getAttribute('style');
        bars[i].setAttribute('style',replaceOrAddStyle(s,'height',(h-4)+'px'));
    }
    var last = heightUnit(h);
    var ins = document.getElementById('chatcp_index'+last);
    if (!ins) {
        // we must have an entry 0
        var w = document.getElementById('chatcp_index0');
        if (w) {
            var t = w.parentNode;
            w = getStyleAttr(w.firstChild.firstChild.nextSibling.getAttribute('style'),'width');
            for (var i=1;i<last;i++) {
                ins = document.getElementById('chatcp_index'+i);
                if (!ins) {
                    newEntry(i,(w+16),t);
                }
            }
        }
    } else {
        var del = document.getElementById('chatcp_index'+last);
        while (del) {
            del.parentNode.removeChild(del);
            last++;
            del = document.getElementById('chatcp_index'+last);
        }
    }
    saveHeight(h);
}

// drag handler for resizing the panel
function dragGoSize(event)  {
    var y;
    // Get cursor position with respect to the page.
    y = event.clientY + window.scrollY;

    var delta = y - d.cursorStartY;
    if (delta!=0) {
        var newh = d.baseH + delta;
        if (newh>unitHeight(1) && newh<(dims.height+(dims.headerw? (dims.headert+16) : (dims.top+16)))) {
            newh = unitHeight(heightUnit(newh));
            delta = newh - d.baseH;
            d.cp.setAttribute('style',replaceOrAddStyle(d.cp.getAttribute('style'),'height',newh+'px'));
            d.cph.setAttribute('style',replaceOrAddStyle(d.cph.getAttribute('style'),'top',(d.baseO+delta)+'px'));
            updateCPSize(newh);
        }
    }
    event.preventDefault();
}

// terminate dragging to resize the panel
function dragStopSize(event)  {
    // Stop capturing mousemove and mouseup events.
    document.removeEventListener("mousemove", dragGoSize, true);
    document.removeEventListener("mouseup", dragStopSize, true);
    d = null;
}

function getMaxDimensions() {
    var chats = document.getElementsByClassName('chatdisplay');
    for (var i=0;i<chats.length;i++) {
        if (chats[i].style.display!='none') {
            var header = document.evaluate('.//div[@class="header"]',chats[i],null,XPathResult.FIRST_ORDERED_NODE_TYPE,null);
            dims = {top:Number(chats[i].style.top.replace(/[^0-9]+/,'')),width:Number(chats[i].clientWidth),height:Number(chats[i].style.height.replace(/[^0-9]+/,''))};
            if (header.singleNodeValue) {
                dims.headert = Number(header.singleNodeValue.style.top.replace(/[^0-9]+/,''));
                dims.headerw = Number(header.singleNodeValue.style.width.replace(/[^0-9]+/,''));
            }
            //console.log('dimensions: top='+dims.top+', width='+dims.width+', height='+dims.height+', offw='+chats[i].offsetWidth+', cw='+chats[i].clientWidth);
			chats[i].addEventListener('scroll',setWidths,false);
            return;
        }
    }
}

function placeDockIcon(f) {
    var img=document.createElement('img');
    img.setAttribute('src',"data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%0E%00%00%00%0C%08%06%00%00%00R%80%8C%DA%00%00%00%01sRGB%00%AE%CE%1C%E9%00%00%00%06bKGD%00%00%00%00%00%00%F9C%BB%7F%00%00%00%09pHYs%00%00%0B%13%00%00%0B%13%01%00%9A%9C%18%00%00%00%07tIME%07%DC%03%1E%14)%06%E8D%1D%FA%00%00%00%19tEXtComment%00Created%20with%20GIMPW%81%0E%17%00%00%00%5BIDAT(%CF%ED%8F%A1%11%C0%20%10%04%97L%8Ay%85%A0%8F%EF%80b%9E%8EPt%81%A7%10%FC%23%A228TD%D6%9C%DA%99%DB%E0%EE%CE%017%40%CE%99Z%2BsNJ)%00%98%19%00)%A5M%EA%BD%3Fb%8C%91%D6%1A%00%AA%FA%DA1%06%22%B2%C9%E1%F4%EA%C5!%7F%E3%A7%1A%17%CC%8BJ%7F%13%D8%1D%F7%00%00%00%00IEND%AEB%60%82");
    if (dims.headerw) 
        img.setAttribute('style','position:absolute;top:'+(dims.headert+1)+'px;left:'+(dims.headerw-15)+'px;z-index:7;');
    else
        img.setAttribute('style','position:absolute;top:'+(dims.top+2)+'px;left:'+(dims.width-15)+'px;z-index:7;');
    img.setAttribute('title','Click to toggle display of chat control panel.');
    img.setAttribute('id','chatcp_dock');
    img.addEventListener('click',rollHandler,false);
    f.parentNode.insertBefore(img,f);
}


function initialize() {
    getMaxDimensions();
    if (dims) {
        addPanel();
    }
}

setTimeout(initialize,50);