Created
July 16, 2026 19:31
-
-
Save SoumyaK4/527634f02694d3de9e73123f9c611726 to your computer and use it in GitHub Desktop.
Sensei's Library Tampermonkey Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Sensei's Library - SoumyaK4 Dark + Popup SGF | |
| // @namespace https://senseis.xmp.net/ | |
| // @version 0.5.8 | |
| // @description Clean dark theme for Sensei's Library with a compact BesoGo SGF popup. | |
| // @author SoumyaK4 | |
| // @license MIT | |
| // @match https://senseis.xmp.net/* | |
| // @match http://senseis.xmp.net/* | |
| // @include file://*/senseis-library/* | |
| // @require https://cdn.jsdelivr.net/gh/yewang/besogo@59cd44cc1a7ffd2e85bafb3634cdf7c874871b71/js/besogo.js | |
| // @require https://cdn.jsdelivr.net/gh/yewang/besogo@59cd44cc1a7ffd2e85bafb3634cdf7c874871b71/js/editor.js | |
| // @require https://cdn.jsdelivr.net/gh/yewang/besogo@59cd44cc1a7ffd2e85bafb3634cdf7c874871b71/js/gameRoot.js | |
| // @require https://cdn.jsdelivr.net/gh/yewang/besogo@59cd44cc1a7ffd2e85bafb3634cdf7c874871b71/js/svgUtil.js | |
| // @require https://cdn.jsdelivr.net/gh/yewang/besogo@59cd44cc1a7ffd2e85bafb3634cdf7c874871b71/js/parseSgf.js | |
| // @require https://cdn.jsdelivr.net/gh/yewang/besogo@59cd44cc1a7ffd2e85bafb3634cdf7c874871b71/js/loadSgf.js | |
| // @require https://cdn.jsdelivr.net/gh/yewang/besogo@59cd44cc1a7ffd2e85bafb3634cdf7c874871b71/js/boardDisplay.js | |
| // @require https://cdn.jsdelivr.net/gh/yewang/besogo@59cd44cc1a7ffd2e85bafb3634cdf7c874871b71/js/coord.js | |
| // @require https://cdn.jsdelivr.net/gh/yewang/besogo@59cd44cc1a7ffd2e85bafb3634cdf7c874871b71/js/commentPanel.js | |
| // @require https://cdn.jsdelivr.net/gh/yewang/besogo@59cd44cc1a7ffd2e85bafb3634cdf7c874871b71/js/treePanel.js | |
| // @resource besogoCSS https://yewang.github.io/besogo/css/besogo.css | |
| // @resource boardCSS https://yewang.github.io/besogo/css/board-kibitz.css | |
| // @grant GM_addStyle | |
| // @grant GM_getResourceText | |
| // @grant GM_xmlhttpRequest | |
| // @connect * | |
| // @run-at document-end | |
| // ==/UserScript== | |
| (() => { | |
| 'use strict'; | |
| const $ = (selector, root = document) => root.querySelector(selector); | |
| const $$ = (selector, root = document) => [...root.querySelectorAll(selector)]; | |
| const sgfCache = new Map(); | |
| let requestId = 0; | |
| const css = ` | |
| :root{color-scheme:dark;--bg:#090b0f;--panel:#0f141b;--panel2:#151b24;--text:#eef3f8;--muted:#c8d1dc;--faint:#8f9baa;--link:#f1cf88;--border:rgba(150,162,178,.18);--accent:rgba(216,177,95,.34);--bad:#ffd1db;--font:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;--mono:"JetBrains Mono",Consolas,monospace} | |
| *,*::before,*::after{box-sizing:border-box!important;letter-spacing:0!important} | |
| html{background:var(--bg)!important} | |
| html.sl-s4-popup-open{overflow:hidden!important} | |
| body,body#browse{margin:0!important;min-height:100vh!important;background:linear-gradient(#090b0f,#0b0e13 50%,#090b0f)!important;color:var(--muted)!important;font:15.5px/1.68 var(--font)!important;-webkit-font-smoothing:antialiased!important} | |
| body:has(#leftpane){display:grid!important;grid-template:"top top" auto "side main" 1fr/218px minmax(0,1fr)!important} | |
| body:not(:has(#leftpane)){display:grid!important;grid-template:"top" auto "main" 1fr/minmax(0,1fr)!important} | |
| .messagepage,.mainpage,.subpage,.discussionpage,#contentpane,#contentwrapper,#leftpane,.left-mainpage,.left-subpage,.left-discussionpage,.left-messagepage{background:transparent!important;background-image:none!important;color:var(--muted)!important} | |
| #contentwrapper{grid-area:main!important;width:100%!important;max-width:1100px!important;margin:0 auto!important;padding:32px 34px 64px!important;position:relative!important} | |
| #contentpane{position:static!important;float:none!important;width:auto!important;margin:0!important;padding:0!important;border:0!important} | |
| .contentsize{max-width:82ch!important;margin:0 auto!important} | |
| #toppane{grid-area:top!important;position:sticky!important;top:0!important;z-index:100000!important;display:flex!important;align-items:center!important;flex-wrap:wrap!important;gap:6px 8px!important;min-height:44px!important;padding:7px 14px!important;background:rgba(11,14,19,.96)!important;border-bottom:1px solid var(--border)!important;box-shadow:0 10px 28px rgba(0,0,0,.28)!important} | |
| #toolbox,#toolbox2,#toolbox3,#editmenu,#searchboxdiv,#searchboxdiv form{display:inline-flex!important;align-items:center!important;gap:6px!important;margin:0!important;border-left:0!important;border-right:0!important} | |
| #toolbox3{order:1!important}#editmenu{order:2!important}#toolbox2{order:3!important;margin-left:auto!important}#toolbox{order:4!important}#searchboxdiv{order:5!important;margin-left:8px!important} | |
| #toppane ul,#toppane li{list-style:none!important;margin:0!important;padding:0!important} | |
| #toolbox a,#editmenu a,.toolmenu>span,#searchboxdiv button,button,input[type=submit],input[type=button]{display:inline-flex!important;align-items:center!important;justify-content:center!important;min-height:30px!important;padding:0 10px!important;background:#12171f!important;border:1px solid var(--border)!important;border-radius:8px!important;color:var(--muted)!important;box-shadow:none!important;font:800 12.5px/1 var(--font)!important;text-decoration:none!important;cursor:pointer!important} | |
| #toolbox a:hover,#editmenu a:hover,.toolmenu:not(#toolbox3):not(#toolbox2):hover>span,.toolmenu.sl-s4-open:not(#toolbox3):not(#toolbox2)>span,#searchboxdiv button:hover,button:hover,input[type=submit]:hover,input[type=button]:hover{background:#18202b!important;border-color:var(--accent)!important;color:var(--link)!important} | |
| #toolbox2>span,#toolbox3>span{background:#12171f!important;border-color:var(--border)!important;color:var(--text)!important} | |
| #toolbox2:hover>span,#toolbox2>span:hover,#toolbox3:hover>span,#toolbox3>span:hover{background:#12171f!important;border-color:var(--border)!important;color:var(--text)!important} | |
| #toolbox a:hover{background:#12171f!important;border-color:var(--border)!important;color:var(--muted)!important} | |
| #toolbox3>span::after{content:"Home";margin-left:8px;color:var(--faint);font-size:11px} | |
| .toolmenu{position:relative!important;display:inline-block!important;background:transparent!important;border-left:0!important;border-right:0!important} | |
| .toolmenu:hover,.toolmenu.over,.toolmenu.sl-s4-open{background:transparent!important;border-left:0!important;border-right:0!important} | |
| .toolmenu ul{display:none!important;position:absolute!important;top:100%!important;left:0!important;z-index:2147483000!important;min-width:220px!important;margin:0!important;padding:8px!important;background:#0d1117!important;border:1px solid var(--border)!important;border-radius:8px!important;box-shadow:0 18px 40px rgba(0,0,0,.45)!important} | |
| .toolmenu:hover ul,.toolmenu.sl-s4-open ul{display:block!important} | |
| .toolmenu ul li.separator{border-top:1px solid var(--border)!important;margin-top:6px!important;padding-top:6px!important} | |
| .toolmenu ul a{display:block!important;width:100%!important;padding:8px 10px!important;background:transparent!important;border:0!important;border-radius:6px!important;color:var(--muted)!important;text-align:left!important;white-space:nowrap!important} | |
| .toolmenu ul a:hover{background:#171d26!important;color:var(--link)!important} | |
| #searchboxdiv form{padding:2px!important;background:#0c1016!important;border:1px solid var(--border)!important;border-radius:9px!important} | |
| #searchbox,#searchboxdiv input[name=searchstring],input[type=text],input[type=password],textarea,select{min-height:30px!important;padding:5px 10px!important;background:#090c11!important;border:1px solid var(--border)!important;border-radius:7px!important;color:var(--text)!important;font:600 13px/1.3 var(--font)!important;outline:none!important} | |
| #searchbox{width:170px!important;border-color:transparent!important} | |
| #searchbox:focus,input:focus,textarea:focus,select:focus{border-color:var(--accent)!important} | |
| #searchboxdiv input[type=image]{display:none!important} | |
| #searchboxdiv button.sl-s4-search{background:transparent!important;border-color:transparent!important;color:var(--faint)!important} | |
| #leftpane{grid-area:side!important;width:218px!important;min-width:218px!important;margin:0!important;padding:18px 12px!important;background:#0b0f14!important;border-right:1px solid var(--border)!important;text-align:left!important} | |
| #leftpane img{display:block!important;max-width:62px!important;height:auto!important;margin:0 auto 12px!important;filter:none!important} | |
| #bookmarks,#related{margin:0 0 6px!important;padding:5px!important;background:var(--panel)!important;border:1px solid var(--border)!important;border-radius:8px!important;line-height:1!important} | |
| #related p{margin:0 0 4px!important}#related p:last-child{margin-bottom:0!important} | |
| #leftpane br{display:none!important} | |
| #leftpane strong,#related b{display:block!important;margin:2px 0 4px!important;padding:0 0 4px!important;border-bottom:1px solid var(--border)!important;color:var(--faint)!important;font-size:10.5px!important;font-weight:900!important;text-transform:uppercase!important} | |
| #leftpane a,#bookmarks a,#related a{display:block!important;margin:0!important;padding:3px 7px!important;background:transparent!important;border:1px solid transparent!important;border-radius:6px!important;color:var(--muted)!important;font-size:12px!important;font-weight:700!important;line-height:1.05!important;text-decoration:none!important;overflow-wrap:anywhere!important} | |
| #bookmarks a{background:#121820!important;border-color:rgba(150,162,178,.12)!important} | |
| #related a{color:#aeb9c7!important} | |
| #leftpane a:hover,#bookmarks a:hover,#related a:hover{padding-left:9px!important;background:#171e28!important;border-color:rgba(216,177,95,.26)!important;color:var(--link)!important} | |
| h1,h2,h3,h4,h1#pagetitle{color:var(--text)!important;line-height:1.22!important} | |
| h1{font-size:2.3rem!important}h2{margin:42px 0 18px!important;padding-bottom:10px!important;border-bottom:1px solid var(--border)!important;font-size:1.48rem!important}h3{margin:30px 0 12px!important;font-size:1.18rem!important}h4{font-size:1rem!important} | |
| h1#pagetitle{margin:4px 0 38px!important;text-align:center!important}h1#pagetitle img{max-width:100%!important;height:auto!important} | |
| p{margin:0 0 1.05em!important;color:var(--muted)!important}b,strong{color:var(--text)!important}small,.path,#pageheaders,#pgfooter{color:var(--faint)!important} | |
| a[href]{color:var(--link)!important;text-decoration:none!important;border-bottom:1px solid rgba(216,177,95,.28)!important}a[href]:visited{color:#c9b1ff!important}a[href]:hover{color:#fff0c6!important;border-bottom-color:var(--link)!important} | |
| .contentsize>ul,.contentsize>ol,.contentsize dl,blockquote,#toc,#subs,table.wiki,.contentsize>table,.contentsize table[border],#pgfooter,#footer,#pagefooter,div.sidebarleft,div.sidebarright,div.inlineimg,fieldset,.postingheader,.postingbody,.postingfooter,.postinginfo,div.relatedchanges,table.white,table.threadlist,table.diff,.warning{background:var(--panel)!important;background-image:none!important;border:1px solid var(--border)!important;border-radius:8px!important;box-shadow:none!important;color:var(--muted)!important;padding:12px!important} | |
| .contentsize>ul,.contentsize>ol{padding:20px 26px 20px 44px!important;margin:24px 0!important}.contentsize dl{padding:20px 24px!important;margin:24px 0!important}.contentsize dd{margin-left:0!important} | |
| table,tbody,thead,tfoot,tr,td,th,.odd,.even,table.wiki tr.odd,table.wiki tr.even{background:transparent!important;background-image:none!important;color:var(--muted)!important;border-color:var(--border)!important} | |
| table.wiki th,table.wiki tr:first-child td,.contentsize table tr:first-child td,#toc td,#subs td{background:var(--panel2)!important;color:var(--text)!important} | |
| td,th{padding:.55em!important}hr{border:0!important;border-top:1px solid var(--border)!important} | |
| a.forum,a.forum2,span.forumlocked{display:inline-flex!important;align-items:center!important;min-height:28px!important;padding:4px 8px!important;background:var(--panel2)!important;border:1px solid var(--border)!important;border-radius:6px!important;color:var(--link)!important;font:800 13px/1 var(--font)!important;text-decoration:none!important} | |
| a.forum:hover,a.forum2:hover{background:#18202b!important;border-color:var(--accent)!important;color:#fff0c6!important} | |
| pre,code{background:#070a0e!important;border:1px solid var(--border)!important;border-radius:7px!important;color:#dce9ff!important;font-family:var(--mono)!important}pre{padding:16px 18px!important;overflow:auto!important} | |
| .delete{background:rgba(255,113,139,.16)!important;color:var(--bad)!important}.added{background:rgba(111,213,157,.14)!important;color:#c9f3d8!important} | |
| #contentpane [bgcolor],#contentpane [style*="background-color"],#contentpane [style*="background:"]{background-color:var(--panel)!important;background-image:none!important;color:var(--muted)!important} | |
| div.diagram{display:inline-block!important;margin:8px 8px 8px 0!important;padding:7px!important;background:var(--panel2)!important;border:1px solid var(--border)!important;border-radius:8px!important;box-shadow:none!important} | |
| div.diagram:hover{border-color:var(--accent)!important} | |
| div.diagram a.sl-s4-diagram{position:relative!important;display:inline-block!important;border:0!important} | |
| div.diagram a.sl-s4-diagram::after{content:"Open SGF";position:absolute;right:8px;bottom:8px;padding:3px 8px;background:rgba(7,10,15,.84);border:1px solid var(--accent);border-radius:999px;color:var(--link);font-size:11px;font-weight:900;opacity:0;transform:translateY(3px);transition:.16s;pointer-events:none} | |
| div.diagram:hover a.sl-s4-diagram::after,div.diagram a.sl-s4-diagram:focus::after{opacity:1;transform:none} | |
| a.sl-s4-sgf:not(.sl-s4-diagram)::after{content:"SGF";margin-left:.45em;padding:1px 7px;background:rgba(216,177,95,.11);border:1px solid var(--accent);border-radius:999px;color:var(--link);font-size:.66em;font-weight:900} | |
| img.right{float:right!important;max-width:min(320px,42vw)!important;height:auto!important;margin:0 0 22px 28px!important;border:1px solid var(--border)!important;border-radius:8px!important} | |
| #sl-s4-sgf-overlay{position:fixed!important;inset:0!important;z-index:2147483647!important} | |
| #sl-s4-sgf-backdrop{position:absolute!important;inset:0!important;background:rgba(0,0,0,.8)!important;backdrop-filter:blur(7px)!important} | |
| #sl-s4-sgf-modal{position:absolute!important;top:50%!important;left:50%!important;width:min(980px,calc(100vw - 56px))!important;height:min(720px,calc(100vh - 56px))!important;transform:translate(-50%,-50%)!important;display:flex!important;flex-direction:column!important;overflow:hidden!important;background:#0a0d12!important;border:1px solid rgba(150,162,178,.22)!important;border-radius:12px!important;box-shadow:0 28px 90px rgba(0,0,0,.72)!important;color:var(--text)!important} | |
| #sl-s4-sgf-header{display:flex!important;align-items:center!important;justify-content:space-between!important;flex-wrap:wrap!important;gap:10px 14px!important;padding:10px 12px!important;background:var(--panel)!important;border-bottom:1px solid var(--border)!important} | |
| #sl-s4-sgf-title{display:flex!important;align-items:center!important;gap:10px!important;min-width:240px!important;flex:1 1 auto!important} | |
| #sl-s4-sgf-title b{display:block!important;max-width:min(56vw,760px)!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;font-size:15px!important} | |
| #sl-s4-sgf-title span{display:inline-flex!important;align-items:center!important;justify-content:center!important;width:36px!important;height:36px!important;background:var(--panel2)!important;border:1px solid var(--accent)!important;border-radius:8px!important;color:var(--link)!important;font-size:12px!important;font-weight:900!important} | |
| #sl-s4-sgf-title a{border:0!important;color:var(--faint)!important;font-size:12px!important;font-weight:800!important} | |
| #sl-s4-sgf-actions{display:flex!important;align-items:center!important;justify-content:flex-end!important;gap:6px!important} | |
| #sl-s4-sgf-actions button{display:inline-flex!important;align-items:center!important;justify-content:center!important;height:32px!important;min-height:32px!important;padding:0 10px!important;background:#131922!important;border:1px solid var(--border)!important;border-radius:7px!important;color:var(--muted)!important;font:800 12px/1 var(--font)!important} | |
| #sl-s4-sgf-actions button:hover{background:#1a222d!important;border-color:var(--accent)!important;color:var(--link)!important} | |
| #sl-s4-sgf-actions button:disabled{opacity:.42!important;pointer-events:none!important} | |
| #sl-s4-sgf-close{border-color:rgba(255,113,139,.32)!important;background:rgba(255,113,139,.08)!important;color:var(--bad)!important} | |
| #sl-s4-sgf-body{flex:1!important;display:flex!important;align-items:center!important;justify-content:center!important;min-height:0!important;padding:10px!important;overflow:hidden!important;background:#080b10!important} | |
| .sl-s4-status{display:flex!important;align-items:center!important;justify-content:center!important;flex-direction:column!important;gap:8px!important;min-height:min(62vh,520px)!important;padding:24px!important;background:#0d1218!important;border:1px solid var(--border)!important;border-radius:8px!important;text-align:center!important} | |
| .sl-s4-status b{font-size:15px!important}.sl-s4-status span{max-width:min(760px,80vw)!important;color:var(--faint)!important;font-size:12px!important;font-weight:700!important;overflow-wrap:anywhere!important} | |
| #sl-s4-sgf-body .besogo-editor{width:100%!important;height:100%!important;max-width:100%!important;margin:auto!important;background:#0d1218!important;border:1px solid var(--accent)!important;border-radius:8px!important;overflow:auto!important} | |
| #sl-s4-sgf-body .besogo-board,#sl-s4-sgf-body .besogo-panels{max-height:100%!important} | |
| #sl-s4-sgf-body .besogo-board,#sl-s4-sgf-body svg{background:#e2bf67!important} | |
| #sl-s4-sgf-body .besogo-panels,#sl-s4-sgf-body .besogo-panel,#sl-s4-sgf-body .besogo-comment,#sl-s4-sgf-body .besogo-tree{background:var(--panel)!important;color:var(--muted)!important;border-color:var(--border)!important} | |
| #sl-s4-sgf-body .besogo-editor input{min-height:28px!important;margin:2px!important;padding:4px 8px!important;background:var(--panel2)!important;border:1px solid var(--border)!important;border-radius:6px!important;color:var(--text)!important;font-size:13px!important;font-weight:700!important;box-shadow:none!important} | |
| #sl-s4-sgf-body .besogo-comment>input[value="Comment"],#sl-s4-sgf-body .besogo-comment>input[value="Edit Info"],#sl-s4-sgf-body .besogo-comment>table.besogo-gameInfo:nth-of-type(2),#sl-s4-sgf-body .besogo-comment>textarea{display:none!important} | |
| #sl-s4-sgf-body .besogo-comment>table.besogo-gameInfo:first-of-type{display:table!important} | |
| #sl-s4-sgf-body .besogo-board svg *{color:initial!important} | |
| @media (max-width:1050px){body:has(#leftpane){grid-template:"top" auto "main" auto "side" auto/1fr!important}#contentwrapper{max-width:100%!important;padding:28px 18px 56px!important}.contentsize{max-width:100%!important}#leftpane{width:auto!important;min-width:0!important;border-top:1px solid var(--border)!important;border-right:0!important}img.right{float:none!important;display:block!important;max-width:100%!important;margin:18px auto!important}} | |
| @media (max-width:760px){body,body#browse{font-size:16px!important}#toppane{padding:8px 10px!important}#searchbox{width:132px!important}#sl-s4-sgf-modal{width:calc(100vw - 12px)!important;height:calc(100vh - 12px)!important}#sl-s4-sgf-title b{max-width:72vw!important}#sl-s4-sgf-actions{width:100%!important;justify-content:stretch!important}#sl-s4-sgf-actions button{flex:1 1 0!important;padding:0 6px!important}.contentsize>ul,.contentsize>ol{padding:18px 18px 18px 38px!important}} | |
| `; | |
| const html = (value) => String(value).replace(/[&"<>]/g, (char) => ({ | |
| '&': '&', '"': '"', '<': '<', '>': '>', | |
| })[char]); | |
| const addStyle = (text) => { | |
| if (!text) return; | |
| GM_addStyle(text); | |
| }; | |
| const hrefOf = (anchor) => new URL(anchor.getAttribute('href'), location.href).href; | |
| const isSgf = (anchor) => { | |
| const href = anchor?.getAttribute('href'); | |
| if (!href || href === '#') return false; | |
| try { | |
| const url = new URL(href, location.href); | |
| return /\.sgf$/i.test(url.pathname) || /\.sgf(?:[?#]|$)/i.test(url.href); | |
| } catch { | |
| return /\.sgf(?:[?#]|$)/i.test(href); | |
| } | |
| }; | |
| const gmText = (url) => { | |
| const ok = (response) => (response.status >= 200 && response.status < 300) | |
| || (response.status === 0 && response.responseText); | |
| return new Promise((resolve, reject) => GM_xmlhttpRequest({ | |
| method: 'GET', | |
| url, | |
| timeout: 20000, | |
| headers: { Accept: 'application/x-go-sgf,text/plain,*/*' }, | |
| onload: (response) => (ok(response) ? resolve(response.responseText) : reject(new Error(`HTTP ${response.status}`))), | |
| onerror: () => reject(new Error('Network request failed')), | |
| ontimeout: () => reject(new Error('Network request timed out')), | |
| })); | |
| }; | |
| const validateSgf = (text, url) => { | |
| const sgf = String(text || '').trim(); | |
| if (!/\(\s*;/.test(sgf.slice(0, 1200))) { | |
| throw new Error(`Response did not look like SGF (${new URL(url, location.href).hostname || 'local file'})`); | |
| } | |
| return sgf; | |
| }; | |
| const fetchSgf = async (url) => { | |
| if (!sgfCache.has(url)) { | |
| sgfCache.set(url, gmText(url).then((text) => validateSgf(text, url)).catch((error) => { | |
| sgfCache.delete(url); | |
| throw error; | |
| })); | |
| } | |
| return sgfCache.get(url); | |
| }; | |
| const sgfTitle = (anchor, url) => { | |
| const file = new URL(url, location.href).pathname.split('/').filter(Boolean).pop() || 'diagram.sgf'; | |
| return anchor?.closest('div.diagram')?.querySelector('p')?.textContent?.trim() | |
| || anchor?.textContent?.trim() | |
| || file; | |
| }; | |
| const pageSgfs = () => $$('a[href].sl-s4-sgf').filter((anchor) => !anchor.closest('#sl-s4-sgf-overlay') && isSgf(anchor)); | |
| const closePopup = () => { | |
| $('#sl-s4-sgf-overlay')?.remove(); | |
| document.documentElement.classList.remove('sl-s4-popup-open'); | |
| }; | |
| const setStatus = (body, title, detail = '') => { | |
| body.innerHTML = `<div class="sl-s4-status"><b>${html(title)}</b>${detail ? `<span>${html(detail)}</span>` : ''}</div>`; | |
| }; | |
| const createEditor = (body, sgf) => { | |
| if (!window.besogo?.create) throw new Error('BesoGo did not load'); | |
| body.textContent = ''; | |
| const editor = Object.assign(document.createElement('div'), { | |
| className: 'besogo-editor', | |
| textContent: sgf, | |
| }); | |
| editor.tabIndex = 0; | |
| body.append(editor); | |
| window.besogo.create(editor, { | |
| resize: 'fixed', | |
| panels: ['comment', 'tree'], | |
| tool: 'navOnly', | |
| coord: 'western', | |
| variants: 1, | |
| }); | |
| requestAnimationFrame(() => { | |
| editor.focus(); | |
| window.dispatchEvent(new Event('resize')); | |
| }); | |
| }; | |
| const openPopup = async (anchorOrUrl) => { | |
| closePopup(); | |
| const anchor = typeof anchorOrUrl === 'string' ? null : anchorOrUrl; | |
| const url = typeof anchorOrUrl === 'string' ? anchorOrUrl : hrefOf(anchor); | |
| const title = sgfTitle(anchor, url); | |
| const links = pageSgfs(); | |
| const index = anchor ? links.indexOf(anchor) : -1; | |
| const id = ++requestId; | |
| document.body.insertAdjacentHTML('beforeend', ` | |
| <div id="sl-s4-sgf-overlay"> | |
| <div id="sl-s4-sgf-backdrop"></div> | |
| <section id="sl-s4-sgf-modal" role="dialog" aria-modal="true" aria-label="SGF viewer"> | |
| <header id="sl-s4-sgf-header"> | |
| <div id="sl-s4-sgf-title"><span>SGF</span><div><b>${html(title)}</b><a href="${html(url)}" target="_blank" rel="noopener noreferrer">Open original</a></div></div> | |
| <div id="sl-s4-sgf-actions"> | |
| <button type="button" data-action="prev"${index <= 0 ? ' disabled' : ''}>Prev</button> | |
| <button type="button" data-action="next"${index < 0 || index >= links.length - 1 ? ' disabled' : ''}>Next</button> | |
| <button id="sl-s4-sgf-close" type="button" data-action="close">Close</button> | |
| </div> | |
| </header> | |
| <div id="sl-s4-sgf-body"></div> | |
| </section> | |
| </div>`); | |
| document.documentElement.classList.add('sl-s4-popup-open'); | |
| $('#sl-s4-sgf-backdrop').addEventListener('click', closePopup); | |
| $('#sl-s4-sgf-actions').addEventListener('click', (event) => { | |
| const action = event.target.closest('button')?.dataset.action; | |
| if (action === 'close') closePopup(); | |
| if (action === 'prev' || action === 'next') openPopup(links[index + (action === 'next' ? 1 : -1)]); | |
| }); | |
| const body = $('#sl-s4-sgf-body'); | |
| setStatus(body, 'Loading SGF...', new URL(url, location.href).pathname); | |
| try { | |
| const sgf = await fetchSgf(url); | |
| if (id !== requestId || !$('#sl-s4-sgf-overlay')) return; | |
| requestAnimationFrame(() => createEditor(body, sgf)); | |
| } catch (error) { | |
| if ($('#sl-s4-sgf-overlay')) { | |
| setStatus(body, 'Could not load this SGF in the popup.', `${error.message}. Use "Open original" above.`); | |
| } | |
| console.warn('[SL Theme] SGF load failed:', error); | |
| } | |
| }; | |
| const markSgfLinks = (root = document) => { | |
| const links = root.matches?.('a[href]') ? [root] : $$('a[href]', root); | |
| links.filter(isSgf).forEach((anchor) => { | |
| anchor.classList.add('sl-s4-sgf'); | |
| anchor.title = 'Open SGF in popup viewer'; | |
| if (anchor.closest('div.diagram') && $('img', anchor)) anchor.classList.add('sl-s4-diagram'); | |
| }); | |
| }; | |
| const normalizeChrome = () => { | |
| $('#partner')?.remove(); | |
| const pane = $('#toppane'); | |
| if (pane) { | |
| [...pane.childNodes].forEach((node) => { | |
| if (node.nodeType === Node.TEXT_NODE && /^[\s|·•¦]+$/.test(node.textContent)) node.remove(); | |
| }); | |
| } | |
| const search = $('#searchboxdiv form'); | |
| if (search && !search.dataset.slS4Text) { | |
| search.dataset.slS4Text = '1'; | |
| $$('input[type=image]', search).forEach((input) => { | |
| const button = Object.assign(document.createElement('button'), { | |
| type: 'submit', | |
| className: 'sl-s4-search', | |
| name: input.name, | |
| value: input.value || input.name || '1', | |
| title: input.title, | |
| textContent: input.name === 'pagesearch' ? 'In titles' : 'Search', | |
| }); | |
| input.replaceWith(button); | |
| }); | |
| } | |
| const home = $('#toolbox3 > span'); | |
| if (home && !home.dataset.slS4Home) { | |
| home.dataset.slS4Home = '1'; | |
| home.role = 'link'; | |
| home.tabIndex = 0; | |
| home.title = 'Go to Sensei\'s Library home'; | |
| const goHome = (event) => { | |
| event.preventDefault(); | |
| event.stopImmediatePropagation(); | |
| location.assign(location.protocol === 'file:' ? ($('#leftpane > a[href]')?.href || location.href) : new URL('/', location.href).href); | |
| }; | |
| home.addEventListener('click', goHome, true); | |
| home.addEventListener('keydown', (event) => (event.key === 'Enter' || event.key === ' ') && goHome(event), true); | |
| } | |
| $$('.toolmenu > span').forEach((trigger) => { | |
| if (trigger.dataset.slS4Menu || trigger === home) return; | |
| trigger.dataset.slS4Menu = '1'; | |
| trigger.tabIndex = 0; | |
| trigger.role = 'button'; | |
| trigger.addEventListener('click', (event) => { | |
| event.preventDefault(); | |
| event.stopPropagation(); | |
| const menu = trigger.closest('.toolmenu'); | |
| $$('.toolmenu.sl-s4-open').forEach((item) => item !== menu && item.classList.remove('sl-s4-open')); | |
| menu.classList.toggle('sl-s4-open'); | |
| }); | |
| }); | |
| }; | |
| addStyle(GM_getResourceText('besogoCSS')); | |
| addStyle(GM_getResourceText('boardCSS')); | |
| addStyle(css); | |
| normalizeChrome(); | |
| markSgfLinks(); | |
| document.addEventListener('click', (event) => { | |
| const target = event.target instanceof Element ? event.target : event.target.parentElement; | |
| const sgf = target?.closest('a[href]'); | |
| if (sgf && !sgf.closest('#sl-s4-sgf-overlay') && isSgf(sgf) && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey && event.button === 0) { | |
| event.preventDefault(); | |
| openPopup(sgf); | |
| return; | |
| } | |
| if (!target?.closest('.toolmenu')) $$('.toolmenu.sl-s4-open').forEach((menu) => menu.classList.remove('sl-s4-open')); | |
| }); | |
| document.addEventListener('keydown', (event) => { | |
| if (event.key === 'Escape') closePopup(); | |
| }); | |
| new MutationObserver((mutations) => { | |
| mutations.forEach(({ addedNodes }) => addedNodes.forEach((node) => { | |
| if (node.nodeType === Node.ELEMENT_NODE) { | |
| normalizeChrome(); | |
| markSgfLinks(node); | |
| } | |
| })); | |
| }).observe(document.body, { childList: true, subtree: true }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment