您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Improvements of dragongoserver.net: conditional moves, grey skin, keyboard shortcuts.
- // ==UserScript==
- // @name DGS Utilities
- // @description Improvements of dragongoserver.net: conditional moves, grey skin, keyboard shortcuts.
- // @author TPReal
- // @namespace https://gf.qytechs.cn/users/9113
- // @version 0.4.8
- // @match *://www.dragongoserver.net/*
- // @run-at document-start
- // @grant none
- // ==/UserScript==
- /* jshint ignore:start */
- (()=>{
- 'use strict';
- const EXTRA_CSS=`\
- table.MessageForm textarea[name='message'] {
- height: 2.5em;
- }
- div#condMoves {
- margin: 1em;
- }
- div#condMoves input {
- width: 35em;
- margin-left: 0.5em;
- }
- div#condMoves > * {
- vertical-align: baseline;
- }
- span.game-info-char {
- font-size: 0.8em;
- }
- div#loadingInfo {
- position: fixed;
- bottom: 0;
- left: 0;
- background: #0c41c9;
- color: #fffc70;
- margin: 8px;
- padding: 0 0.3em;
- display: none;
- }
- table#PoolViewerTable td.Matrix > a[title~="[Timeout]"]::after {
- content: "T";
- font-size: 0.5em;
- }
- `;
- const SKIN_GREY=`\
- body {
- background: #f8f8f8;
- font-family: Georgia, Times, Times New Roman, serif;
- }
- table#pageHead, table#pageFoot {
- border: solid 1px #707070;
- background: #d0d0d0;
- color: #202020;
- margin-top: -2px;
- }
- table#pageHead a, table#pageFoot a {
- color: #202020;
- }
- table#pageMenu {
- background: #f8f8f8;
- border-color: #707070;
- }
- span.MainMenuCount {
- color: #a0a0a0;
- }
- table.GameInfos tr {
- background: #eeeeee;
- }
- table.Links a {
- color: #202020;
- }
- a[target] {
- color: initial;
- }
- a {
- color: initial;
- }
- table.Goban img.brdl, table.Goban img.brdn, table.Goban img[src='images/blank.gif'] {
- filter: grayscale() brightness(102%);
- }
- td.Logo1 img[alt='Dragon'] {
- filter: grayscale() brightness(101%);
- }
- img[alt='Dragon Go Server'] {
- filter: grayscale() brightness(68.5%) contrast(300%);
- }
- img[alt='Samuraj Logo'] {
- filter: grayscale() brightness(97%);
- margin-top: -10px;
- }
- img[alt='Rating graph'] {
- filter: grayscale();
- }
- table.Table tr.Row2, table.Table tr.Row4 {
- background: #f4f4f4;
- }
- table.Table tr.Row1 td.RemTimeWarn2, table.Table tr.Row2 td.RemTimeWarn2,
- table.Table tr.Row3 td.RemTimeWarn2, table.Table tr.Row4 td.RemTimeWarn2 {
- background: initial;
- }
- table.Table tr.Row1 td.RemTimeWarn1, table.Table tr.Row2 td.RemTimeWarn1,
- table.Table tr.Row3 td.RemTimeWarn1, table.Table tr.Row4 td.RemTimeWarn1 {
- background: #fa96a6;
- }
- h3.Header, h3.Header .Rating, h3.Header .User {
- color: initial;
- }
- table.Infos, table.InfoBox, table.MessageBox {
- border-collapse: collapse;
- border-color: #707070;
- }
- table.Infos td, table.InfoBox td, table.MessageBox td, table.Infos th, table.Infos td {
- border-color: #707070;
- }
- img[alt='RSS'] {
- display: none;
- }
- font[color] {
- color: initial;
- }
- table.Table td.Sgf a {
- color: initial;
- }
- td.ServerHome {
- color: rgba(0,0,0,0);
- }
- td.ServerHome select, td.ServerHome input {
- display: none;
- }
- table#PoolViewerTable tr.Empty, table#PoolViewerTable tr.Title {
- background: initial;
- }
- dl.ExtraInfos dd.Guidance, dl.ExtraInfos dd.Info {
- color: initial;
- }
- table.Infos th {
- color: initial;
- }
- table.GameNotes th {
- background: #d0d0d0;
- color: initial;
- }
- div#loadingInfo {
- background: #d0d0d0;
- color: #202020;
- border: solid 1px #707070;
- }
- `;
- const RELOAD_INTERVAL=10*60*1000;
- class LoadingIndicator{
- constructor(){
- this.counter_=0;
- this.element_=null;
- }
- static create(){
- return new LoadingIndicator();
- }
- init(){
- this.element_=document.createElement("div");
- this.element_.id="loadingInfo";
- this.element_.title="Working...";
- this.element_.innerHTML="...";
- document.body.appendChild(this.element_);
- this.setVisibility_();
- }
- registerPromise(loadingPromise){
- this.counter_++;
- this.setVisibility_();
- loadingPromise.catch(()=>null).then(()=>{
- setTimeout(()=>{
- this.counter_--;
- this.setVisibility_();
- },10);
- });
- return loadingPromise;
- }
- registerStart(){
- let done;
- this.registerPromise(new Promise(success=>{
- done=()=>success(null);
- }));
- return done;
- }
- setVisibility_(){
- if(this.element_)
- this.element_.style.display=this.counter_?"block":null;
- }
- }
- const LOADING_INDICATOR=LoadingIndicator.create();
- async function ajax(path,params={},init={}){
- const searchParams=new URLSearchParams();
- for(const param of Object.keys(params))
- searchParams.set(param,params[param]);
- const paramsStr=searchParams.toString();
- const done=LOADING_INDICATOR.registerStart();
- try{
- const response=await fetch(
- `${path}${paramsStr?`?${paramsStr}`:``}`,
- Object.assign({credentials:"include"},init));
- return response.ok?response.text():Promise.reject(response);
- }finally{
- done();
- }
- }
- class ParseError extends Error{}
- const coord={
- SGFPattern:"[a-s]{2}",
- fromSGF(str){
- if(!str.match(/^[a-s]{2}$/))
- throw new Error(`Bad SGF coordinates format: ${str}`);
- const x=str.charCodeAt(0)-96;
- const y=str.charCodeAt(1)-96;
- return `${String.fromCharCode((x>=9?x+1:x)+96)}${20-y}`;
- },
- validate(str){
- if(!str.match(/^[a-hj-t]([1-9]|1[0-9])$/))
- throw new ParseError(`Bad move coordinates: ${str||"(empty)"}`);
- },
- toSGF(str){
- this.validate(str);
- let xc=str.charCodeAt(0)-96;
- const x=xc>=9?xc-1:xc;
- const y=20-parseInt(str.substr(1),10);
- return String.fromCharCode(x+96,y+96);
- },
- };
- class CondMoves{
- constructor(gameId,atMoveNo,tree={}){
- this.gameId=gameId;
- this.atMoveNo=atMoveNo;
- this.tree=tree;
- }
- static parseUserString(gameId,atMoveNo,str){
- const condMoves=new CondMoves(gameId,atMoveNo);
- for(const path of str.trim().split(/\s*[,;\n]\s*/)){
- if(!path)
- continue;
- const moves=path.split(/\s+/);
- for(const move of moves)
- coord.validate(move);
- if(moves.length%2!==0)
- throw new ParseError(`Path with odd length (no response specified for opponent's last move): ${moves.join(" ")}`);
- let cm=condMoves;
- for(let i=0;i<moves.length;i+=2){
- const branch=cm.getBranch_(moves[i],moves[i+1]);
- cm=branch.condMoves;
- }
- }
- return condMoves;
- }
- getBranch_(move,response){
- let branch=this.tree[move];
- if(branch&&branch.response!==response)
- throw new ParseError(`Inconsistent response to ${move} in different branches: ${branch.response} vs ${response}`);
- if(!branch){
- branch={response,condMoves:new CondMoves(this.gameId,this.atMoveNo+2)};
- this.tree[move]=branch;
- }
- return branch;
- }
- hasMoves(){
- return !!Object.keys(this.tree).length;
- }
- toUserString(){
- return this.toUserStringHelper_().map(path=>path.join(" ")).join(", ");
- }
- toUserStringHelper_(){
- const paths=[];
- if(this.hasMoves()){
- for(const move of Object.keys(this.tree)){
- const branch=this.tree[move];
- for(const path of branch.condMoves.toUserStringHelper_())
- paths.push([move,branch.response,...path]);
- }
- }else
- paths.push([]);
- return paths;
- }
- toString(){
- return `CondMoves[${this.gameId}@${this.atMoveNo}: ${this.toUserString()}]`;
- }
- serialise(){
- return `@${this.atMoveNo} ${this.toUserString()}`;
- }
- static deserialise(gameId,str){
- const mat=str.match(/^\s*@(\d+) (.*)$/);
- if(!mat)
- throw new Error(`Bad serialised conditional moves: ${str}`);
- return CondMoves.parseUserString(gameId,parseInt(mat[1],10),mat[2]);
- }
- }
- /*
- class Storage{
- constructor(base,fieldsWithDefaults,serialiser=JSON){
- const serialise=(serialiser.serialise||serialiser.stringify).bind(serialiser);
- for(const field of Object.keys(fieldsWithDefaults)){
- const defVal=fieldsWithDefaults[field];
- Object.defineProperty(this,field,{
- get:()=>{
- const str=base.getItem(field);
- if(str===null)
- return defVal;
- return serialiser.parse(str);
- },
- set:v=>{
- base.setItem(field,serialise(v));
- },
- });
- }
- }
- }
- const LOCAL_STORAGE=new Storage(localStorage,{});
- */
- class PrivateNotes{
- constructor(gameId,base,condMoves){
- this.gameId=gameId;
- this.base=base;
- this.condMoves=condMoves;
- }
- static parse(gameId,text){
- const mat=text.match(/^([\s\S]*?)(?:(?:^|\n)Conditional moves: (.+)\n*)?$/);
- let base=mat[1];
- let condMoves=null;
- if(mat[2])
- try{
- condMoves=CondMoves.deserialise(gameId,mat[2]);
- if(!(condMoves instanceof CondMoves))
- throw new Error(`Expected CondMoves, got ${condMoves}`);
- }catch(e){
- console.warn(`Cannot parse conditional moves from private notes: ${mat[2]}, error: ${e}`);
- base=mat[0];
- condMoves=null;
- }
- base=base.trim();
- if(base)
- base+="\n";
- return new PrivateNotes(gameId,base,condMoves);
- }
- static empty(gameId){
- return new PrivateNotes(gameId,"",null);
- }
- async saveCondMoves(condMoves){
- this.condMoves=condMoves;
- let text=this.base;
- if(condMoves&&condMoves.hasMoves())
- text+=`\n\n\nConditional moves: ${condMoves.serialise()}\n`;
- await ajax("/quick_do.php",{
- obj:"game",
- cmd:"save_notes",
- gid:this.gameId,
- notes:text,
- });
- ajax("/quick_do.php",{
- obj:"game",
- cmd:"hide_notes",
- gid:this.gameId,
- });
- }
- toString(){
- return `PrivateNotes[condMoves=${this.condMoves}]`;
- }
- }
- class GameSGF{
- constructor(gameId,moveNo,lastMove,privateNotes,hasExtraComments){
- this.gameId=gameId;
- this.moveNo=moveNo;
- this.lastMove=lastMove;
- this.privateNotes=privateNotes;
- this.hasExtraComments=hasExtraComments;
- }
- get condMoves(){
- return this.privateNotes&&this.privateNotes.condMoves;
- }
- static parse(gameId,sgf){
- const nodes=sgf.split("\n;").slice(1);
- const firstNodeTags=GameSGF.parseTags_(nodes[0]);
- const moveNo=firstNodeTags.XM?parseInt(firstNodeTags.XM,10):null;
- const lastNodeTags=GameSGF.parseTags_(nodes[nodes.length-1]);
- const lastMoveSGF=lastNodeTags.B||lastNodeTags.W;
- const lastMove=lastMoveSGF?coord.fromSGF(lastMoveSGF):null;
- const lastMoveComment=lastNodeTags.C;
- let privateNotes=null;
- let hasExtraComments=false;
- if(lastMoveComment){
- const nMat=lastMoveComment.match(/(?:^|\n)Notes - .+? \(.+?\):\n([\s\S]*)/);
- if(nMat){
- privateNotes=PrivateNotes.parse(gameId,nMat[1]);
- hasExtraComments=nMat[0].trim()!==lastMoveComment.trim();
- }else
- hasExtraComments=true;
- }
- return new GameSGF(gameId,moveNo,lastMove,privateNotes,hasExtraComments);
- }
- static async load(gameId,allowCache=false){
- return GameSGF.parse(gameId,await ajax("/sgf.php",{
- gid:gameId,
- owned_comments:1,
- quick_mode:1,
- no_cache:allowCache?0:1,
- }));
- }
- static parseTags_(node){
- const regexp=/([A-Z]{1,2})\[/g;
- const result={};
- for(;;){
- const mat=regexp.exec(node);
- if(!mat)
- break;
- let value="";
- let i=regexp.lastIndex;
- for(;;){
- if(i>=node.length)
- break;
- if(node[i]==="]"&&!(i+1<node.length&&node[i+1]==="["))
- break;
- if(node[i]==="\\"&&i+1<node.length&&(node[i+1]==="\\"||node[i+1]==="["))
- value+=node[++i];
- else
- value+=node[i];
- i++;
- }
- result[mat[1]]=value;
- regexp.lastIndex=i;
- }
- return result;
- }
- async executeCondMoves(){
- if(!this.condMoves||!this.moveNo||!this.lastMove)
- return {};
- const {clearCondMoves,condMoves:newCondMoves,response}=this.analyseCondMoves_();
- let condMovesToSave=clearCondMoves?null:newCondMoves;
- let promise;
- if(response){
- console.debug(`Responding in game ${this.gameId} to ${this.lastMove} with ${response}`);
- promise=ajax("/quick_do.php",{
- obj:"game",
- cmd:"move",
- gid:this.gameId,
- move_id:this.moveNo,
- move:coord.toSGF(response),
- },{method:"POST"}).then(()=>({responded:true}));
- promise.catch(error=>{
- console.warn(`Responding in game ${this.gameId} at move ${this.moveNo} with ${response} failed: ${error}`);
- condMovesToSave=null;
- });
- }else
- promise=Promise.resolve({});
- promise.catch(()=>null).then(()=>this.saveCondMoves(condMovesToSave));
- return promise;
- }
- analyseCondMoves_(){
- if(this.hasExtraComments){
- console.debug(`In game ${this.gameId} additional information is associated with last move; clearing conditional moves`);
- return {clearCondMoves:true};
- }
- if(this.moveNo!==this.condMoves.atMoveNo){
- console.debug(`In game ${this.gameId} current move is ${this.moveNo}, but conditional moves defined for move ${this.condMoves.atMoveNo}; clearing conditional moves`);
- return {clearCondMoves:true};
- }
- const branch=this.condMoves.tree[this.lastMove];
- if(!branch){
- console.debug(`In game ${this.gameId} no response defined for ${this.lastMove}; clearing conditional moves`);
- return {clearCondMoves:true};
- }
- return branch;
- }
- async checkCondMovesOnOpponentTurn(){
- if(!this.condMoves||!this.moveNo)
- return null;
- if(this.moveNo>this.condMoves.atMoveNo){
- console.debug(`In game ${this.gameId} current move is ${this.moveNo}, but conditional moves defined for move ${this.condMoves.atMoveNo}; clearing conditional moves`);
- return this.saveCondMoves(null);
- }
- return null;
- }
- async saveCondMoves(condMoves){
- if(!this.privateNotes)
- this.privateNotes=PrivateNotes.empty(this.gameId);
- return this.privateNotes.saveCondMoves(condMoves);
- }
- toString(){
- return `GameSGF[gameId=${this.gameId}, @${this.moveNo}, lastMove=${this.lastMove
- }, privateNotes=${this.privateNotes}, hasExtraComments=${this.hasExtraComments}]`;
- }
- }
- class QuickStatus{
- constructor(objects){
- this.objects_=objects;
- }
- static parse(status){
- const errMat=status.match(/\[#Error: (.+?)\]$/m);
- if(errMat)
- throw new Error(`QuickStatus parse error: ${errMat[1]}`);
- const objectsByType=new Map();
- const headersByType=new Map();
- const getFields=str=>{
- const fields=[];
- let field="";
- let quoted=false;
- for(let i=0;i<str.length;i++){
- if(str[i]==="'"){
- if(quoted)
- quoted=false;
- else if(field)
- throw new Error(`Unexpected quote in string: ${str}`);
- else
- quoted=true;
- }else if(str[i]==="\\"&"ed){
- if(++i>=str.length)
- throw new Error(`Unexpected end of string: ${str}`);
- field+=str[i];
- }else if(str[i]===","&&!quoted){
- fields.push(field);
- field="";
- }else
- field+=str[i];
- }
- fields.push(field);
- return fields;
- };
- const scan=function*(str,regexp){
- let mat;
- while(mat=regexp.exec(str))
- yield mat;
- };
- for(const mat of scan(status,/^## ([A-Z]),(.+?)$/mg))
- headersByType.set(mat[1],getFields(mat[2]));
- for(const mat of scan(status,/^([A-Z]),(.+?)$/mg)){
- const type=mat[1];
- let objects=objectsByType.get(type);
- if(!objects){
- objects=[];
- objectsByType.set(type,objects);
- }
- const headers=headersByType.get(type);
- const fields=getFields(mat[2]);
- const object={};
- objects.push(object);
- for(let i=0;i<headers.length;i++)
- object[headers[i]]=fields[i];
- }
- return new QuickStatus(objectsByType);
- }
- static async load(){
- return QuickStatus.parse(await ajax("/quick_status.php",{version:2,no_cache:1,order:0}));
- }
- get messages(){
- return this.objects_.get("M")||[];
- }
- get games(){
- return this.objects_.get("G")||[];
- }
- }
- class TitleUpdater{
- constructor(base,initialCount){
- this.base_=base;
- this.initialCount_=initialCount;
- }
- static create(){
- const mat=document.title.match(/^(.+?)(?: \((\d+)\))?$/)
- return new TitleUpdater(mat[1],mat[2]||null);
- }
- quickUpdate(){
- this.updateInternal_(this.initialCount_,0);
- }
- update(quickStatus){
- this.updateInternal_(quickStatus.games.length,quickStatus.messages.length);
- }
- updateInternal_(gamesCount,messagesCount){
- document.title=`${gamesCount==null?``:`[${gamesCount}${messagesCount?`, ${messagesCount}`:``}] `}${this.base_}`;
- }
- }
- class Manager{
- constructor(titleUpdater){
- this.titleUpdater_=titleUpdater;
- this.quickStatus=null;
- }
- static start(){
- const titleUpdater=TitleUpdater.create();
- titleUpdater.quickUpdate();
- const manager=new Manager(titleUpdater);
- const update=async()=>{
- try{
- await manager.update();
- }finally{
- scheduleUpdate();
- }
- };
- const scheduleUpdate=()=>setTimeout(update,RELOAD_INTERVAL);
- scheduleUpdate();
- return manager;
- }
- async update(){
- if(location.pathname==="/index.php")
- return;
- if(location.pathname==="/status.php")
- location.reload();
- else{
- this.quickStatus=await QuickStatus.load();
- this.titleUpdater_.update(this.quickStatus);
- Promise.all(this.quickStatus.games.map(game=>
- GameSGF.load(game.game_id).then(sgf=>sgf.executeCondMoves()).catch(error=>{
- console.warn(`Error while executing conditional move for game ${gameId}: ${error}`);
- return {error};
- }))).then(results=>{
- if(results.some(result=>result.responded))
- this.update();
- });
- }
- }
- }
- function addCSS(css){
- const styleElem=document.createElement("style");
- styleElem.innerText=css;
- document.head.appendChild(styleElem);
- }
- async function start(){
- const COND_MOVES_TR_INNER_HTML=`\
- <td class="UnderBoard">
- <div id="condMoves" title="Specify branches of conditional moves, e.g.: f3 c6 d2 c3, c6 f3
- Right-click on board to enter coordinates">
- <span>Conditional moves:</span>
- <input id="condMoves" type="text">
- <button id="saveCondMoves" type="button">Save</button>
- </div>
- </td>`;
- LOADING_INDICATOR.init();
- const manager=Manager.start();
- const keyHandlers=new Map();
- if(location.pathname==="/status.php"){
- Promise.all([...document.querySelectorAll("table#gameTable tr td.Button:first-child")].map(gameIdElem=>{
- const gameId=gameIdElem.innerText.trim();
- return GameSGF.load(gameId).then(sgf=>sgf.executeCondMoves()).catch(error=>{
- console.warn(`Error while executing conditional move for game ${gameId}: ${error}`);
- return {error};
- });
- })).then(results=>{
- if(results.some(result=>result.responded))
- location.reload();
- });
- }else if(location.pathname==="/game.php"){
- const gameId=location.searchParams.get("g")||location.searchParams.get("gid");
- let gameState;
- if(location.searchParams.get("a")==="domove")
- gameState="confirmMove";
- else if(location.searchParams.get("a")==="resign")
- gameState="resigning";
- else if(document.querySelector("dl.ExtraInfos dd.Score"))
- gameState="finished";
- else if(document.querySelector("input[name='action'][value='choose_move']"))
- gameState="myMove";
- else
- gameState="theirMove";
- const eidogoLinkImg=document.querySelector("a.NoPrint > img[title='EidoGo Game Player']");
- if(eidogoLinkImg)
- eidogoLinkImg.parentElement.setAttribute("target","_blank");
- if(gameState==="confirmMove"){
- const linkifyField=(imageElement,href)=>{
- if(!imageElement)
- return;
- const td=imageElement.parentElement;
- const link=document.createElement("a");
- link.setAttribute("href",href);
- link.appendChild(imageElement);
- td.appendChild(link);
- };
- const moveParams=new URLSearchParams(location.searchParams);
- for(const mark of [".",","]){
- for(const img of document.querySelectorAll(`table#Goban td[id].brdx img[alt='${mark}'].brdx`)){
- moveParams.set("c",img.parentElement.id);
- linkifyField(img,`/game.php?${moveParams}`);
- }
- }
- moveParams.delete("a");
- moveParams.delete("c");
- for(const mark of ["@","#"])
- linkifyField(document.querySelector(`table#Goban td[id].brdx img[alt='${mark}'].brdx`),`/game.php?${moveParams}`);
- const cancelMove=()=>document.querySelector("input[name='cancel']").click();
- keyHandlers.set("ArrowLeft",cancelMove);
- keyHandlers.set("Home",cancelMove);
- keyHandlers.set("End",cancelMove);
- }else{
- const navigate=(selOptFunc)=>{
- const selMoveOption=document.querySelector("select[name='gotomove'] option[selected]");
- if(!selMoveOption)
- return;
- const newMoveOption=selOptFunc(selMoveOption);
- if(newMoveOption&&newMoveOption!==selMoveOption){
- selMoveOption.removeAttribute("selected");
- newMoveOption.setAttribute("selected","");
- document.querySelector("input[name='movechange']").click();
- }
- };
- keyHandlers.set("ArrowLeft",()=>navigate(o=>o.nextElementSibling));
- keyHandlers.set("ArrowRight",()=>navigate(o=>o.previousElementSibling));
- keyHandlers.set("Home",()=>navigate(o=>o.parentElement.lastElementChild));
- keyHandlers.set("End",()=>navigate(o=>o.parentElement.firstElementChild));
- }
- if(gameState==="confirmMove"||(gameState==="theirMove"&&
- document.querySelector("select[name='gotomove'] option[selected]:first-of-type"))){
- const condMovesRow=document.createElement("tr");
- condMovesRow.innerHTML=COND_MOVES_TR_INNER_HTML;
- const summaryRow=document.querySelector("table#GamePage > tbody > tr:nth-of-type(2)");
- condMovesRow.querySelector("td").setAttribute("colspan",summaryRow.querySelector("td").getAttribute("colspan"));
- if(gameState==="confirmMove")
- condMovesRow.querySelector("#saveCondMoves").style.display="none";
- const condMovesInput=condMovesRow.querySelector("input#condMoves");
- summaryRow.parentElement.insertBefore(condMovesRow,summaryRow);
- const sgfPromise=GameSGF.load(gameId);
- sgfPromise.then(sgf=>{
- if(sgf.condMoves)
- condMovesInput.value=sgf.condMoves.toUserString();
- });
- const moveNoOffset=gameState==="confirmMove"?2:1;
- const saveCondMoves=()=>{
- const promise=sgfPromise.then(sgf=>{
- const condMoves=CondMoves.parseUserString(gameId,sgf.moveNo+moveNoOffset,condMovesInput.value);
- condMovesInput.value=condMoves.toUserString();
- return sgf.saveCondMoves(condMoves);
- });
- promise.catch(error=>{
- let msg;
- if(error instanceof ParseError)
- msg=`Invalid conditional moves: ${error.message}`;
- else
- msg=`Error saving conditional moves: ${error}`;
- console.warn(msg);
- alert(msg);
- });
- return promise;
- };
- condMovesRow.querySelector("#saveCondMoves").addEventListener("click",()=>{
- saveCondMoves();
- return true;
- });
- condMovesInput.addEventListener("keydown",event=>{
- if(event.code==="Enter"){
- saveCondMoves();
- event.preventDefault();
- }
- });
- const rightClickHandler=event=>{
- if(event.button===2){
- const move=coord.fromSGF(event.currentTarget.id);
- const text=condMovesInput.value;
- const focused=document.activeElement===condMovesInput;
- let selRange;
- if(focused)
- selRange=[text.length,text.length];
- else
- selRange=[condMovesInput.selectionStart,condMovesInput.selectionEnd];
- const preText=text.substring(0,selRange[0]);
- const postText=text.substring(selRange[1]);
- let newText=preText;
- if(preText.match(/\S$/))
- newText+=" ";
- newText+=move;
- const newCursorPos=newText.length;
- if(postText.match(/^[^,\s]/))
- newText+=" ";
- newText+=postText;
- condMovesInput.value=newText;
- condMovesInput.focus();
- condMovesInput.setSelectionRange(newCursorPos,newCursorPos);
- event.preventDefault();
- }
- };
- for(const field of document.querySelectorAll("table#Goban td[id].brdx")){
- field.addEventListener("mouseup",rightClickHandler);
- field.addEventListener("contextmenu",event=>event.preventDefault());
- }
- let allowedSubmitButton=null;
- const submitHandler=event=>{
- if(event.target===allowedSubmitButton)
- return;
- saveCondMoves().then(()=>{
- allowedSubmitButton=event.target;
- event.target.click();
- });
- event.preventDefault();
- };
- for(const name of ["nextgame","nextstatus"]){
- const button=document.querySelector(`input[type='submit'][name='${name}']`);
- if(button)
- button.addEventListener("click",submitHandler);
- }
- }
- keyHandlers.set("Space",()=>{
- const skipParams=new URLSearchParams();
- skipParams.set("gid",gameId);
- skipParams.set("nextskip","t");
- location.href=`/confirm.php?${skipParams}`;
- });
- }else if(location.pathname==="/show_games.php"){
- for(const gameIdElem of document.querySelectorAll("table#runningTable tr td.Button:first-child")){
- const infoElem=gameIdElem.parentElement.querySelector("td.ImagesLeft");
- if(infoElem){
- const gameId=gameIdElem.innerText.trim();
- GameSGF.load(gameId).then(sgf=>{
- return sgf.checkCondMovesOnOpponentTurn().then(()=>{
- if(sgf.privateNotes&&sgf.privateNotes.base.trim())
- infoElem.innerHTML+=` <span class="game-info-char" title="There are private notes saved for this game">(p)</span>`;
- if(sgf.condMoves)
- infoElem.innerHTML+=` <span class="game-info-char" title="Conditional moves are defined for this game">(c)</span>`;
- });
- });
- }
- }
- }else if(location.pathname==="/error.php")
- setTimeout(()=>location.href="/status.php",3600*1000);
- keyHandlers.set("Escape",()=>location.href=`/status.php`);
- if(keyHandlers.size){
- document.addEventListener("keydown",event=>{
- if(["INPUT","TEXTAREA"].includes(event.target.tagName))
- return;
- const code=event.code;
- const handler=keyHandlers.get(code);
- if(handler){
- event.preventDefault();
- handler();
- }
- });
- }
- }
- async function onLoaded(){
- addCSS(EXTRA_CSS);
- addCSS(SKIN_GREY);
- location.searchParams=new URLSearchParams(location.search);
- try{
- await start();
- }catch(e){
- console.error(e);
- }
- }
- if(document.readyState==="loading")
- document.addEventListener("DOMContentLoaded",onLoaded,false);
- else
- onLoaded();
- })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址