MediaWiki:ClamComponent.js

From BetaMC Wiki
Revision as of 11:38, 8 October 2026 by RobertWesner (talk | contribs)
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
 * Reusable XSS-safe html-builder DSL.
 * Taken from Roberts userscripts so we can avoid insecure innerHtml nonsense.
 *
 * Example:
 *  $builder('div')
 *      .id('foo')
 *      .className('a b c')
 *      .onBuildText('<b>Test</b>')
 *      .build()
 *
 * Result:
 *  ```
 *  <div id="foo" class="a b c">
 *      &lt;b&gt;Test&lt;/b&gt;
 *  </div>;
 *  ```
 *
 * @license MIT
 * @author Robert Wesner
 * @see https://github.com/RobertWesner/YouTube-Play-All/blob/main/script.user.js
 */
const { $builder, $style } = (()=>{const $builder=t=>(t=>{const e=t.match(/^[a-zA-Z0-9-]+/);if(!e)throw"Invalid tag supplied to parseQuery.";const n=e[0],r=(t=>{let e=[];const getOperations=()=>{const t=[...e.flat()];return e=[],t},n=new Proxy(t,{get(r,a,s){const P=t=>(...e)=>(t(...e),n),PBO=t=>P(((...n)=>e.push((()=>t(...n)))));switch(a){case"build":return()=>(getOperations().forEach((e=>e(t))),t);case"buildWithSync":return async()=>{for(const e of getOperations()){const n=e(t);n&&"function"==typeof n.then&&await n}return t};case"addClass":return P((e=>t.classList.add(e)));case"onBuild":return P((t=>e.push(t)));case"onBuildAppend":return PBO(((...e)=>t.append(...e)));case"onBuildText":return PBO((e=>t.textContent=e));case"on":return P(((e,n)=>t.addEventListener(e,n)))}const c=["hidden","style"];return e=>(!c.includes(a)&&a in t?t[a]=e:t.setAttribute(a.replaceAll("_","-"),e),n)}});return n})(document.createElement(n));if(!t.match(/^[a-zA-Z0-9-]+$/)){const e=(t=>t.match(/^[a-zA-Z0-9-]+((?:[#.][a-zA-Z0-9-]+)+)?((?:\[[a-zA-Z0-9-]+(?:=".*?")?])+)?/))(t),n=e[1]??"",a=e[2]??"";n.matchAll(/([#.])([a-zA-Z0-9-]+)/g).forEach((([t,e,n])=>{({"#":r.id,".":r.addClass})[e](n)})),a.matchAll(/\[([a-zA-Z0-9-]+)(?:="(.*?)")?]/g).forEach((([t,e,n])=>{r[e](n)}))}return r})(t);return{$builder,$style:(t,e)=>document.head.insertAdjacentElement("beforeend",$builder(`style#${t}`).onBuild((t=>t.textContent=e)).build())}})();

$style('clam-components', `
    .clam-component---online-badge {
        border: 1px solid #2c303624;
        background-color: white;
        color: white;
        padding: 0.12em 0.4em;
        border-radius: 0.32em;
        font-weight: bold;
    
        &[data-online="true"]::after {
            color: #11cc0e;
            content: '● online';
        }
        &[data-online="false"]::after {
            color: #cc140e;
            content: '● offline';
        }
    }
`);

(async () => {
    const userPathMatch = location.pathname.match(/\/index.php\/User:([^\/]+)/);

    if (userPathMatch) {
        const stats = await statsClient.player(userPathMatch[1]);

        document.querySelectorAll('br.cc.-online').forEach(element => element.replaceWith(
            $builder('span.clam-component---online-badge')
                .data_online(stats.online)
                .build(),
        ));
    }
})();